Skip to content

Commit c016718

Browse files
committed
test: add Playwright tests for passwordless UI and auth methods
- Registration page: toggle visibility, password field hiding, required attribute toggling, correct endpoint targeting, active state switching - Profile page: auth methods card display, password badge, remove password button visibility, set password link visibility, passkey management section, empty passkeys message - Change password page: current password field for password users - Navigation: change password link text verification
1 parent 679170c commit c016718

2 files changed

Lines changed: 392 additions & 0 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { test, expect, generateTestUser } from '../../src/fixtures';
2+
3+
test.describe('Passwordless Registration', () => {
4+
test.describe('Registration Mode Toggle', () => {
5+
test('should show passwordless toggle on registration page', async ({
6+
registerPage,
7+
}) => {
8+
await registerPage.goto();
9+
await registerPage.page.waitForLoadState('networkidle');
10+
11+
// The toggle should be visible (WebAuthn is supported in Chromium)
12+
const toggle = registerPage.page.locator('#registrationModeToggle');
13+
await expect(toggle).toBeVisible();
14+
15+
// Both mode buttons should be present
16+
const passwordBtn = registerPage.page.locator('#modePassword');
17+
const passwordlessBtn = registerPage.page.locator('#modePasswordless');
18+
await expect(passwordBtn).toBeVisible();
19+
await expect(passwordlessBtn).toBeVisible();
20+
21+
// Password mode should be active by default
22+
await expect(passwordBtn).toHaveClass(/active/);
23+
await expect(passwordlessBtn).not.toHaveClass(/active/);
24+
});
25+
26+
test('should hide password fields when passwordless mode is selected', async ({
27+
registerPage,
28+
}) => {
29+
await registerPage.goto();
30+
await registerPage.page.waitForLoadState('networkidle');
31+
32+
// Password fields should be visible initially
33+
const passwordFields = registerPage.page.locator('#passwordFields');
34+
await expect(passwordFields).toBeVisible();
35+
await expect(registerPage.passwordInput).toBeVisible();
36+
await expect(registerPage.confirmPasswordInput).toBeVisible();
37+
38+
// Click passwordless mode button
39+
await registerPage.page.locator('#modePasswordless').click();
40+
41+
// Password fields should be hidden
42+
await expect(passwordFields).toBeHidden();
43+
44+
// Passwordless info alert should be visible
45+
const passwordlessInfo = registerPage.page.locator('#passwordlessInfo');
46+
await expect(passwordlessInfo).toBeVisible();
47+
});
48+
49+
test('should show password fields when switching back to password mode', async ({
50+
registerPage,
51+
}) => {
52+
await registerPage.goto();
53+
await registerPage.page.waitForLoadState('networkidle');
54+
55+
// Switch to passwordless
56+
await registerPage.page.locator('#modePasswordless').click();
57+
const passwordFields = registerPage.page.locator('#passwordFields');
58+
await expect(passwordFields).toBeHidden();
59+
60+
// Switch back to password mode
61+
await registerPage.page.locator('#modePassword').click();
62+
63+
// Password fields should be visible again
64+
await expect(passwordFields).toBeVisible();
65+
await expect(registerPage.passwordInput).toBeVisible();
66+
await expect(registerPage.confirmPasswordInput).toBeVisible();
67+
68+
// Passwordless info alert should be hidden
69+
const passwordlessInfo = registerPage.page.locator('#passwordlessInfo');
70+
await expect(passwordlessInfo).toBeHidden();
71+
});
72+
73+
test('should toggle active state on mode buttons', async ({
74+
registerPage,
75+
}) => {
76+
await registerPage.goto();
77+
await registerPage.page.waitForLoadState('networkidle');
78+
79+
const passwordBtn = registerPage.page.locator('#modePassword');
80+
const passwordlessBtn = registerPage.page.locator('#modePasswordless');
81+
82+
// Switch to passwordless
83+
await passwordlessBtn.click();
84+
await expect(passwordlessBtn).toHaveClass(/active/);
85+
await expect(passwordBtn).not.toHaveClass(/active/);
86+
87+
// Switch back to password
88+
await passwordBtn.click();
89+
await expect(passwordBtn).toHaveClass(/active/);
90+
await expect(passwordlessBtn).not.toHaveClass(/active/);
91+
});
92+
93+
test('should keep name and email fields visible in passwordless mode', async ({
94+
registerPage,
95+
}) => {
96+
await registerPage.goto();
97+
await registerPage.page.waitForLoadState('networkidle');
98+
99+
// Switch to passwordless
100+
await registerPage.page.locator('#modePasswordless').click();
101+
102+
// Name and email fields should still be visible
103+
await expect(registerPage.firstNameInput).toBeVisible();
104+
await expect(registerPage.lastNameInput).toBeVisible();
105+
await expect(registerPage.emailInput).toBeVisible();
106+
107+
// Terms checkbox should still be visible
108+
await expect(registerPage.termsCheckbox).toBeVisible();
109+
});
110+
111+
test('should remove required attribute from password fields in passwordless mode', async ({
112+
registerPage,
113+
}) => {
114+
await registerPage.goto();
115+
await registerPage.page.waitForLoadState('networkidle');
116+
117+
// Password fields should be required initially
118+
await expect(registerPage.passwordInput).toHaveAttribute('required', '');
119+
await expect(registerPage.confirmPasswordInput).toHaveAttribute('required', '');
120+
121+
// Switch to passwordless
122+
await registerPage.page.locator('#modePasswordless').click();
123+
124+
// Password fields should no longer be required
125+
await expect(registerPage.passwordInput).not.toHaveAttribute('required', '');
126+
await expect(registerPage.confirmPasswordInput).not.toHaveAttribute('required', '');
127+
128+
// Switch back - should be required again
129+
await registerPage.page.locator('#modePassword').click();
130+
await expect(registerPage.passwordInput).toHaveAttribute('required', '');
131+
await expect(registerPage.confirmPasswordInput).toHaveAttribute('required', '');
132+
});
133+
});
134+
135+
test.describe('Passwordless Form Submission', () => {
136+
test('should send passwordless registration request to correct endpoint', async ({
137+
page,
138+
registerPage,
139+
}) => {
140+
await registerPage.goto();
141+
await page.waitForLoadState('networkidle');
142+
143+
// Switch to passwordless mode
144+
await page.locator('#modePasswordless').click();
145+
146+
// Fill name and email
147+
await registerPage.firstNameInput.fill('Test');
148+
await registerPage.lastNameInput.fill('User');
149+
await registerPage.emailInput.fill('test-pwless-endpoint@example.com');
150+
await registerPage.acceptTerms();
151+
152+
// Intercept the fetch request to verify it goes to the right endpoint
153+
const requestPromise = page.waitForRequest(
154+
request => request.url().includes('/user/registration/passwordless') && request.method() === 'POST'
155+
);
156+
157+
await registerPage.submit();
158+
159+
// Verify the request was sent to the passwordless endpoint
160+
const request = await requestPromise;
161+
expect(request.url()).toContain('/user/registration/passwordless');
162+
163+
// Verify the payload contains only name and email (no password)
164+
const postData = JSON.parse(request.postData() || '{}');
165+
expect(postData.firstName).toBe('Test');
166+
expect(postData.lastName).toBe('User');
167+
expect(postData.email).toBe('test-pwless-endpoint@example.com');
168+
expect(postData.password).toBeUndefined();
169+
expect(postData.matchingPassword).toBeUndefined();
170+
});
171+
172+
test('should send standard registration request when in password mode', async ({
173+
page,
174+
registerPage,
175+
}) => {
176+
await registerPage.goto();
177+
await page.waitForLoadState('networkidle');
178+
179+
// Stay in password mode (default)
180+
await registerPage.fillForm('Test', 'User', 'test-standard@example.com', 'Test@Pass123!');
181+
await registerPage.acceptTerms();
182+
183+
// Intercept the fetch request
184+
const requestPromise = page.waitForRequest(
185+
request => request.url().includes('/user/registration') && request.method() === 'POST'
186+
);
187+
188+
await registerPage.submit();
189+
190+
// Verify the request goes to the standard endpoint (not passwordless)
191+
const request = await requestPromise;
192+
expect(request.url()).not.toContain('passwordless');
193+
});
194+
});
195+
});
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { test, expect, generateTestUser, createAndLoginUser } from '../../src/fixtures';
2+
3+
test.describe('Authentication Methods', () => {
4+
test.describe('Profile Page Auth Methods Card', () => {
5+
test('should display auth methods section for logged-in user', async ({
6+
page,
7+
updateUserPage,
8+
testApiClient,
9+
cleanupEmails,
10+
}) => {
11+
const user = generateTestUser('auth-methods-display');
12+
cleanupEmails.push(user.email);
13+
14+
await createAndLoginUser(page, testApiClient, user);
15+
await updateUserPage.goto();
16+
await page.waitForLoadState('networkidle');
17+
18+
// Auth methods section should become visible after JS loads
19+
const authSection = page.locator('#auth-methods-section');
20+
await authSection.waitFor({ state: 'visible', timeout: 5000 });
21+
await expect(authSection).toBeVisible();
22+
});
23+
24+
test('should show password badge for user with password', async ({
25+
page,
26+
updateUserPage,
27+
testApiClient,
28+
cleanupEmails,
29+
}) => {
30+
const user = generateTestUser('auth-methods-badges');
31+
cleanupEmails.push(user.email);
32+
33+
await createAndLoginUser(page, testApiClient, user);
34+
await updateUserPage.goto();
35+
await page.waitForLoadState('networkidle');
36+
37+
// Wait for auth methods to load
38+
const badgesContainer = page.locator('#auth-method-badges');
39+
await badgesContainer.locator('.badge').first().waitFor({ state: 'visible', timeout: 5000 });
40+
41+
// Should show a "Password" badge
42+
const passwordBadge = badgesContainer.locator('.badge:has-text("Password")');
43+
await expect(passwordBadge).toBeVisible();
44+
});
45+
46+
test('should hide remove password button when user has no passkeys', async ({
47+
page,
48+
updateUserPage,
49+
testApiClient,
50+
cleanupEmails,
51+
}) => {
52+
const user = generateTestUser('auth-methods-no-passkey');
53+
cleanupEmails.push(user.email);
54+
55+
await createAndLoginUser(page, testApiClient, user);
56+
await updateUserPage.goto();
57+
await page.waitForLoadState('networkidle');
58+
59+
// Wait for auth methods section to load
60+
const authSection = page.locator('#auth-methods-section');
61+
await authSection.waitFor({ state: 'visible', timeout: 5000 });
62+
63+
// Remove password button should be hidden (user has no passkeys)
64+
const removePasswordContainer = page.locator('#removePasswordContainer');
65+
await expect(removePasswordContainer).toBeHidden();
66+
});
67+
68+
test('should hide set password link for user with password', async ({
69+
page,
70+
updateUserPage,
71+
testApiClient,
72+
cleanupEmails,
73+
}) => {
74+
const user = generateTestUser('auth-methods-set-pass');
75+
cleanupEmails.push(user.email);
76+
77+
await createAndLoginUser(page, testApiClient, user);
78+
await updateUserPage.goto();
79+
await page.waitForLoadState('networkidle');
80+
81+
// Wait for auth methods section to load
82+
const authSection = page.locator('#auth-methods-section');
83+
await authSection.waitFor({ state: 'visible', timeout: 5000 });
84+
85+
// Set password container should be hidden (user already has password)
86+
const setPasswordContainer = page.locator('#setPasswordContainer');
87+
await expect(setPasswordContainer).toBeHidden();
88+
});
89+
});
90+
91+
test.describe('Passkey Management Section', () => {
92+
test('should display passkey management section', async ({
93+
page,
94+
updateUserPage,
95+
testApiClient,
96+
cleanupEmails,
97+
}) => {
98+
const user = generateTestUser('passkey-section');
99+
cleanupEmails.push(user.email);
100+
101+
await createAndLoginUser(page, testApiClient, user);
102+
await updateUserPage.goto();
103+
await page.waitForLoadState('networkidle');
104+
105+
// Passkey section should be visible
106+
const passkeySection = page.locator('#passkey-section');
107+
await expect(passkeySection).toBeVisible();
108+
109+
// Add passkey button should be present
110+
const registerBtn = page.locator('#registerPasskeyBtn');
111+
await expect(registerBtn).toBeVisible();
112+
113+
// Label input should be present
114+
const labelInput = page.locator('#passkeyLabel');
115+
await expect(labelInput).toBeVisible();
116+
});
117+
118+
test('should show empty passkeys message when user has no passkeys', async ({
119+
page,
120+
updateUserPage,
121+
testApiClient,
122+
cleanupEmails,
123+
}) => {
124+
const user = generateTestUser('passkey-empty');
125+
cleanupEmails.push(user.email);
126+
127+
await createAndLoginUser(page, testApiClient, user);
128+
await updateUserPage.goto();
129+
await page.waitForLoadState('networkidle');
130+
131+
// Wait for passkeys to load
132+
const passkeysList = page.locator('#passkeys-list');
133+
await passkeysList.locator('p, .card').first().waitFor({ state: 'visible', timeout: 5000 });
134+
135+
// Should show "No passkeys registered yet" message
136+
const emptyMessage = passkeysList.locator('text=No passkeys registered yet');
137+
await expect(emptyMessage).toBeVisible();
138+
});
139+
});
140+
141+
test.describe('Change Password Page Adaptation', () => {
142+
test('should show current password field for user with password', async ({
143+
page,
144+
updatePasswordPage,
145+
testApiClient,
146+
cleanupEmails,
147+
}) => {
148+
const user = generateTestUser('change-pass-has-pass');
149+
cleanupEmails.push(user.email);
150+
151+
await createAndLoginUser(page, testApiClient, user);
152+
await updatePasswordPage.goto();
153+
await page.waitForLoadState('networkidle');
154+
155+
// Current password section should be visible
156+
const currentPasswordSection = page.locator('#currentPasswordSection');
157+
await expect(currentPasswordSection).toBeVisible();
158+
159+
// Page title should say "Update" (not "Set")
160+
const pageTitle = page.locator('#pageTitle');
161+
const titleText = await pageTitle.textContent();
162+
expect(titleText).not.toContain('Set a Password');
163+
164+
// Set password info alert should be hidden
165+
const setPasswordInfo = page.locator('#setPasswordInfo');
166+
await expect(setPasswordInfo).toBeHidden();
167+
});
168+
});
169+
170+
test.describe('Navigation Links', () => {
171+
test('should show change password link on profile page', async ({
172+
page,
173+
updateUserPage,
174+
testApiClient,
175+
cleanupEmails,
176+
}) => {
177+
const user = generateTestUser('auth-nav-links');
178+
cleanupEmails.push(user.email);
179+
180+
await createAndLoginUser(page, testApiClient, user);
181+
await updateUserPage.goto();
182+
await page.waitForLoadState('networkidle');
183+
184+
// Wait for auth methods to load so link text updates
185+
const authSection = page.locator('#auth-methods-section');
186+
await authSection.waitFor({ state: 'visible', timeout: 5000 });
187+
188+
// Change password link should be visible
189+
const changePasswordLink = page.locator('#changePasswordLink');
190+
await expect(changePasswordLink).toBeVisible();
191+
192+
// For a user with a password, it should say "Change Password"
193+
const linkText = await changePasswordLink.textContent();
194+
expect(linkText).toContain('Change Password');
195+
});
196+
});
197+
});

0 commit comments

Comments
 (0)