From ad561e8b048e8ca858b183527785049b8dc4269c Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 8 Jan 2025 17:53:41 +0100 Subject: [PATCH 01/14] add a few error state tests for login component --- .../tests-e2e/src/connect/models/BaseModel.ts | 10 +++-- .../src/connect/models/LoginModel.ts | 38 ++++++++++++++++--- .../src/connect/models/PasskeyListModel.ts | 10 ++--- .../tests-e2e/src/connect/scenarios/hooks.ts | 20 +++++----- .../src/connect/scenarios/login.spec.ts | 35 +++++++++++++++-- .../tests-e2e/src/connect/utils/Constants.ts | 8 ++++ .../src/connect/utils/ExpectScreen.ts | 20 +++++++++- 7 files changed, 112 insertions(+), 29 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 996225a3c..b68e29351 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -1,7 +1,7 @@ -import type { Page } from '@playwright/test'; +import { Page } from '@playwright/test'; -import { ScreenNames } from '../utils/Constants'; -import { expectScreen } from '../utils/ExpectScreen'; +import { ErrorTexts, ScreenNames } from '../utils/Constants'; +import { expectScreen, expectError } from '../utils/ExpectScreen'; import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; import { AppendModel } from './AppendModel'; import { HomeModel } from './HomeModel'; @@ -53,6 +53,10 @@ export class BaseModel { return expectScreen(this.page, screenName); } + expectError(message: ErrorTexts) { + return expectError(this.page, message); + } + async createUser(invited: boolean, append: boolean) { this.email = await this.signup.autofillCredentials(); await this.signup.submit(); diff --git a/packages/tests-e2e/src/connect/models/LoginModel.ts b/packages/tests-e2e/src/connect/models/LoginModel.ts index eed6e05c3..8f5766150 100644 --- a/packages/tests-e2e/src/connect/models/LoginModel.ts +++ b/packages/tests-e2e/src/connect/models/LoginModel.ts @@ -1,7 +1,9 @@ -import { expect } from '@playwright/test'; import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; +import { expectScreen } from '../utils/ExpectScreen'; +import { ScreenNames } from '../utils/Constants'; export class LoginModel { page: Page; @@ -12,16 +14,40 @@ export class LoginModel { this.authenticator = authenticator; } - submitPasskeyButton(): Promise { + submitPasskeyButton(complete: boolean) { const operationTrigger = () => this.page.locator('.cb-passkey-button').click(); - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + if (complete) { + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + } else { + return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => + expectScreen(this.page, ScreenNames.PasskeyError1), + ); + } } - removePasskeyButton(): Promise { + removePasskeyButton() { return this.page.locator('.cb-switch').click(); } - async submitEmail(email: string, withPasskey: boolean): Promise { + async repeatedlyFailPasskeyInput() { + const operationTrigger1 = () => this.page.getByRole('button', { name: 'Continue' }).click(); + await this.authenticator.startAndCancelPasskeyOperation(operationTrigger1, () => + expectScreen(this.page, ScreenNames.PasskeyError2), + ); + + const operationTrigger2 = () => this.page.getByRole('button', { name: 'Try again' }).click(); + await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => + this.page.waitForTimeout(100), + ); + await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => + this.page.waitForTimeout(100), + ); + await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => + expectScreen(this.page, ScreenNames.InitLoginFallback), + ); + } + + async submitEmail(email: string, withPasskey: boolean) { await this.page.getByLabel('Email address').fill(email); if (withPasskey) { const operationTrigger = () => this.page.getByRole('button', { name: 'Login' }).click(); @@ -31,7 +57,7 @@ export class LoginModel { } } - async submitFallbackCredentials(email: string, password: string, emailAutofilled = false): Promise { + async submitFallbackCredentials(email: string, password: string, emailAutofilled = false) { if (emailAutofilled) { await expect(this.page.getByPlaceholder('Email')).toHaveValue(email); } else { diff --git a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts index 52f47392e..3d8e836a7 100644 --- a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts +++ b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts @@ -16,17 +16,17 @@ export class PasskeyListModel { return expect(this.page.locator('.cb-passkey-list-item')).toHaveCount(n); } - async deletePasskey(index: number) { + async deletePasskey(index: number): Promise { await this.page.locator('.cb-passkey-list-item-delete-icon').nth(index).click(); await this.page.getByRole('button', { name: 'Delete' }).click(); } - async appendPasskey(complete: boolean) { - const operationTrigger = () => this.page.getByRole('button', { name: 'Add a passkey' }).click(); + appendPasskey(complete: boolean): Promise { + const operationTrigger: () => Promise = (): Promise => this.page.getByRole('button', { name: 'Add a passkey' }).click(); if (complete) { - await this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); } else { - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => + return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, (): Promise => expect(this.page.locator('.cb-notification-text')).toHaveText( 'You have cancelled setting up your passkey. Please try again.', ), diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index eaa287ded..8abea9824 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -24,16 +24,16 @@ export function setupVirtualAuthenticator( }); } -// export function loadInvitationToken( -// test: TestType< -// PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, -// PlaywrightWorkerArgs & PlaywrightWorkerOptions -// >, -// ) { -// test.beforeEach(async ({ model }) => { -// await model.loadInvitationToken(); -// }); -// } +export function loadInvitationToken( + test: TestType< + PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, + PlaywrightWorkerArgs & PlaywrightWorkerOptions + >, +) { + test.beforeEach(async ({ model }) => { + await model.loadInvitationToken(); + }); +} export function loadSignup( test: TestType< diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 230229727..8dda5d79d 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -1,6 +1,6 @@ import { test } from '../fixtures/BaseTest'; -import { password, ScreenNames } from '../utils/Constants'; -import { setupUser, setupVirtualAuthenticator } from './hooks'; +import { ErrorTexts, password, ScreenNames } from '../utils/Constants'; +import { loadInvitationToken, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('login component (without invitation token)', () => { setupUser(test, false); @@ -41,7 +41,7 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.home.logout(); await model.expectScreen(ScreenNames.InitLoginOneTap); - await model.login.submitPasskeyButton(); + await model.login.submitPasskeyButton(true); await model.expectScreen(ScreenNames.Home); }); @@ -55,4 +55,33 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.login.submitEmail(model.email, true); await model.expectScreen(ScreenNames.Home); }); + + test('attempt login with repeated failed passkey input', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLoginOneTap); + + await model.login.submitPasskeyButton(false); + await model.login.repeatedlyFailPasskeyInput(); + }); +}); + +test.describe('login component (input validation)', () => { + setupVirtualAuthenticator(test); + loadInvitationToken(test); + + test('attempt login with incomplete credentials', async ({ model }) => { + await model.loadLogin(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail('', false); + await model.expectError(ErrorTexts.EmptyEmail); + }); + + test('attempt login with unknown credentials', async ({ model }) => { + await model.loadLogin(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail('unknown-email@corbado.com', false); + await model.expectError(ErrorTexts.UnknownEmail); + }); }); diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index 3b3a444d4..29159a1f3 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -7,6 +7,14 @@ export enum ScreenNames { PasskeyAppended, Home, PasskeyList, + PasskeyError1, + PasskeyError2, +} + +export enum ErrorTexts { + EmptyEmail = 'Enter your email address.', + UnknownEmail = 'There is no account registered to that email address.', + CancelledPasskey = 'You have cancelled setting up your passkey. Please try again.', } export const phone = '+4915121609839'; diff --git a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts index a27179fb8..0befd2de2 100644 --- a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts +++ b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts @@ -1,9 +1,9 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import { ScreenNames } from './Constants'; +import { ErrorTexts, ScreenNames } from './Constants'; -export const expectScreen = async (page: Page, screenName: ScreenNames) => { +export const expectScreen = async (page: Page, screenName: ScreenNames): Promise => { switch (screenName) { case ScreenNames.InitSignup: await expect(page.locator('div.font-bold.text-xl')).toHaveText('Signup'); @@ -41,7 +41,23 @@ export const expectScreen = async (page: Page, screenName: ScreenNames) => { await expect(page.locator('.cb-connect-passkey-list')).toBeVisible(); return; + case ScreenNames.PasskeyError1: + await expect(page.locator('.cb-connect-container').locator('.cb-login-header')).toContainText( + 'Use your passkey to confirm it’s really you', + ); + return; + + case ScreenNames.PasskeyError2: + await expect(page.locator('.cb-connect-container').locator('.cb-login-header')).toContainText( + 'Something went wrong!', + ); + return; + default: throw new Error('Invalid screen'); } }; + +export const expectError = (page: Page, message: ErrorTexts): Promise => { + return expect(page.locator('.cb-notification-text')).toHaveText(message); +}; From d807791d6e6d33fbe186211ec9d6fda5e0ceccf7 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 8 Jan 2025 18:58:11 +0100 Subject: [PATCH 02/14] prettier --- packages/tests-e2e/src/connect/models/LoginModel.ts | 8 ++------ .../src/connect/models/PasskeyListModel.ts | 13 ++++++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/LoginModel.ts b/packages/tests-e2e/src/connect/models/LoginModel.ts index 8f5766150..e31a921b7 100644 --- a/packages/tests-e2e/src/connect/models/LoginModel.ts +++ b/packages/tests-e2e/src/connect/models/LoginModel.ts @@ -36,12 +36,8 @@ export class LoginModel { ); const operationTrigger2 = () => this.page.getByRole('button', { name: 'Try again' }).click(); - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => - this.page.waitForTimeout(100), - ); - await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => - this.page.waitForTimeout(100), - ); + await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => this.page.waitForTimeout(100)); + await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => this.page.waitForTimeout(100)); await this.authenticator.startAndCancelPasskeyOperation(operationTrigger2, () => expectScreen(this.page, ScreenNames.InitLoginFallback), ); diff --git a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts index 3d8e836a7..eab308022 100644 --- a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts +++ b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts @@ -22,14 +22,17 @@ export class PasskeyListModel { } appendPasskey(complete: boolean): Promise { - const operationTrigger: () => Promise = (): Promise => this.page.getByRole('button', { name: 'Add a passkey' }).click(); + const operationTrigger: () => Promise = (): Promise => + this.page.getByRole('button', { name: 'Add a passkey' }).click(); if (complete) { return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); } else { - return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, (): Promise => - expect(this.page.locator('.cb-notification-text')).toHaveText( - 'You have cancelled setting up your passkey. Please try again.', - ), + return this.authenticator.startAndCancelPasskeyOperation( + operationTrigger, + (): Promise => + expect(this.page.locator('.cb-notification-text')).toHaveText( + 'You have cancelled setting up your passkey. Please try again.', + ), ); } } From 67f5f0d95e702644885dfd9217580b597b639e14 Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 14 Jan 2025 19:36:09 +0100 Subject: [PATCH 03/14] login error state tests --- .../src/connect/fixtures/BaseTest.ts | 11 ++- .../tests-e2e/src/connect/models/BaseModel.ts | 29 +++++--- .../src/connect/models/LoginModel.ts | 4 +- .../tests-e2e/src/connect/scenarios/hooks.ts | 15 +++- .../src/connect/scenarios/login.spec.ts | 67 ++++++++++++++++- .../src/connect/utils/CDPSessionManager.ts | 16 ++++ .../src/connect/utils/ExpectScreen.ts | 3 +- .../connect/utils/NetworkRequestBlocker.ts | 45 +++++++++++ .../src/connect/utils/VirtualAuthenticator.ts | 74 +++++++++++-------- 9 files changed, 210 insertions(+), 54 deletions(-) create mode 100644 packages/tests-e2e/src/connect/utils/CDPSessionManager.ts create mode 100644 packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts diff --git a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts index 19c5cbb73..bc0688907 100644 --- a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts +++ b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts @@ -1,14 +1,19 @@ import { test as base } from '@playwright/test'; import { BaseModel } from '../models/BaseModel'; +import { CDPSessionManager } from '../utils/CDPSessionManager'; import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; +import { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; export const test = base.extend<{ model: BaseModel }>({ model: async ({ page }, use) => { - const authenticator = new VirtualAuthenticator(); - await authenticator.initializeCDPSession(page); + const cdpManager = new CDPSessionManager(); + await cdpManager.initialize(page); - const model = new BaseModel(page, authenticator); + const authenticator = new VirtualAuthenticator(cdpManager); + const blocker = new NetworkRequestBlocker(cdpManager); + + const model = new BaseModel(page, authenticator, blocker); await use(model); }, diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index b68e29351..4952b90ff 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -1,7 +1,9 @@ -import { Page } from '@playwright/test'; +import type { Page } from '@playwright/test'; -import { ErrorTexts, ScreenNames } from '../utils/Constants'; -import { expectScreen, expectError } from '../utils/ExpectScreen'; +import type { ErrorTexts } from '../utils/Constants'; +import { ScreenNames } from '../utils/Constants'; +import { expectError,expectScreen } from '../utils/ExpectScreen'; +import type { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; import { AppendModel } from './AppendModel'; import { HomeModel } from './HomeModel'; @@ -12,6 +14,7 @@ import { SignupModel } from './SignupModel'; export class BaseModel { page: Page; authenticator: VirtualAuthenticator; + blocker: NetworkRequestBlocker; signup: SignupModel; login: LoginModel; append: AppendModel; @@ -19,9 +22,10 @@ export class BaseModel { passkeyList: PasskeyListModel; email = ''; - constructor(page: Page, authenticator: VirtualAuthenticator) { + constructor(page: Page, authenticator: VirtualAuthenticator, blocker: NetworkRequestBlocker) { 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); @@ -29,14 +33,6 @@ export class BaseModel { this.passkeyList = new PasskeyListModel(page, authenticator); } - addWebAuthn() { - return this.authenticator.addWebAuthn(); - } - - removeWebAuthn() { - return this.authenticator.removeWebAuthn(); - } - loadInvitationToken() { return this.page.goto('/login?invitationToken=inv-token-correct'); } @@ -49,6 +45,10 @@ export class BaseModel { return this.page.goto('/login'); } + loadHome() { + return this.page.goto('/home'); + } + expectScreen(screenName: ScreenNames) { return expectScreen(this.page, screenName); } @@ -90,4 +90,9 @@ export class BaseModel { // // expect(response.ok).toBeTruthy(); // } + + async clearLocalStorageAndCookies() { + await this.page.evaluate(() => localStorage.clear()); + await this.page.context().clearCookies(); + } } diff --git a/packages/tests-e2e/src/connect/models/LoginModel.ts b/packages/tests-e2e/src/connect/models/LoginModel.ts index e31a921b7..8a6c5a91e 100644 --- a/packages/tests-e2e/src/connect/models/LoginModel.ts +++ b/packages/tests-e2e/src/connect/models/LoginModel.ts @@ -1,9 +1,9 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; -import { expectScreen } from '../utils/ExpectScreen'; import { ScreenNames } from '../utils/Constants'; +import { expectScreen } from '../utils/ExpectScreen'; +import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; export class LoginModel { page: Page; diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index 8abea9824..5368f699e 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -16,11 +16,22 @@ export function setupVirtualAuthenticator( >, ) { test.beforeEach(async ({ model }) => { - await model.addWebAuthn(); + await model.authenticator.addWebAuthn(); }); test.afterEach(async ({ model }) => { - await model.removeWebAuthn(); + await model.authenticator.removeWebAuthn(); + }); +} + +export function setupNetworkBlocker( + test: TestType< + PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, + PlaywrightWorkerArgs & PlaywrightWorkerOptions + >, +) { + test.beforeEach(async ({ model }) => { + await model.blocker.enableBlocking(); }); } diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 8dda5d79d..74fae0e5a 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -1,6 +1,6 @@ import { test } from '../fixtures/BaseTest'; import { ErrorTexts, password, ScreenNames } from '../utils/Constants'; -import { loadInvitationToken, setupUser, setupVirtualAuthenticator } from './hooks'; +import { loadInvitationToken, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('login component (without invitation token)', () => { setupUser(test, false); @@ -35,6 +35,7 @@ test.describe('login component (with invitation token, without passkeys)', () => test.describe('login component (with invitation token, with passkeys)', () => { setupVirtualAuthenticator(test); + setupNetworkBlocker(test); setupUser(test, true, true); test('successful login with passkey (one-tap)', async ({ model }) => { @@ -63,10 +64,65 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.login.submitPasskeyButton(false); await model.login.repeatedlyFailPasskeyInput(); }); + + test('inaccessible passkey on login', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLoginOneTap); + + await model.authenticator.clearCredentials(); + await model.clearLocalStorageAndCookies(); + await model.loadInvitationToken(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, false); + await model.expectScreen(ScreenNames.InitLoginFallback); + }); + + test('Corbado FAPI unavailable after authentication', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLoginOneTap); + + await model.blocker.blockCorbadoFAPIFinishEndpoint(); + + await model.login.submitPasskeyButton(true); + await model.expectScreen(ScreenNames.InitLoginFallback); + }); + + test.skip('passkey signature validation fails', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLoginOneTap); + + await model.authenticator.clearCredentials(); + await model.authenticator.addDummyCredential(); + await model.login.removePasskeyButton(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, true); + await model.expectScreen(ScreenNames.InitLoginFallback); + }); + + test('attempt login with server-side deleted passkey', async ({ model }) => { + await model.home.gotoPasskeyList(); + await model.expectScreen(ScreenNames.PasskeyList); + + await model.passkeyList.expectPasskeys(1); + await model.passkeyList.deletePasskey(0); + await model.passkeyList.expectPasskeys(0); + + await model.loadHome(); + await model.expectScreen(ScreenNames.Home); + + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, false); + await model.expectScreen(ScreenNames.InitLoginFallback); + }); }); -test.describe('login component (input validation)', () => { +test.describe('login component (without user)', () => { setupVirtualAuthenticator(test); + setupNetworkBlocker(test); loadInvitationToken(test); test('attempt login with incomplete credentials', async ({ model }) => { @@ -84,4 +140,11 @@ test.describe('login component (input validation)', () => { await model.login.submitEmail('unknown-email@corbado.com', false); await model.expectError(ErrorTexts.UnknownEmail); }); + + test('Corbado FAPI unavailable', async ({ model }) => { + await model.blocker.blockCorbadoFAPI(); + + await model.loadLogin(); + await model.expectScreen(ScreenNames.InitLoginFallback); + }); }); diff --git a/packages/tests-e2e/src/connect/utils/CDPSessionManager.ts b/packages/tests-e2e/src/connect/utils/CDPSessionManager.ts new file mode 100644 index 000000000..49d28660e --- /dev/null +++ b/packages/tests-e2e/src/connect/utils/CDPSessionManager.ts @@ -0,0 +1,16 @@ +import type { CDPSession, Page } from '@playwright/test'; + +export class CDPSessionManager { + #cdpClient: CDPSession | null = null; + + async initialize(page: Page) { + this.#cdpClient = await page.context().newCDPSession(page); + } + + getClient(): CDPSession { + if (!this.#cdpClient) { + throw new Error('CDP client not initialized'); + } + return this.#cdpClient; + } +} diff --git a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts index 0befd2de2..610ae9e56 100644 --- a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts +++ b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts @@ -1,7 +1,8 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import { ErrorTexts, ScreenNames } from './Constants'; +import type { ErrorTexts } from './Constants'; +import { ScreenNames } from './Constants'; export const expectScreen = async (page: Page, screenName: ScreenNames): Promise => { switch (screenName) { diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts new file mode 100644 index 000000000..c47633429 --- /dev/null +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -0,0 +1,45 @@ +import type { CDPSession } from '@playwright/test'; + +import type { CDPSessionManager } from './CDPSessionManager'; + +export class NetworkRequestBlocker { + #cdpClient: CDPSession; + + constructor(cdpManager: CDPSessionManager) { + this.#cdpClient = cdpManager.getClient(); + } + + enableBlocking() { + return this.#cdpClient.send('Network.enable'); + } + + blockCorbadoCDN() { + return this.#cdpClient.send("Network.setBlockedURLs", { + urls: ["cdn.vr.corbado-staging.io/*"], + }); + } + + blockCorbadoFAPI() { + return this.#cdpClient.send("Network.setBlockedURLs", { + urls: ["*.frontendapi.cloud.corbado-staging.io/v2/connect"], + }); + } + + blockCorbadoFAPIFinishEndpoint() { + return this.#cdpClient.send("Network.setBlockedURLs", { + urls: ["*.frontendapi.cloud.corbado-staging.io/v2/connect/*/finish"], + }); + } + + blockCorbadoBAPI() { + return this.#cdpClient.send("Network.setBlockedURLs", { + urls: ["api.vr.corbado-staging.io"], + }); + } + + blockCorbadoConnectTokenEndpoint() { + return this.#cdpClient.send("Network.setBlockedURLs", { + urls: ["vrdemo.vr.corbado-staging.io/corbadoTokens"], + }) + } +} diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index a771ecc86..4fe3c3b21 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -1,20 +1,17 @@ -import type { CDPSession, Page } from '@playwright/test'; +import type { CDPSession } from '@playwright/test'; +import type { CDPSessionManager } from './CDPSessionManager'; import { operationTimeout } from './Constants'; export class VirtualAuthenticator { - #cdpClient: CDPSession | null = null; + #cdpClient: CDPSession; #authenticatorId = ''; - async initializeCDPSession(page: Page) { - this.#cdpClient = await page.context().newCDPSession(page); + constructor(cdpManager: CDPSessionManager) { + this.#cdpClient = cdpManager.getClient(); } async addWebAuthn(passkeySupported = true) { - if (!this.#cdpClient) { - throw new Error('CDP client not initialized'); - } - await this.#cdpClient.send('WebAuthn.enable'); const result = await this.#cdpClient.send('WebAuthn.addVirtualAuthenticator', { options: passkeySupported @@ -34,59 +31,72 @@ export class VirtualAuthenticator { this.#authenticatorId = result.authenticatorId; } - async removeWebAuthn() { - if (!this.#cdpClient) { - throw new Error('CDP client not initialized'); - } - - await this.#cdpClient.send('WebAuthn.removeVirtualAuthenticator', { + removeWebAuthn() { + return this.#cdpClient.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId: this.#authenticatorId, }); } async startAndCompletePasskeyOperation(operationTrigger: () => Promise) { - if (!this.#cdpClient) { - throw new Error('CDP client not initialized'); - } - const operationCompleted = new Promise(resolve => { this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); }); const wait = new Promise(resolve => setTimeout(resolve, operationTimeout)); - await this.#setWebAuthnUserVerified(this.#cdpClient, this.#authenticatorId, true); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#cdpClient, this.#authenticatorId, true); + await this.#setWebAuthnUserVerified(this.#authenticatorId, true); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); await operationTrigger(); await Promise.race([operationCompleted, wait.then(() => Promise.reject('Passkey input timeout'))]); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#cdpClient, this.#authenticatorId, false); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); } async startAndCancelPasskeyOperation(operationTrigger: () => Promise, postOperationCheck: () => Promise) { - if (!this.#cdpClient) { - throw new Error('CDP client not initialized'); - } - - await this.#setWebAuthnUserVerified(this.#cdpClient, this.#authenticatorId, false); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#cdpClient, this.#authenticatorId, true); + await this.#setWebAuthnUserVerified(this.#authenticatorId, false); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, true); await operationTrigger(); await postOperationCheck(); - await this.#setWebAuthnAutomaticPresenceSimulation(this.#cdpClient, this.#authenticatorId, false); + await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); + } + + clearCredentials() { + return this.#cdpClient.send('WebAuthn.clearCredentials', { + authenticatorId: this.#authenticatorId, + }); + } + + async addDummyCredential() { + try { + await this.#cdpClient.send("WebAuthn.addCredential", { + authenticatorId: this.#authenticatorId, + credential: { + credentialId: 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', + isResidentCredential: true, + rpId: 'connect-next.playground.corbado.io', + privateKey: 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', + userHandle: 'TDBlaFVpNnRNQg==', + signCount: 1, + }, + }); + } catch (e) { + console.error(e); + throw e; + } } - #setWebAuthnAutomaticPresenceSimulation(client: CDPSession, authenticatorId: string, automatic: boolean) { - return client.send('WebAuthn.setAutomaticPresenceSimulation', { + #setWebAuthnAutomaticPresenceSimulation(authenticatorId: string, automatic: boolean) { + return this.#cdpClient.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId: authenticatorId, enabled: automatic, }); } - #setWebAuthnUserVerified(client: CDPSession, authenticatorId: string, isUserVerified: boolean) { - return client.send('WebAuthn.setUserVerified', { + #setWebAuthnUserVerified(authenticatorId: string, isUserVerified: boolean) { + return this.#cdpClient.send('WebAuthn.setUserVerified', { authenticatorId, isUserVerified, }); From 2533459259f3b3702018161bc1973fac463a068d Mon Sep 17 00:00:00 2001 From: aehnh Date: Tue, 14 Jan 2025 19:37:15 +0100 Subject: [PATCH 04/14] prettier --- .../tests-e2e/src/connect/models/BaseModel.ts | 2 +- .../connect/utils/NetworkRequestBlocker.ts | 22 +++++++++---------- .../src/connect/utils/VirtualAuthenticator.ts | 5 +++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 4952b90ff..5c9f7ba38 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -2,7 +2,7 @@ import type { Page } from '@playwright/test'; import type { ErrorTexts } from '../utils/Constants'; import { ScreenNames } from '../utils/Constants'; -import { expectError,expectScreen } from '../utils/ExpectScreen'; +import { expectError, expectScreen } from '../utils/ExpectScreen'; import type { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; import { AppendModel } from './AppendModel'; diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index c47633429..a4babdd03 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -14,32 +14,32 @@ export class NetworkRequestBlocker { } blockCorbadoCDN() { - return this.#cdpClient.send("Network.setBlockedURLs", { - urls: ["cdn.vr.corbado-staging.io/*"], + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['cdn.vr.corbado-staging.io/*'], }); } blockCorbadoFAPI() { - return this.#cdpClient.send("Network.setBlockedURLs", { - urls: ["*.frontendapi.cloud.corbado-staging.io/v2/connect"], + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect'], }); } blockCorbadoFAPIFinishEndpoint() { - return this.#cdpClient.send("Network.setBlockedURLs", { - urls: ["*.frontendapi.cloud.corbado-staging.io/v2/connect/*/finish"], + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect/*/finish'], }); } blockCorbadoBAPI() { - return this.#cdpClient.send("Network.setBlockedURLs", { - urls: ["api.vr.corbado-staging.io"], + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['api.vr.corbado-staging.io'], }); } blockCorbadoConnectTokenEndpoint() { - return this.#cdpClient.send("Network.setBlockedURLs", { - urls: ["vrdemo.vr.corbado-staging.io/corbadoTokens"], - }) + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['vrdemo.vr.corbado-staging.io/corbadoTokens'], + }); } } diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index 4fe3c3b21..60aa0f0c1 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -71,13 +71,14 @@ export class VirtualAuthenticator { async addDummyCredential() { try { - await this.#cdpClient.send("WebAuthn.addCredential", { + await this.#cdpClient.send('WebAuthn.addCredential', { authenticatorId: this.#authenticatorId, credential: { credentialId: 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', isResidentCredential: true, rpId: 'connect-next.playground.corbado.io', - privateKey: 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', + privateKey: + 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', userHandle: 'TDBlaFVpNnRNQg==', signCount: 1, }, From fe1b9de9534c976aea3d7844262cc215e4e2a9ce Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 10:00:48 +0100 Subject: [PATCH 05/14] add some append error state tests --- .../src/connect/models/AppendModel.ts | 16 +++++++--- .../tests-e2e/src/connect/models/BaseModel.ts | 2 +- .../src/connect/scenarios/append.spec.ts | 29 +++++++++++++++++-- .../connect/utils/NetworkRequestBlocker.ts | 2 +- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/AppendModel.ts b/packages/tests-e2e/src/connect/models/AppendModel.ts index f17b0c7f9..20efe4530 100644 --- a/packages/tests-e2e/src/connect/models/AppendModel.ts +++ b/packages/tests-e2e/src/connect/models/AppendModel.ts @@ -1,5 +1,7 @@ import type { Page } from '@playwright/test'; +import { ErrorTexts } from '../utils/Constants'; +import { expectError } from '../utils/ExpectScreen'; import type { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; export class AppendModel { @@ -11,16 +13,22 @@ export class AppendModel { this.authenticator = authenticator; } - async appendPasskey(): Promise { + appendPasskey(complete: boolean) { const operationTrigger = () => this.page.getByRole('button', { name: 'Continue' }).click(); - await this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + if (complete) { + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + } else { + return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => + expectError(this.page, ErrorTexts.CancelledPasskey), + ); + } } - confirmAppended(): Promise { + confirmAppended() { return this.page.getByRole('button', { name: 'Continue' }).click(); } - skipAppend(): Promise { + skipAppend() { return this.page.locator('.cb-append-skip').click(); } } diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index 5c9f7ba38..55c8227d4 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -63,7 +63,7 @@ export class BaseModel { if (invited) { await this.expectScreen(ScreenNames.PasskeyAppend); if (append) { - await this.append.appendPasskey(); + await this.append.appendPasskey(true); await this.expectScreen(ScreenNames.PasskeyAppended); await this.append.confirmAppended(); } else { diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index 9afd56cc3..c5bd8427b 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -1,6 +1,6 @@ import { test } from '../fixtures/BaseTest'; -import { ScreenNames } from '../utils/Constants'; -import { loadPasskeyAppend, setupUser, setupVirtualAuthenticator } from './hooks'; +import { password, ScreenNames } from '../utils/Constants'; +import { loadPasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('append component', () => { setupVirtualAuthenticator(test); @@ -8,10 +8,33 @@ test.describe('append component', () => { loadPasskeyAppend(test); test('successful passkey append on login', async ({ model }) => { - await model.append.appendPasskey(); + await model.append.appendPasskey(true); await model.expectScreen(ScreenNames.PasskeyAppended); await model.append.confirmAppended(); await model.expectScreen(ScreenNames.Home); }); + + test('failed passkey append on login', async ({ model }) => { + await model.append.appendPasskey(false); + }); +}); + +test.describe('skip append component', () => { + setupVirtualAuthenticator(test); + setupNetworkBlocker(test); + setupUser(test, true, false); + + test('Corbado BAPI unavailable', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, false); + await model.expectScreen(ScreenNames.InitLoginFallback); + + await model.blocker.blockCorbadoBAPI(); + + await model.login.submitFallbackCredentials(model.email, password, true); + await model.expectScreen(ScreenNames.Home); + }); }); diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index a4babdd03..a27f3b4e2 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -33,7 +33,7 @@ export class NetworkRequestBlocker { blockCorbadoBAPI() { return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['api.vr.corbado-staging.io'], + urls: ['api.cloud.corbado-staging.io'], }); } From 4cab164b5418bddad3246b7e29cacdefbbfa00b7 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 10:17:59 +0100 Subject: [PATCH 06/14] remaining append error state tests --- .../src/connect/scenarios/append.spec.ts | 21 +++++++++++++++++++ .../connect/utils/NetworkRequestBlocker.ts | 12 ----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index c5bd8427b..f9217d2c1 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -4,6 +4,7 @@ import { loadPasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenti test.describe('append component', () => { setupVirtualAuthenticator(test); + setupNetworkBlocker(test); setupUser(test, true, false); loadPasskeyAppend(test); @@ -18,6 +19,13 @@ test.describe('append component', () => { test('failed passkey append on login', async ({ model }) => { await model.append.appendPasskey(false); }); + + test('Corbado FAPI unavailable after authentication', async ({ model }) => { + await model.blocker.blockCorbadoFAPIFinishEndpoint(); + + await model.append.appendPasskey(true); + await model.expectScreen(ScreenNames.Home); + }); }); test.describe('skip append component', () => { @@ -37,4 +45,17 @@ test.describe('skip append component', () => { await model.login.submitFallbackCredentials(model.email, password, true); await model.expectScreen(ScreenNames.Home); }); + + test('Corbado FAPI unavailable', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, false); + await model.expectScreen(ScreenNames.InitLoginFallback); + + await model.blocker.blockCorbadoFAPI(); + + await model.login.submitFallbackCredentials(model.email, password, true); + await model.expectScreen(ScreenNames.Home); + }); }); diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index a27f3b4e2..6696e9ef6 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -13,12 +13,6 @@ export class NetworkRequestBlocker { return this.#cdpClient.send('Network.enable'); } - blockCorbadoCDN() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['cdn.vr.corbado-staging.io/*'], - }); - } - blockCorbadoFAPI() { return this.#cdpClient.send('Network.setBlockedURLs', { urls: ['*.frontendapi.cloud.corbado-staging.io/v2/connect'], @@ -36,10 +30,4 @@ export class NetworkRequestBlocker { urls: ['api.cloud.corbado-staging.io'], }); } - - blockCorbadoConnectTokenEndpoint() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['vrdemo.vr.corbado-staging.io/corbadoTokens'], - }); - } } From 7c97db0daf120bcd44d5e398c798264d2a05182c Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 11:11:34 +0100 Subject: [PATCH 07/14] add conditional UI test --- .../src/connect/models/LoginModel.ts | 4 ++++ .../src/connect/scenarios/login.spec.ts | 23 ++++++++++++++----- .../connect/scenarios/passkey-list.spec.ts | 17 +++++++++++++- .../tests-e2e/src/connect/utils/Constants.ts | 1 + .../connect/utils/NetworkRequestBlocker.ts | 6 +++++ 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/LoginModel.ts b/packages/tests-e2e/src/connect/models/LoginModel.ts index 8a6c5a91e..bdc9114df 100644 --- a/packages/tests-e2e/src/connect/models/LoginModel.ts +++ b/packages/tests-e2e/src/connect/models/LoginModel.ts @@ -62,4 +62,8 @@ export class LoginModel { await this.page.getByPlaceholder('Password').fill(password); await this.page.getByRole('button', { name: 'Login' }).click(); } + + submitConditionalUI(operationTrigger: () => Promise) { + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + } } diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 74fae0e5a..33890e301 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -38,22 +38,33 @@ test.describe('login component (with invitation token, with passkeys)', () => { setupNetworkBlocker(test); setupUser(test, true, true); - test('successful login with passkey (one-tap)', async ({ model }) => { + test('successful login with passkey', async ({ model }) => { await model.home.logout(); await model.expectScreen(ScreenNames.InitLoginOneTap); - await model.login.submitPasskeyButton(true); + await model.login.removePasskeyButton(); + await model.expectScreen(ScreenNames.InitLogin); + + await model.login.submitEmail(model.email, true); await model.expectScreen(ScreenNames.Home); }); - test('successful login with passkey', async ({ model }) => { + test('successful login with passkey (conditional UI)', async ({ model }) => { await model.home.logout(); await model.expectScreen(ScreenNames.InitLoginOneTap); - await model.login.removePasskeyButton(); - await model.expectScreen(ScreenNames.InitLogin); + await model.login.submitConditionalUI(async () => { + await model.login.removePasskeyButton(); + await model.expectScreen(ScreenNames.InitLogin); + }); + await model.expectScreen(ScreenNames.Home); + }); - await model.login.submitEmail(model.email, true); + test('successful login with passkey (one-tap)', async ({ model }) => { + await model.home.logout(); + await model.expectScreen(ScreenNames.InitLoginOneTap); + + await model.login.submitPasskeyButton(true); await model.expectScreen(ScreenNames.Home); }); diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 1d102baae..61b81d8c6 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -1,5 +1,6 @@ import { test } from '../fixtures/BaseTest'; -import { loadPasskeyList, setupUser, setupVirtualAuthenticator } from './hooks'; +import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { ErrorTexts, ScreenNames } from '../utils/Constants'; test.describe('passkey-list component', () => { setupVirtualAuthenticator(test); @@ -14,3 +15,17 @@ test.describe('passkey-list component', () => { await model.passkeyList.expectPasskeys(1); }); }); + +test.describe('skip passkey-list component', () => { + setupVirtualAuthenticator(test); + setupNetworkBlocker(test); + setupUser(test, true, true); + + test('Corbado FAPI unavailable', async ({ model }) => { + await model.blocker.blockCorbadoFAPI(); + + await model.home.gotoPasskeyList(); + await model.expectScreen(ScreenNames.PasskeyList); + await model.expectError(ErrorTexts.PasskeyFetchFail); + }); +}); diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index 29159a1f3..53726e602 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -15,6 +15,7 @@ export enum ErrorTexts { EmptyEmail = 'Enter your email address.', UnknownEmail = 'There is no account registered to that email address.', CancelledPasskey = 'You have cancelled setting up your passkey. Please try again.', + PasskeyFetchFail = 'Unable to access passkeys. Check your connection and try again.', } export const phone = '+4915121609839'; diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index 6696e9ef6..110e58807 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -30,4 +30,10 @@ export class NetworkRequestBlocker { urls: ['api.cloud.corbado-staging.io'], }); } + + blockCorbadoConnectTokenEndpoint() { + return this.#cdpClient.send('Network.setBlockedURLs', { + urls: ['api.cloud.corbado-staging.io/v2/connectTokens'], + }); + } } From 74f39cb9145386ccf1b72738855f13471c2f7484 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 16:06:39 +0100 Subject: [PATCH 08/14] passkey list error state tests --- .../src/connect/models/PasskeyListModel.ts | 27 +++--- .../connect/scenarios/passkey-list.spec.ts | 83 +++++++++++++++++-- .../tests-e2e/src/connect/utils/Constants.ts | 2 + .../connect/utils/NetworkRequestBlocker.ts | 2 +- .../src/connect/utils/VirtualAuthenticator.ts | 21 +++-- 5 files changed, 112 insertions(+), 23 deletions(-) diff --git a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts index eab308022..acbc2b1c5 100644 --- a/packages/tests-e2e/src/connect/models/PasskeyListModel.ts +++ b/packages/tests-e2e/src/connect/models/PasskeyListModel.ts @@ -16,24 +16,29 @@ export class PasskeyListModel { return expect(this.page.locator('.cb-passkey-list-item')).toHaveCount(n); } - async deletePasskey(index: number): Promise { + async deletePasskey(index: number) { await this.page.locator('.cb-passkey-list-item-delete-icon').nth(index).click(); await this.page.getByRole('button', { name: 'Delete' }).click(); } - appendPasskey(complete: boolean): Promise { - const operationTrigger: () => Promise = (): Promise => - this.page.getByRole('button', { name: 'Add a passkey' }).click(); + createPasskey(complete: boolean, postOperationCheck: (() => Promise) | null = null) { + const operationTrigger = (): Promise => this.page.getByRole('button', { name: 'Add a passkey' }).click(); if (complete) { - return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + if (postOperationCheck === null) { + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger); + } else { + return this.authenticator.startAndCompletePasskeyOperation(operationTrigger, postOperationCheck); + } } else { - return this.authenticator.startAndCancelPasskeyOperation( - operationTrigger, - (): Promise => - expect(this.page.locator('.cb-notification-text')).toHaveText( - 'You have cancelled setting up your passkey. Please try again.', - ), + return this.authenticator.startAndCancelPasskeyOperation(operationTrigger, () => + expect(this.page.locator('.cb-notification-text')).toHaveText( + 'You have cancelled setting up your passkey. Please try again.', + ), ); } } + + confirmModal() { + return this.page.getByRole('button', { name: 'Okay' }).click(); + } } diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 61b81d8c6..13df490d1 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -1,17 +1,82 @@ -import { test } from '../fixtures/BaseTest'; -import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { expect, test } from '../fixtures/BaseTest'; import { ErrorTexts, ScreenNames } from '../utils/Constants'; +import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('passkey-list component', () => { setupVirtualAuthenticator(test); - setupUser(test, true, true); + setupNetworkBlocker(test); + setupUser(test, true, false); loadPasskeyList(test); - test('list, delete, append passkey', async ({ model }) => { + test('list, delete, create passkey', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + await model.passkeyList.createPasskey(true); await model.passkeyList.expectPasskeys(1); await model.passkeyList.deletePasskey(0); await model.passkeyList.expectPasskeys(0); - await model.passkeyList.appendPasskey(true); + }); + + test('abort passkey creation', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + await model.passkeyList.createPasskey(false); + await model.passkeyList.expectPasskeys(0); + }); + + test.skip('Connect Token endpoint unavailable during passkey creation', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + + await model.blocker.blockCorbadoConnectTokenEndpoint(); + + await model.page.getByRole('button', { name: 'Add a passkey' }).click(); + await model.expectError(ErrorTexts.PasskeyCreateFail); + await model.passkeyList.expectPasskeys(0); + }); + + test('Corbado FAPI unavailable during passkey creation', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + + await model.blocker.blockCorbadoFAPI(); + + await model.page.getByRole('button', { name: 'Add a passkey' }).click(); + await model.expectError(ErrorTexts.PasskeyCreateFail); + await model.passkeyList.expectPasskeys(0); + }); + + test('passkey already registered', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + await model.passkeyList.createPasskey(true); + await model.passkeyList.expectPasskeys(1); + + await model.passkeyList.createPasskey(true, () => + expect(model.page.getByRole('heading', { name: 'No passkey created' })).toBeVisible(), + ); + await expect(model.page.getByText('No passkey created')).toBeVisible(); + + await model.passkeyList.confirmModal(); + await model.passkeyList.expectPasskeys(1); + }); + + test.skip('Connect Token endpoint unavailable during passkey deletion', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + await model.passkeyList.createPasskey(true); + await model.passkeyList.expectPasskeys(1); + + await model.blocker.blockCorbadoConnectTokenEndpoint(); + + await model.passkeyList.deletePasskey(0); + await model.expectError(ErrorTexts.PasskeyDeleteFail); + await model.passkeyList.expectPasskeys(1); + }); + + test('Corbado FAPI unavailable during passkey deletion', async ({ model }) => { + await model.passkeyList.expectPasskeys(0); + await model.passkeyList.createPasskey(true); + await model.passkeyList.expectPasskeys(1); + + await model.blocker.blockCorbadoFAPI(); + + await model.passkeyList.deletePasskey(0); + await model.expectError(ErrorTexts.PasskeyDeleteFail); await model.passkeyList.expectPasskeys(1); }); }); @@ -21,6 +86,14 @@ test.describe('skip passkey-list component', () => { setupNetworkBlocker(test); setupUser(test, true, true); + test.skip('Connect Token endpoint unavailable', async ({ model }) => { + await model.blocker.blockCorbadoConnectTokenEndpoint(); + + await model.home.gotoPasskeyList(); + await model.expectScreen(ScreenNames.PasskeyList); + await model.expectError(ErrorTexts.PasskeyFetchFail); + }); + test('Corbado FAPI unavailable', async ({ model }) => { await model.blocker.blockCorbadoFAPI(); diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index 53726e602..59b202364 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -16,6 +16,8 @@ export enum ErrorTexts { UnknownEmail = 'There is no account registered to that email address.', CancelledPasskey = 'You have cancelled setting up your passkey. Please try again.', PasskeyFetchFail = 'Unable to access passkeys. Check your connection and try again.', + PasskeyCreateFail = 'Passkey creation failed. Please try again later.', + PasskeyDeleteFail = 'Passkey deletion failed. Please try again later.', } export const phone = '+4915121609839'; diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index 110e58807..49f591f5c 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -33,7 +33,7 @@ export class NetworkRequestBlocker { blockCorbadoConnectTokenEndpoint() { return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['api.cloud.corbado-staging.io/v2/connectTokens'], + urls: ['backendapi.cloud.corbado-staging.io/v2/connectTokens'], }); } } diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index 60aa0f0c1..6a3a1e692 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -37,11 +37,20 @@ export class VirtualAuthenticator { }); } - async startAndCompletePasskeyOperation(operationTrigger: () => Promise) { - const operationCompleted = new Promise(resolve => { - this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); - this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); - }); + async startAndCompletePasskeyOperation( + operationTrigger: () => Promise, + postOperationCheck: (() => Promise) | null = null, + ) { + let postOperationPromise: Promise; + if (postOperationCheck === null) { + postOperationPromise = new Promise(resolve => { + console.log('listening'); + this.#cdpClient?.on('WebAuthn.credentialAdded', () => { console.log('added'); resolve(); }); + this.#cdpClient?.on('WebAuthn.credentialAsserted', () => { console.log('asserted'); resolve(); }); + }); + } else { + postOperationPromise = postOperationCheck(); + } const wait = new Promise(resolve => setTimeout(resolve, operationTimeout)); await this.#setWebAuthnUserVerified(this.#authenticatorId, true); @@ -49,7 +58,7 @@ export class VirtualAuthenticator { await operationTrigger(); - await Promise.race([operationCompleted, wait.then(() => Promise.reject('Passkey input timeout'))]); + await Promise.race([postOperationPromise, wait.then(() => Promise.reject('Passkey input timeout'))]); await this.#setWebAuthnAutomaticPresenceSimulation(this.#authenticatorId, false); } From 43ecdb3a87a8cbc85588f6f7cd8c01ce6b10e259 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 16:08:54 +0100 Subject: [PATCH 09/14] prettier --- .../tests-e2e/src/connect/utils/VirtualAuthenticator.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index 6a3a1e692..bcca59f9d 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -44,10 +44,9 @@ export class VirtualAuthenticator { let postOperationPromise: Promise; if (postOperationCheck === null) { postOperationPromise = new Promise(resolve => { - console.log('listening'); - this.#cdpClient?.on('WebAuthn.credentialAdded', () => { console.log('added'); resolve(); }); - this.#cdpClient?.on('WebAuthn.credentialAsserted', () => { console.log('asserted'); resolve(); }); - }); + this.#cdpClient?.on('WebAuthn.credentialAdded', () => resolve()); + this.#cdpClient?.on('WebAuthn.credentialAsserted', () => resolve()); + }); } else { postOperationPromise = postOperationCheck(); } From 5a02a986c4f9a2fd0a36c12d0a22f343fe592d3e Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 16:37:33 +0100 Subject: [PATCH 10/14] fix incorrectly implemented test --- packages/tests-e2e/src/connect/scenarios/login.spec.ts | 9 ++++----- packages/tests-e2e/src/connect/utils/Constants.ts | 1 + packages/tests-e2e/src/connect/utils/ExpectScreen.ts | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 33890e301..efce02389 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -55,7 +55,6 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.login.submitConditionalUI(async () => { await model.login.removePasskeyButton(); - await model.expectScreen(ScreenNames.InitLogin); }); await model.expectScreen(ScreenNames.Home); }); @@ -123,11 +122,11 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.loadHome(); await model.expectScreen(ScreenNames.Home); - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, false); + await model.login.submitConditionalUI(async () => { + await model.home.logout(); + }); await model.expectScreen(ScreenNames.InitLoginFallback); + await model.expectError(ErrorTexts.DeletedPasskeyUsed); }); }); diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index 59b202364..108ad274d 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -18,6 +18,7 @@ export enum ErrorTexts { PasskeyFetchFail = 'Unable to access passkeys. Check your connection and try again.', PasskeyCreateFail = 'Passkey creation failed. Please try again later.', PasskeyDeleteFail = 'Passkey deletion failed. Please try again later.', + DeletedPasskeyUsed = 'You previously deleted this passkey. Use your password to log in instead.', } export const phone = '+4915121609839'; diff --git a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts index 610ae9e56..6cd7c0d58 100644 --- a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts +++ b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts @@ -1,7 +1,7 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { ErrorTexts } from './Constants'; +import { ErrorTexts } from './Constants'; import { ScreenNames } from './Constants'; export const expectScreen = async (page: Page, screenName: ScreenNames): Promise => { @@ -60,5 +60,9 @@ export const expectScreen = async (page: Page, screenName: ScreenNames): Promise }; export const expectError = (page: Page, message: ErrorTexts): Promise => { + // This error message isn't a part of cb-container, so it doesn't come as cb-notification-text. + if (message === ErrorTexts.DeletedPasskeyUsed) { + return expect(page.getByText(message)).toBeVisible(); + } return expect(page.locator('.cb-notification-text')).toHaveText(message); }; From 189c7d3f88583bccb07142771ae97f7d4edf5895 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 17:02:49 +0100 Subject: [PATCH 11/14] fix passkey signature validation test --- packages/tests-e2e/src/connect/scenarios/login.spec.ts | 9 +++++---- packages/tests-e2e/src/connect/utils/Constants.ts | 1 + packages/tests-e2e/src/connect/utils/ExpectScreen.ts | 2 +- .../tests-e2e/src/connect/utils/VirtualAuthenticator.ts | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index efce02389..54fb22bf9 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -98,17 +98,18 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.expectScreen(ScreenNames.InitLoginFallback); }); - test.skip('passkey signature validation fails', async ({ model }) => { + test('passkey signature validation fails', async ({ model }) => { await model.home.logout(); await model.expectScreen(ScreenNames.InitLoginOneTap); await model.authenticator.clearCredentials(); await model.authenticator.addDummyCredential(); - await model.login.removePasskeyButton(); - await model.expectScreen(ScreenNames.InitLogin); - await model.login.submitEmail(model.email, true); + await model.login.submitConditionalUI(async () => { + await model.login.removePasskeyButton(); + }); await model.expectScreen(ScreenNames.InitLoginFallback); + await model.expectError(ErrorTexts.PasskeySignatureValidationFail); }); test('attempt login with server-side deleted passkey', async ({ model }) => { diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index 108ad274d..d3ce78fc1 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -19,6 +19,7 @@ export enum ErrorTexts { PasskeyCreateFail = 'Passkey creation failed. Please try again later.', PasskeyDeleteFail = 'Passkey deletion failed. Please try again later.', DeletedPasskeyUsed = 'You previously deleted this passkey. Use your password to log in instead.', + PasskeySignatureValidationFail = 'We couldn\'t log you in with your passkey due to a system error. Use your password to log in instead.', } export const phone = '+4915121609839'; diff --git a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts index 6cd7c0d58..76389f231 100644 --- a/packages/tests-e2e/src/connect/utils/ExpectScreen.ts +++ b/packages/tests-e2e/src/connect/utils/ExpectScreen.ts @@ -61,7 +61,7 @@ export const expectScreen = async (page: Page, screenName: ScreenNames): Promise export const expectError = (page: Page, message: ErrorTexts): Promise => { // This error message isn't a part of cb-container, so it doesn't come as cb-notification-text. - if (message === ErrorTexts.DeletedPasskeyUsed) { + if (message === ErrorTexts.DeletedPasskeyUsed || message === ErrorTexts.PasskeySignatureValidationFail) { return expect(page.getByText(message)).toBeVisible(); } return expect(page.locator('.cb-notification-text')).toHaveText(message); diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index bcca59f9d..dea81b8a4 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -82,12 +82,12 @@ export class VirtualAuthenticator { await this.#cdpClient.send('WebAuthn.addCredential', { authenticatorId: this.#authenticatorId, credential: { - credentialId: 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', + credentialId: '', // 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', isResidentCredential: true, rpId: 'connect-next.playground.corbado.io', privateKey: 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', - userHandle: 'TDBlaFVpNnRNQg==', + userHandle: '', // 'TDBlaFVpNnRNQg==', signCount: 1, }, }); From 1910c3f04fc51f83aaa970f02290b0887b7485ca Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 17:03:10 +0100 Subject: [PATCH 12/14] prettier --- packages/tests-e2e/src/connect/utils/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tests-e2e/src/connect/utils/Constants.ts b/packages/tests-e2e/src/connect/utils/Constants.ts index d3ce78fc1..2dde0c2e8 100644 --- a/packages/tests-e2e/src/connect/utils/Constants.ts +++ b/packages/tests-e2e/src/connect/utils/Constants.ts @@ -19,7 +19,7 @@ export enum ErrorTexts { PasskeyCreateFail = 'Passkey creation failed. Please try again later.', PasskeyDeleteFail = 'Passkey deletion failed. Please try again later.', DeletedPasskeyUsed = 'You previously deleted this passkey. Use your password to log in instead.', - PasskeySignatureValidationFail = 'We couldn\'t log you in with your passkey due to a system error. Use your password to log in instead.', + PasskeySignatureValidationFail = "We couldn't log you in with your passkey due to a system error. Use your password to log in instead.", } export const phone = '+4915121609839'; From 3c47dbbc4306443072a3e89d0ad715462fad8b4f Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 18:03:13 +0100 Subject: [PATCH 13/14] unskip tests for blocking corbado token endpoint --- .../tests-e2e/src/connect/scenarios/passkey-list.spec.ts | 6 +++--- .../tests-e2e/src/connect/utils/NetworkRequestBlocker.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 13df490d1..2a0934014 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -22,7 +22,7 @@ test.describe('passkey-list component', () => { await model.passkeyList.expectPasskeys(0); }); - test.skip('Connect Token endpoint unavailable during passkey creation', async ({ model }) => { + test('Connect Token endpoint unavailable during passkey creation', async ({ model }) => { await model.passkeyList.expectPasskeys(0); await model.blocker.blockCorbadoConnectTokenEndpoint(); @@ -56,7 +56,7 @@ test.describe('passkey-list component', () => { await model.passkeyList.expectPasskeys(1); }); - test.skip('Connect Token endpoint unavailable during passkey deletion', async ({ model }) => { + test('Connect Token endpoint unavailable during passkey deletion', async ({ model }) => { await model.passkeyList.expectPasskeys(0); await model.passkeyList.createPasskey(true); await model.passkeyList.expectPasskeys(1); @@ -86,7 +86,7 @@ test.describe('skip passkey-list component', () => { setupNetworkBlocker(test); setupUser(test, true, true); - test.skip('Connect Token endpoint unavailable', async ({ model }) => { + test('Connect Token endpoint unavailable', async ({ model }) => { await model.blocker.blockCorbadoConnectTokenEndpoint(); await model.home.gotoPasskeyList(); diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index 49f591f5c..47751563a 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -32,8 +32,9 @@ export class NetworkRequestBlocker { } blockCorbadoConnectTokenEndpoint() { + // This is sufficient, as the connectTokens endpoint is called from /passkey-list handler return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['backendapi.cloud.corbado-staging.io/v2/connectTokens'], + urls: ['*.playground.corbado.io/passkey-list'], }); } } From cfd3959365a1a5be1901a8c2614c0dae84719844 Mon Sep 17 00:00:00 2001 From: aehnh Date: Wed, 15 Jan 2025 18:09:41 +0100 Subject: [PATCH 14/14] remove bapi blocking test --- .../tests-e2e/src/connect/scenarios/append.spec.ts | 13 ------------- .../src/connect/utils/NetworkRequestBlocker.ts | 6 ------ 2 files changed, 19 deletions(-) diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index f9217d2c1..3c1f032e8 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -33,19 +33,6 @@ test.describe('skip append component', () => { setupNetworkBlocker(test); setupUser(test, true, false); - test('Corbado BAPI unavailable', async ({ model }) => { - await model.home.logout(); - await model.expectScreen(ScreenNames.InitLogin); - - await model.login.submitEmail(model.email, false); - await model.expectScreen(ScreenNames.InitLoginFallback); - - await model.blocker.blockCorbadoBAPI(); - - await model.login.submitFallbackCredentials(model.email, password, true); - await model.expectScreen(ScreenNames.Home); - }); - test('Corbado FAPI unavailable', async ({ model }) => { await model.home.logout(); await model.expectScreen(ScreenNames.InitLogin); diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index 47751563a..c780a351f 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -25,12 +25,6 @@ export class NetworkRequestBlocker { }); } - blockCorbadoBAPI() { - return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['api.cloud.corbado-staging.io'], - }); - } - blockCorbadoConnectTokenEndpoint() { // This is sufficient, as the connectTokens endpoint is called from /passkey-list handler return this.#cdpClient.send('Network.setBlockedURLs', {