diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index f93b75da1..b8335f17b 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -103,7 +103,7 @@ jobs: test: needs: deploy timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout code diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index 568c353e5..526da0fb3 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -44,6 +44,8 @@ export default defineConfig({ timeout: operationTimeout, // default: 5000ms }, use: { + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15.3.2) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/129.0.6668.29 Safari/537.36', actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none baseURL: process.env.PLAYWRIGHT_TEST_URL, diff --git a/packages/tests-e2e/src/connect/models/AppendModel.ts b/packages/tests-e2e/src/connect/models/AppendModel.ts index 20efe4530..63b6757b6 100644 --- a/packages/tests-e2e/src/connect/models/AppendModel.ts +++ b/packages/tests-e2e/src/connect/models/AppendModel.ts @@ -1,7 +1,7 @@ import type { Page } from '@playwright/test'; -import { ErrorTexts } from '../utils/Constants'; -import { expectError } from '../utils/ExpectScreen'; +import { ErrorTexts, ScreenNames } from '../utils/Constants'; +import { expectError, expectScreen } from '../utils/ExpectScreen'; import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; export class AppendModel { @@ -24,6 +24,17 @@ export class AppendModel { } } + autoAppendPasskey(complete: boolean, operationTrigger: () => Promise) { + if (complete) { + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + } else { + return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, async () => { + await expectScreen(this.page, ScreenNames.PasskeyAppend); + await this.page.waitForSelector('.button-loading-container', { state: 'detached' }); + }); + } + } + confirmAppended() { return this.page.getByRole('button', { name: 'Continue' }).click(); } diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 4d891e6a9..f3d87f0ad 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -32,14 +32,14 @@ export class BaseModel { this.page = page; this.authenticator = authenticator; this.blocker = blocker; - this.signup = new SignupModel(page); this.login = new LoginModel(page, authenticator); this.append = new AppendModel(page, authenticator); + this.signup = new SignupModel(page, this.append); + this.mfa = new MFAModel(page, this.append); this.home = new HomeModel(page); this.passkeyList = new PasskeyListModel(page, authenticator); this.webhook = new WebhookModel(page); this.storage = new StorageModel(page); - this.mfa = new MFAModel(page); } loadSignup() { @@ -64,15 +64,14 @@ export class BaseModel { async createUser(invited: boolean, append: boolean) { this.email = await this.signup.autofillCredentials(); - await this.signup.submit(); + await this.signup.submit(invited, append); this.mfa.registerTokenUsed(); if (invited) { - await this.expectScreen(ScreenNames.PasskeyAppend); if (append) { - await this.append.appendPasskey(true); await this.expectScreen(ScreenNames.PasskeyAppended); await this.append.confirmAppended(); } else { + await this.expectScreen(ScreenNames.PasskeyAppend); await this.append.skipAppend(); } } diff --git a/packages/tests-e2e/src/connect/models/MFAModel.ts b/packages/tests-e2e/src/connect/models/MFAModel.ts index d5131c723..8e5adda1c 100644 --- a/packages/tests-e2e/src/connect/models/MFAModel.ts +++ b/packages/tests-e2e/src/connect/models/MFAModel.ts @@ -1,12 +1,16 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; +import type { AppendModel } from './AppendModel'; + export class MFAModel { page: Page; + append: AppendModel; timestamp: number; - constructor(page: Page) { + constructor(page: Page, append: AppendModel) { this.page = page; + this.append = append; this.timestamp = Date.now(); } @@ -21,7 +25,12 @@ export class MFAModel { this.registerTokenUsed(); } - submit() { - return this.page.getByRole('button', { name: 'Submit' }).click(); + submit(invited: boolean, autoAppend: boolean) { + if (invited) { + const operationTrigger = () => this.page.getByRole('button', { name: 'Submit' }).click(); + return this.append.autoAppendPasskey(autoAppend, operationTrigger); + } else { + return this.page.getByRole('button', { name: 'Submit' }).click(); + } } } diff --git a/packages/tests-e2e/src/connect/models/SignupModel.ts b/packages/tests-e2e/src/connect/models/SignupModel.ts index 13e46f26d..852d34993 100644 --- a/packages/tests-e2e/src/connect/models/SignupModel.ts +++ b/packages/tests-e2e/src/connect/models/SignupModel.ts @@ -1,10 +1,14 @@ import type { Page } from '@playwright/test'; +import type { AppendModel } from './AppendModel'; + export class SignupModel { page: Page; + append: AppendModel; - constructor(page: Page) { + constructor(page: Page, append: AppendModel) { this.page = page; + this.append = append; } async autofillCredentials(): Promise { @@ -12,7 +16,12 @@ export class SignupModel { return await this.page.getByPlaceholder('Email').inputValue(); } - submit() { - return this.page.getByRole('button', { name: 'Sign up' }).click(); + submit(invited: boolean, autoAppend: boolean) { + if (invited) { + const operationTrigger = () => this.page.getByRole('button', { name: 'Sign up' }).click(); + return this.append.autoAppendPasskey(autoAppend, operationTrigger); + } else { + return this.page.getByRole('button', { name: 'Sign up' }).click(); + } } } diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 3add83f22..d50d4138e 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -2,16 +2,16 @@ import { expect } from '@playwright/test'; import { test } from '../fixtures/BaseTest'; import { password, ScreenNames } from '../utils/Constants'; -import { loadPasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { loadBeforePasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('append component', () => { setupVirtualAuthenticator(test); setupNetworkBlocker(test); setupUser(test, true, false); - loadPasskeyAppend(test); + loadBeforePasskeyAppend(test); test('successful passkey append on login', async ({ model }) => { - await model.append.appendPasskey(true); + await model.mfa.submit(true, true); await model.expectScreen(ScreenNames.PasskeyAppended); await model.append.confirmAppended(); @@ -19,13 +19,13 @@ test.describe('append component', () => { }); test('failed passkey append on login', async ({ model }) => { - await model.append.appendPasskey(false); + await model.mfa.submit(true, false); }); test('Corbado FAPI unavailable after authentication', async ({ model }) => { await model.blocker.blockCorbadoFAPIFinishEndpoint(); - await model.append.appendPasskey(true); + await model.mfa.submit(true, true); await model.expectScreen(ScreenNames.Home); }); }); @@ -48,7 +48,7 @@ test.describe('skip append component', () => { await model.expectScreen(ScreenNames.MFA); await model.mfa.autofillTOTP(); - await model.mfa.submit(); + await model.mfa.submit(false, false); await model.expectScreen(ScreenNames.Home); }); @@ -67,7 +67,7 @@ test.describe('skip append component', () => { await model.expectScreen(ScreenNames.MFA); await model.mfa.autofillTOTP(); - await model.mfa.submit(); + await model.mfa.submit(false, false); await model.expectScreen(ScreenNames.Home); }); }); diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index 90efe88d0..4f05a47e2 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -83,7 +83,7 @@ export function setupUser( } // assumes that setupUser(test, true, false) has been called right before -export function loadPasskeyAppend( +export function loadBeforePasskeyAppend( test: TestType< PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions @@ -100,8 +100,8 @@ export function loadPasskeyAppend( await model.expectScreen(ScreenNames.MFA); await model.mfa.autofillTOTP(); - await model.mfa.submit(); - await model.expectScreen(ScreenNames.PasskeyAppend); + // await model.mfa.submit(); + // await model.expectScreen(ScreenNames.PasskeyAppend); }); } diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 6d647c4a0..104413850 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -15,7 +15,7 @@ test.describe('login component (without invitation token)', () => { await model.expectScreen(ScreenNames.MFA); await model.mfa.autofillTOTP(); - await model.mfa.submit(); + await model.mfa.submit(false, false); await model.expectScreen(ScreenNames.Home); }); }); @@ -35,8 +35,7 @@ test.describe('login component (with invitation token, without passkeys)', () => await model.expectScreen(ScreenNames.MFA); await model.mfa.autofillTOTP(); - await model.mfa.submit(); - await model.expectScreen(ScreenNames.PasskeyAppend); + await model.mfa.submit(true, false); await model.append.skipAppend(); await model.expectScreen(ScreenNames.Home); diff --git a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts index 36156d7da..001cff981 100644 --- a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts @@ -1,7 +1,7 @@ import { test } from '../fixtures/BaseTest'; import { ScreenNames, WebhookTypes } from '../utils/Constants'; import { - loadPasskeyAppend, + loadBeforePasskeyAppend, loadPasskeyList, setupNetworkBlocker, setupUser, @@ -34,11 +34,11 @@ test.describe.serial('webhook tests', () => { setupVirtualAuthenticator(test); setupNetworkBlocker(test); setupUser(test, true, false); - loadPasskeyAppend(test); + loadBeforePasskeyAppend(test); setupWebhooks(test, [WebhookTypes.Create]); test('successful passkey append on login (+ webhook)', async ({ model }) => { - await model.append.appendPasskey(true); + await model.mfa.submit(true, true); await model.expectScreen(ScreenNames.PasskeyAppended); await model.append.confirmAppended();