Skip to content
11 changes: 8 additions & 3 deletions packages/tests-e2e/src/connect/fixtures/BaseTest.ts
Original file line number Diff line number Diff line change
@@ -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);
},
Expand Down
16 changes: 12 additions & 4 deletions packages/tests-e2e/src/connect/models/AppendModel.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -11,16 +13,22 @@ export class AppendModel {
this.authenticator = authenticator;
}

async appendPasskey(): Promise<void> {
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<void> {
confirmAppended() {
return this.page.getByRole('button', { name: 'Continue' }).click();
}

skipAppend(): Promise<void> {
skipAppend() {
return this.page.locator('.cb-append-skip').click();
}
}
31 changes: 18 additions & 13 deletions packages/tests-e2e/src/connect/models/BaseModel.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,31 +14,25 @@ import { SignupModel } from './SignupModel';
export class BaseModel {
page: Page;
authenticator: VirtualAuthenticator;
blocker: NetworkRequestBlocker;
signup: SignupModel;
login: LoginModel;
append: AppendModel;
home: HomeModel;
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);
this.home = new HomeModel(page);
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');
}
Expand All @@ -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);
}
Expand All @@ -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 {
Expand All @@ -90,4 +90,9 @@ export class BaseModel {
//
// expect(response.ok).toBeTruthy();
// }

async clearLocalStorageAndCookies() {
await this.page.evaluate(() => localStorage.clear());
await this.page.context().clearCookies();
}
}
8 changes: 6 additions & 2 deletions packages/tests-e2e/src/connect/models/LoginModel.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<void>) {
return this.authenticator.startAndCompletePasskeyOperation(operationTrigger);
}
}
27 changes: 16 additions & 11 deletions packages/tests-e2e/src/connect/models/PasskeyListModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,29 @@ export class PasskeyListModel {
return expect(this.page.locator('.cb-passkey-list-item')).toHaveCount(n);
}

async deletePasskey(index: number): Promise<void> {
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<void> {
const operationTrigger: () => Promise<void> = (): Promise<void> =>
this.page.getByRole('button', { name: 'Add a passkey' }).click();
createPasskey(complete: boolean, postOperationCheck: (() => Promise<void>) | null = null) {
const operationTrigger = (): Promise<void> => 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<void> =>
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();
}
}
37 changes: 34 additions & 3 deletions packages/tests-e2e/src/connect/scenarios/append.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
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);
setupNetworkBlocker(test);
setupUser(test, true, false);
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('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', () => {
setupVirtualAuthenticator(test);
setupNetworkBlocker(test);
setupUser(test, true, false);

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);
});
});
15 changes: 13 additions & 2 deletions packages/tests-e2e/src/connect/scenarios/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
}

Expand Down
Loading
Loading