mirror of https://github.com/boxyhq/jackson.git
E2E tests for admin portal SSO (#846)
* Sync lock file * Add admin portal tests * Use data-testid to enable `page.getByTestId()` * Support data-testid for inner elements * Pass data-testid to sso login button * Remove env-cmd * Update folder structure * Add data-testid * - Use production build for local testing - Set NODE_ENV to pick up .env.test.local * Add test-id for logout dropdown activation * Remove test; superseded with new tests * Fix tests * Wait for navigation to complete * [Failing ci test try fix] Pass url for waiting * [Failing ci test try fix] skip assertion * [Fix failing test] use predicate to match origin * Add back the visibility check * Fix query param * Tweak test code * Update return type of callback * Group tests and reorganise folders * Add actions to login and assert by locator * Support html attribute passing * Set data-testid on edit button * Set data-testids * Final changes * Tweak
This commit is contained in:
parent
43ea311067
commit
305ff93cbb
|
@ -18,7 +18,9 @@ const ConfirmationModal = (props: {
|
|||
<Modal visible={visible} title={title} description={description}>
|
||||
<div className='modal-action'>
|
||||
<ButtonOutline onClick={onCancel}>{t('cancel')}</ButtonOutline>
|
||||
<ButtonDanger onClick={onConfirm}>{actionButtonText || t('delete')}</ButtonDanger>
|
||||
<ButtonDanger onClick={onConfirm} data-testid='confirm-delete'>
|
||||
{actionButtonText || t('delete')}
|
||||
</ButtonDanger>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import classNames from 'classnames';
|
||||
|
||||
export const IconButton = ({ Icon, tooltip, onClick, className }) => {
|
||||
export const IconButton = ({ Icon, tooltip, onClick, className, ...other }) => {
|
||||
return (
|
||||
<div className='tooltip' data-tip={tooltip}>
|
||||
<Icon
|
||||
className={classNames('hover:scale-115 h-5 w-5 cursor-pointer text-secondary', className)}
|
||||
onClick={onClick}
|
||||
{...other}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ export const Navbar = ({ session }: { session: Session | null }) => {
|
|||
className='flex h-8 w-8 items-center justify-center rounded-full bg-secondary uppercase text-cyan-50 focus:outline-none'
|
||||
aria-expanded='false'
|
||||
aria-haspopup='true'
|
||||
data-testid='user-avatar'
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen);
|
||||
}}>
|
||||
|
@ -45,6 +46,7 @@ export const Navbar = ({ session }: { session: Session | null }) => {
|
|||
className='link flex px-4 py-2 text-sm hover:link-primary'
|
||||
role='menuitem'
|
||||
tabIndex={-1}
|
||||
data-testid='logout'
|
||||
id='user-menu-item-2'
|
||||
onClick={() => signOut()}>
|
||||
<PowerIcon className='mr-1 h-5 w-5' aria-hidden />
|
||||
|
|
|
@ -72,14 +72,14 @@ const ConnectionList = ({
|
|||
{t(isSettingsView ? 'admin_portal_sso' : 'enterprise_sso')}
|
||||
</h2>
|
||||
<div className='flex gap-2'>
|
||||
<LinkPrimary Icon={PlusIcon} href={createConnectionUrl} data-test-id='create-connection'>
|
||||
<LinkPrimary Icon={PlusIcon} href={createConnectionUrl} data-testid='create-connection'>
|
||||
{t('new_connection')}
|
||||
</LinkPrimary>
|
||||
{!setupLinkToken && !isSettingsView && (
|
||||
<LinkPrimary
|
||||
Icon={LinkIcon}
|
||||
href='/admin/sso-connection/setup-link/new'
|
||||
data-test-id='create-setup-link'>
|
||||
data-testid='create-setup-link'>
|
||||
{t('new_setup_link')}
|
||||
</LinkPrimary>
|
||||
)}
|
||||
|
@ -165,6 +165,7 @@ const ConnectionList = ({
|
|||
tooltip={t('edit')}
|
||||
Icon={PencilIcon}
|
||||
className='hover:text-green-400'
|
||||
data-testid='edit'
|
||||
onClick={() => {
|
||||
router.push(
|
||||
setupLinkToken
|
||||
|
|
|
@ -149,7 +149,9 @@ const CreateConnection = ({
|
|||
.filter(({ attributes: { hideInSetupView } }) => (setupLinkToken ? !hideInSetupView : true))
|
||||
.map(renderFieldList({ formObj, setFormObj }))}
|
||||
<div className='flex'>
|
||||
<ButtonPrimary loading={loading}>{t('save_changes')}</ButtonPrimary>
|
||||
<ButtonPrimary loading={loading} data-testid='submit-form-create-sso'>
|
||||
{t('save_changes')}
|
||||
</ButtonPrimary>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -171,7 +171,11 @@ const EditConnection = ({ connection, setupLinkToken, isSettingsView = false }:
|
|||
<h6 className='mb-1 font-medium'>{t('delete_this_connection')}</h6>
|
||||
<p className='font-light'>{t('all_your_apps_using_this_connection_will_stop_working')}</p>
|
||||
</div>
|
||||
<ButtonDanger type='button' onClick={toggleDelConfirm} data-modal-toggle='popup-modal'>
|
||||
<ButtonDanger
|
||||
type='button'
|
||||
onClick={toggleDelConfirm}
|
||||
data-modal-toggle='popup-modal'
|
||||
data-testid='delete-connection'>
|
||||
{t('delete')}
|
||||
</ButtonDanger>
|
||||
</section>
|
||||
|
|
|
@ -15,7 +15,7 @@ export const saveConnection = async ({
|
|||
connectionIsSAML: boolean;
|
||||
connectionIsOIDC: boolean;
|
||||
setupLinkToken?: string;
|
||||
callback: (res: Response) => void;
|
||||
callback: (res: Response) => Promise<void>;
|
||||
}) => {
|
||||
const { rawMetadata, redirectUrl, oidcDiscoveryUrl, oidcClientId, oidcClientSecret, metadataUrl, ...rest } =
|
||||
formObj;
|
||||
|
|
|
@ -119,7 +119,7 @@ const SetupLinkList = ({ service }: { service: SetupLinkService }) => {
|
|||
<div className='mb-5 flex items-center justify-between'>
|
||||
<h3>{description}</h3>
|
||||
<div>
|
||||
<LinkPrimary Icon={PlusIcon} href={createSetupLinkUrl} data-test-id='create-setup-link'>
|
||||
<LinkPrimary Icon={PlusIcon} href={createSetupLinkUrl} data-testid='create-setup-link'>
|
||||
{t('new_setup_link')}
|
||||
</LinkPrimary>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { test } from '@playwright/test';
|
||||
|
||||
test('MAGIC_LINK in globalSetup should log me in', async ({ page }) => {
|
||||
await page.goto('/admin/sso-connection');
|
||||
|
||||
// Find the button and click on it
|
||||
await page.locator('data-test-id=create-connection').click();
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const TEST_SSO_CONNECTION_NAME = 'pw_admin_portal_sso';
|
||||
const MOCKSAML_ORIGIN = 'https://mocksaml.com';
|
||||
const MOCKSAML_METADATA_URL = `${MOCKSAML_ORIGIN}/api/saml/metadata`;
|
||||
const MOCKSAML_SIGNIN_BUTTON_NAME = 'Sign In';
|
||||
|
||||
test.describe('Admin Portal SSO', () => {
|
||||
test('should be able to add SSO connection to mocksaml.com', async ({ page }) => {
|
||||
await page.goto('/admin/settings');
|
||||
// Find the new connection button and click on it
|
||||
await page.getByTestId('create-connection').click();
|
||||
// Fill the name for the connection
|
||||
const nameInput = page.locator('#name');
|
||||
await nameInput.fill(TEST_SSO_CONNECTION_NAME);
|
||||
// Enter the metadata url for mocksaml.com in the form
|
||||
const metadataUrlInput = page.locator('#metadataUrl');
|
||||
await metadataUrlInput.fill(MOCKSAML_METADATA_URL);
|
||||
// submit the form
|
||||
await page.getByTestId('submit-form-create-sso').click();
|
||||
// check if the added connection appears in the connection list
|
||||
await expect(page.getByText(TEST_SSO_CONNECTION_NAME)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be able to login via mocksaml.com SSO', async ({ page, baseURL }) => {
|
||||
const userAvatarLocator = page.getByTestId('user-avatar');
|
||||
// Logout from the magic link authentication
|
||||
await page.goto('/');
|
||||
await userAvatarLocator.click();
|
||||
await page.getByTestId('logout').click();
|
||||
// Click on login with sso button
|
||||
await page.getByTestId('sso-login-button').click();
|
||||
// Perform sign in at mocksaml
|
||||
await page.waitForURL((url) => url.origin === MOCKSAML_ORIGIN);
|
||||
await page.getByPlaceholder('jackson').fill('bob');
|
||||
await page.getByRole('button', { name: MOCKSAML_SIGNIN_BUTTON_NAME }).click();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// assert login state
|
||||
await expect(userAvatarLocator).toBeVisible();
|
||||
});
|
||||
|
||||
test('delete the SSO connection', async ({ page }) => {
|
||||
await page.goto('/admin/settings');
|
||||
// select the row of the connection list table, then locate the edit button
|
||||
const editButton = page.getByText(TEST_SSO_CONNECTION_NAME).locator('..').getByTestId('edit');
|
||||
await editButton.click();
|
||||
// click the delete and confirm deletion
|
||||
await page.getByTestId('delete-connection').click();
|
||||
await page.getByTestId('confirm-delete').click();
|
||||
// check that the SSO connection is deleted from the connection list
|
||||
await expect(page.getByText(TEST_SSO_CONNECTION_NAME)).not.toBeVisible();
|
||||
});
|
||||
});
|
|
@ -33,7 +33,7 @@
|
|||
"prepare:npm": "cd npm && npm install --legacy-peer-deps",
|
||||
"prepare:sdk": "cd sdk/ui/react && npm install",
|
||||
"pretest:e2e": "env-cmd -f .env.test.local ts-node --log-error e2e/seedAuthDb.ts",
|
||||
"test:e2e": "env-cmd -f .env.test.local playwright test",
|
||||
"test:e2e": "playwright test",
|
||||
"test": "cd npm && npm run test"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -96,4 +96,4 @@
|
|||
"engines": {
|
||||
"node": ">=14.18.1 <=18.x"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,9 @@ const Login = ({ csrfToken, tenant, product }: InferGetServerSidePropsType<typeo
|
|||
container: 'mt-2',
|
||||
button: 'btn-outline btn-block btn',
|
||||
}}
|
||||
innerProps={{
|
||||
button: { 'data-testid': 'sso-login-button' },
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,10 +17,13 @@ const config: PlaywrightTestConfig = {
|
|||
// Run your local dev server before starting the tests:
|
||||
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
|
||||
webServer: {
|
||||
command: process.env.CI ? 'npm run start' : 'npm run postgres',
|
||||
command: process.env.CI ? 'npm run start' : 'npm run build && npm run start',
|
||||
port: 5225,
|
||||
timeout: 60 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
},
|
||||
|
||||
use: {
|
||||
|
|
|
@ -46,9 +46,9 @@ export interface LoginProps {
|
|||
*/
|
||||
classNames?: { container?: string; button?: string; input?: string; label?: string };
|
||||
innerProps?: {
|
||||
input?: InputHTMLAttributes<HTMLInputElement>;
|
||||
button?: ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
label?: LabelHTMLAttributes<HTMLLabelElement>;
|
||||
container?: HTMLAttributes<HTMLDivElement>;
|
||||
input?: InputHTMLAttributes<HTMLInputElement> & { 'data-testid'?: string };
|
||||
button?: ButtonHTMLAttributes<HTMLButtonElement> & { 'data-testid'?: string };
|
||||
label?: LabelHTMLAttributes<HTMLLabelElement> & { 'data-testid'?: string };
|
||||
container?: HTMLAttributes<HTMLDivElement> & { 'data-testid'?: string };
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue