Skip to content

Commit 522fdb7

Browse files
authored
Merge branch 'develop' into dependabot/npm_and_yarn/nanoid-3.3.18
2 parents 57fbfcc + 235dd68 commit 522fdb7

5 files changed

Lines changed: 1256 additions & 4008 deletions

File tree

e2e/helpers/auth.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { APIRequestContext, Page } from '@playwright/test';
2+
import { expect } from '../fixtures';
3+
import { usernamePrefix, emailSuffix, password } from './test-user';
4+
5+
export interface TestUser {
6+
username: string;
7+
email: string;
8+
}
9+
10+
/**
11+
* Creates a test user via a direct API call rather than the UI signup flow —
12+
* login doesn't require a verified email (see
13+
* server/controllers/user.controller/signup.ts), so this is a faster, more
14+
* focused way to get a test user for tests that aren't testing signup itself.
15+
*/
16+
export async function createTestUser(
17+
request: APIRequestContext
18+
): Promise<TestUser> {
19+
const username = `${usernamePrefix()}${Date.now().toString(36)}`;
20+
const email = `${username}${emailSuffix()}`;
21+
22+
const res = await request.post('/editor/signup', {
23+
headers: { 'Content-Type': 'application/json' },
24+
data: {
25+
username,
26+
email,
27+
password: password(),
28+
confirmPassword: password()
29+
}
30+
});
31+
32+
if (!res.ok()) {
33+
throw new Error(
34+
`createTestUser: failed to create test user — ${res.status()}\n${await res.text()}`
35+
);
36+
}
37+
38+
return { username, email };
39+
}
40+
41+
/**
42+
* Logs in via the UI form. Assumes the page is already on /login.
43+
*/
44+
export async function loginAs(page: Page, user: TestUser): Promise<void> {
45+
await page.fill('input[name="email"]', user.email);
46+
await page.fill('input[name="password"]', password());
47+
await expect(page.locator('button[type="submit"]')).toBeEnabled({
48+
timeout: 5_000
49+
});
50+
await page.click('button[type="submit"]');
51+
52+
// Successful login redirects to the editor — a screen-visible signal
53+
// rather than asserting on the URL.
54+
await expect(page.locator('.editor-holder')).toBeVisible({
55+
timeout: 15_000
56+
});
57+
}

e2e/specs/login.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { test, expect } from '../fixtures';
2+
import { dismissCookieBanner } from '../helpers/cookie-banner';
3+
import { password } from '../helpers/test-user';
4+
import { createTestUser, TestUser } from '../helpers/auth';
5+
6+
test.describe('login', () => {
7+
let testUser: TestUser;
8+
9+
test.beforeAll(async ({ request }) => {
10+
testUser = await createTestUser(request);
11+
});
12+
13+
test.beforeEach(async ({ page }) => {
14+
await page.goto('/');
15+
await dismissCookieBanner(page);
16+
});
17+
18+
test('existing user can log in with username and password', async ({
19+
page
20+
}) => {
21+
await page.locator('a[href="/login"]').click();
22+
23+
await expect(page.locator('h2.form-container__title')).toHaveText('Log In');
24+
25+
// Passport's usernameField is 'email' the input accepts either
26+
// username or email as the value but the field name is 'email'
27+
await page.fill('input[name="email"]', testUser.email);
28+
await page.fill('input[name="password"]', password());
29+
30+
await expect(page.locator('button[type="submit"]')).toBeEnabled({
31+
timeout: 5_000
32+
});
33+
await page.click('button[type="submit"]');
34+
35+
// Successful login redirects to the editor. A screen-visible signal
36+
// rather than asserting on the URL.
37+
await expect(page.locator('.editor-holder')).toBeVisible({
38+
timeout: 15_000
39+
});
40+
41+
await expect(page.locator('a[href="/login"]')).toHaveCount(0, {
42+
timeout: 5_000
43+
});
44+
await expect(
45+
page.locator(`text=${testUser.username}`).first()
46+
).toBeVisible({ timeout: 5_000 });
47+
48+
// logout flow
49+
await page.locator(`button:has-text("${testUser.username}")`).click();
50+
await page.locator('#account-logout').click();
51+
await expect(page.locator('a[href="/login"]')).toBeVisible({
52+
timeout: 5_000
53+
});
54+
await expect(page.locator(`text=${testUser.username}`)).toHaveCount(0, {
55+
timeout: 5_000
56+
});
57+
});
58+
});

e2e/specs/signup.spec.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ test.describe('signup and email verification', () => {
1111
const uniqueId = `${usernamePrefix()}${Date.now()}`;
1212
const email = `${uniqueId}${emailSuffix()}`;
1313

14-
await page.goto('/signup');
15-
14+
await page.goto('/');
1615
await dismissCookieBanner(page);
1716

17+
await page.locator('a[href="/signup"]').click();
18+
1819
await page.locator('#username').fill(uniqueId);
1920
await page.locator('#email').fill(email);
2021
await page.locator('#password').fill(password());
@@ -42,4 +43,63 @@ test.describe('signup and email verification', () => {
4243
const session = await page.request.get('/editor/session');
4344
expect((await session.json()).verified).toBe('verified');
4445
});
46+
47+
test('cannot sign up with an already-used username or email', async ({
48+
page
49+
}) => {
50+
const uniqueId = `${usernamePrefix()}${Date.now()}`;
51+
const email = `${uniqueId}${emailSuffix()}`;
52+
53+
await page.goto('/');
54+
55+
await dismissCookieBanner(page);
56+
57+
await page.locator('a[href="/signup"]').click();
58+
59+
await page.locator('#username').fill(uniqueId);
60+
await page.locator('#email').fill(email);
61+
await page.locator('#password').fill(password());
62+
await page.locator('#confirmPassword').fill(password());
63+
64+
await page.getByRole('button', { name: 'Sign Up', exact: true }).click();
65+
66+
// Successful signup logs the user in and redirects to the editor
67+
await expect(page.locator('.editor-holder')).toBeVisible({
68+
timeout: 15_000
69+
});
70+
71+
await page.locator(`button:has-text("${uniqueId}")`).click();
72+
await page.locator('#account-logout').click();
73+
74+
await expect(page.locator('a[href="/signup"]')).toBeVisible({
75+
timeout: 5_000
76+
});
77+
await page.locator('a[href="/signup"]').click();
78+
79+
// Fill the input field and move away, error appears on blur
80+
await page.locator('#username').fill(uniqueId);
81+
await page.locator('#email').click();
82+
await expect(
83+
page
84+
.locator('.form-error')
85+
.filter({ hasText: 'This username is already taken.' })
86+
).toBeVisible({ timeout: 5_000 });
87+
await page.locator('#email').fill(email);
88+
await page.locator('#password').click();
89+
await expect(
90+
page
91+
.locator('.form-error')
92+
.filter({ hasText: 'This email is already taken.' })
93+
).toBeVisible({ timeout: 5_000 });
94+
95+
// Now fill passwords — email error will disappear but that's fine
96+
// we already asserted on it above
97+
await page.locator('#password').fill(password());
98+
await page.locator('#confirmPassword').fill(password());
99+
100+
// Submit button should still be disabled due to duplicate username/email
101+
await expect(
102+
page.getByRole('button', { name: 'Sign Up', exact: true })
103+
).toBeDisabled();
104+
});
45105
});

0 commit comments

Comments
 (0)