Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/tests-e2e/src/connect/models/BaseModel.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
Expand Down
34 changes: 28 additions & 6 deletions packages/tests-e2e/src/connect/models/LoginModel.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,16 +14,36 @@ export class LoginModel {
this.authenticator = authenticator;
}

submitPasskeyButton(): Promise<void> {
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<void> {
removePasskeyButton() {
return this.page.locator('.cb-switch').click();
}

async submitEmail(email: string, withPasskey: boolean): Promise<void> {
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();
Expand All @@ -31,7 +53,7 @@ export class LoginModel {
}
}

async submitFallbackCredentials(email: string, password: string, emailAutofilled = false): Promise<void> {
async submitFallbackCredentials(email: string, password: string, emailAutofilled = false) {
if (emailAutofilled) {
await expect(this.page.getByPlaceholder('Email')).toHaveValue(email);
} else {
Expand Down
19 changes: 11 additions & 8 deletions packages/tests-e2e/src/connect/models/PasskeyListModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ export class PasskeyListModel {
return expect(this.page.locator('.cb-passkey-list-item')).toHaveCount(n);
}

async deletePasskey(index: number) {
async deletePasskey(index: number): Promise<void> {
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<void> {
const operationTrigger: () => Promise<void> = (): Promise<void> =>
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, () =>
expect(this.page.locator('.cb-notification-text')).toHaveText(
'You have cancelled setting up your passkey. Please try again.',
),
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.',
),
);
}
}
Expand Down
20 changes: 10 additions & 10 deletions packages/tests-e2e/src/connect/scenarios/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
35 changes: 32 additions & 3 deletions packages/tests-e2e/src/connect/scenarios/login.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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);
});

Expand All @@ -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);
});
});
8 changes: 8 additions & 0 deletions packages/tests-e2e/src/connect/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
20 changes: 18 additions & 2 deletions packages/tests-e2e/src/connect/utils/ExpectScreen.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 { 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<void> => {
switch (screenName) {
case ScreenNames.InitSignup:
await expect(page.locator('div.font-bold.text-xl')).toHaveText('Signup');
Expand Down Expand Up @@ -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<void> => {
return expect(page.locator('.cb-notification-text')).toHaveText(message);
};
Loading