Skip to content

Commit fa0d28b

Browse files
feat: add passkey login to nextjs example template (#165)
* feat: add passkey login to nextjs example template * feat: display title and close on no progress * feat: input user passkey * test: adapt keys for sign-in with II * feat: passkey page * feat: for each test with passkey init * test: with passkey * fix: serial and close * chore: fmt * docs: review comment * test: override keyword * chore: build * feat: fine tuning * chore: reset as on main * chore: latest lib * feat: check if passkey supported * feat: is webauthn reexported * build: released juno libs * feat: update types * chore: fmt
1 parent b0a8166 commit fa0d28b

14 files changed

Lines changed: 672 additions & 111 deletions

File tree

e2e/example.spec.ts

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,98 @@
11
import {testWithII} from '@dfinity/internet-identity-playwright';
2-
import {initTestSuite} from './utils/init.utils';
2+
import {initTestSuiteWithInternetIdentity, initTestSuiteWithPasskey} from './utils/init.utils';
33

4-
const getExamplePage = initTestSuite();
4+
testWithII.describe.configure({mode: 'serial'});
55

6-
testWithII('should sign-in', async () => {
7-
const examplePage = getExamplePage();
6+
[
7+
{title: 'With II', initExamplePage: initTestSuiteWithInternetIdentity},
8+
{title: 'With Passkey', initExamplePage: initTestSuiteWithPasskey}
9+
].forEach(({title, initExamplePage}) => {
10+
testWithII.describe(title, () => {
11+
const getExamplePage = initExamplePage();
812

9-
await examplePage.assertSignedIn();
10-
});
13+
testWithII('should sign-in', async () => {
14+
const examplePage = getExamplePage();
1115

12-
testWithII('should add an entry', async () => {
13-
const examplePage = getExamplePage();
16+
await examplePage.assertSignedIn();
17+
});
1418

15-
await examplePage.addEntry('My notes.');
16-
});
19+
testWithII('should add an entry', async () => {
20+
const examplePage = getExamplePage();
1721

18-
testWithII('should add an entry with file', async () => {
19-
const examplePage = getExamplePage();
22+
await examplePage.addEntry('My notes.');
23+
});
2024

21-
await examplePage.addEntryWithFile({
22-
text: 'My file.',
23-
filePath: 'e2e/data/dog.jpg'
24-
});
25+
testWithII('should add an entry with file', async () => {
26+
const examplePage = getExamplePage();
2527

26-
await examplePage.assertUploadedImage();
27-
});
28+
await examplePage.addEntryWithFile({
29+
text: 'My file.',
30+
filePath: 'e2e/data/dog.jpg'
31+
});
2832

29-
const lastEntryText = 'My last note.';
33+
await examplePage.assertUploadedImage();
34+
});
3035

31-
testWithII('should add another entry', async () => {
32-
const examplePage = getExamplePage();
36+
const lastEntryText = 'My last note.';
3337

34-
await examplePage.addEntry(lastEntryText);
35-
});
38+
testWithII('should add another entry', async () => {
39+
const examplePage = getExamplePage();
3640

37-
testWithII('should delete entry', async () => {
38-
const examplePage = getExamplePage();
41+
await examplePage.addEntry(lastEntryText);
42+
});
3943

40-
await examplePage.deleteLastEntry();
41-
});
44+
testWithII('should delete entry', async () => {
45+
const examplePage = getExamplePage();
4246

43-
testWithII('should sign-out', async () => {
44-
const examplePage = getExamplePage();
47+
await examplePage.deleteLastEntry();
48+
});
4549

46-
await examplePage.signOut();
50+
testWithII('should sign-out', async () => {
51+
const examplePage = getExamplePage();
4752

48-
await examplePage.assertSignedOut();
49-
});
53+
await examplePage.signOut();
5054

51-
// TODO: testWithII does not seem to support setting dark or light mode so for now we just use screenshot of default mode
55+
await examplePage.assertSignedOut();
56+
});
5257

53-
testWithII('match login screenshot', async () => {
54-
const examplePage = getExamplePage();
58+
// TODO: testWithII does not seem to support setting dark or light mode so for now we just use screenshot of default mode
5559

56-
await examplePage.assertSignedOut();
60+
testWithII('match login screenshot', async () => {
61+
const examplePage = getExamplePage();
5762

58-
await examplePage.assertScreenshot({mode: 'current', name: 'login'});
59-
});
63+
await examplePage.assertSignedOut();
6064

61-
testWithII('match logged in screenshot', async () => {
62-
const examplePage = getExamplePage();
65+
await examplePage.assertScreenshot({mode: 'current', name: 'login'});
66+
});
6367

64-
await examplePage.signInWithIdentity();
68+
testWithII('match logged in screenshot', async () => {
69+
const examplePage = getExamplePage();
6570

66-
await examplePage.assertSignedIn();
71+
await examplePage.signIn();
6772

68-
await examplePage.assertScreenshot({mode: 'current', name: 'logged-in'});
69-
});
73+
await examplePage.assertSignedIn();
7074

71-
testWithII('match modal screenshot', async () => {
72-
const examplePage = getExamplePage();
75+
await examplePage.assertScreenshot({mode: 'current', name: 'logged-in'});
76+
});
7377

74-
await examplePage.openAddEntry();
78+
testWithII('match modal screenshot', async () => {
79+
const examplePage = getExamplePage();
7580

76-
await examplePage.assertScreenshot({mode: 'current', name: 'modal'});
81+
await examplePage.openAddEntry();
7782

78-
await examplePage.closeAddEntryModal();
79-
});
83+
await examplePage.assertScreenshot({mode: 'current', name: 'modal'});
84+
85+
await examplePage.closeAddEntryModal();
86+
});
8087

81-
testWithII('match logout screenshot', async () => {
82-
const examplePage = getExamplePage();
88+
testWithII('match logout screenshot', async () => {
89+
const examplePage = getExamplePage();
8390

84-
await examplePage.signOut();
91+
await examplePage.signOut();
8592

86-
await examplePage.assertSignedOut();
93+
await examplePage.assertSignedOut();
8794

88-
await examplePage.assertScreenshot({mode: 'current', name: 'logout'});
95+
await examplePage.assertScreenshot({mode: 'current', name: 'logout'});
96+
});
97+
});
8998
});

e2e/page-objects/app.page.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@ export abstract class AppPage {
1313

1414
protected readonly callToActions = {
1515
logout: 'Logout',
16-
continue_with_ii: 'Sign in',
1716
add_an_entry: 'Add an entry',
18-
submit: 'Submit'
17+
submit: 'Submit',
18+
internet_identity: {
19+
continue: 'Continue with Internet Identity'
20+
},
21+
passkey: {
22+
continue: 'Continue with Passkey',
23+
create: 'Create a new passkey',
24+
create_now: 'Create now',
25+
use: 'Use your Passkey'
26+
}
1927
};
2028

2129
protected readonly locators = {
22-
sign_in_with_ii: `button:has-text("${this.callToActions.continue_with_ii}")`,
2330
open_data: 'a[aria-label="Open data"]',
24-
delete_entry: 'button[aria-label="Delete entry"]'
31+
delete_entry: 'button[aria-label="Delete entry"]',
32+
internet_identity: {
33+
sign_in: `button:has-text("${this.callToActions.internet_identity.continue}")`
34+
}
2535
};
2636

2737
protected constructor({page, context, browser}: AppPageParams) {
@@ -30,6 +40,12 @@ export abstract class AppPage {
3040
this.browser = browser;
3141
}
3242

43+
waitReady?(): Promise<void>;
44+
45+
cleanUp?(): Promise<void>;
46+
47+
abstract signUp(): Promise<void>;
48+
3349
abstract signIn(): Promise<void>;
3450

3551
async signOut(): Promise<void> {
@@ -39,5 +55,6 @@ export abstract class AppPage {
3955

4056
async close(): Promise<void> {
4157
await this.page.close();
58+
await this.browser.close();
4259
}
4360
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {InternetIdentityPage} from '@dfinity/internet-identity-playwright';
2+
import {assertNonNullish} from '@dfinity/utils';
3+
import type {AppPageParams} from './app.page';
4+
import {ExamplePage} from './example.page';
5+
6+
export class ExampleInternetIdentityPage extends ExamplePage {
7+
#identity: number | undefined;
8+
9+
#iiPage: InternetIdentityPage;
10+
11+
private constructor(params: AppPageParams) {
12+
super(params);
13+
14+
this.#iiPage = new InternetIdentityPage({
15+
page: this.page,
16+
context: this.context,
17+
browser: this.browser
18+
});
19+
}
20+
21+
static async create(params: AppPageParams): Promise<ExampleInternetIdentityPage> {
22+
return new ExampleInternetIdentityPage(params);
23+
}
24+
25+
override async waitReady(): Promise<void> {
26+
const REPLICA_URL = 'http://127.0.0.1:5987';
27+
const INTERNET_IDENTITY_ID = 'rdmx6-jaaaa-aaaaa-aaadq-cai';
28+
29+
await this.#iiPage.waitReady({url: REPLICA_URL, canisterId: INTERNET_IDENTITY_ID});
30+
}
31+
32+
override async signUp(): Promise<void> {
33+
this.#identity = await this.#iiPage.signInWithNewIdentity({
34+
selector: this.locators.internet_identity.sign_in
35+
});
36+
}
37+
38+
override async signIn(): Promise<void> {
39+
assertNonNullish(this.#identity);
40+
41+
await this.#iiPage.signInWithIdentity({
42+
identity: this.#identity,
43+
selector: this.locators.internet_identity.sign_in
44+
});
45+
}
46+
}

e2e/page-objects/example.page.ts

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,19 @@
1-
import {InternetIdentityPage} from '@dfinity/internet-identity-playwright';
2-
import {assertNonNullish} from '@dfinity/utils';
31
import {expect} from '@playwright/test';
4-
import {AppPage, AppPageParams} from './app.page';
5-
6-
export class ExamplePage extends AppPage {
7-
#identity: number | undefined;
8-
9-
#iiPage: InternetIdentityPage;
10-
11-
constructor(params: AppPageParams) {
12-
super(params);
13-
14-
this.#iiPage = new InternetIdentityPage({
15-
page: this.page,
16-
context: this.context,
17-
browser: this.browser
18-
});
19-
}
20-
21-
override async signIn(): Promise<void> {
22-
this.#identity = await this.#iiPage.signInWithNewIdentity({
23-
selector: this.locators.sign_in_with_ii
24-
});
25-
}
26-
27-
async signInWithIdentity(): Promise<void> {
28-
assertNonNullish(this.#identity);
29-
30-
await this.#iiPage.signInWithIdentity({
31-
identity: this.#identity,
32-
selector: this.locators.sign_in_with_ii
33-
});
34-
}
2+
import {AppPage} from './app.page';
353

4+
export abstract class ExamplePage extends AppPage {
365
async assertSignedIn(): Promise<void> {
376
const button = this.page.locator('button', {hasText: this.callToActions.logout});
387
await expect(button).toBeVisible();
398
}
409

4110
async assertSignedOut(): Promise<void> {
42-
const button = this.page.locator('button', {hasText: this.callToActions.continue_with_ii});
11+
const button = this.page.locator('button', {
12+
hasText: this.callToActions.internet_identity.continue
13+
});
4314
await expect(button).toBeVisible();
4415
}
4516

46-
async waitReady(): Promise<void> {
47-
const REPLICA_URL = 'http://127.0.0.1:5987';
48-
const INTERNET_IDENTITY_ID = 'rdmx6-jaaaa-aaaaa-aaadq-cai';
49-
50-
await this.#iiPage.waitReady({url: REPLICA_URL, canisterId: INTERNET_IDENTITY_ID});
51-
}
52-
5317
async goto(): Promise<void> {
5418
await this.page.goto('/');
5519
}

0 commit comments

Comments
 (0)