bilhej/frontend/e2e/account-settings.spec.ts
Joakim Mörling 3532e4d486
All checks were successful
CI / Lint, type check, unit tests, coverage (pull_request) Successful in 2m9s
CI / E2E browser tests (pull_request) Successful in 1m55s
Add account settings dropdown and verified email change flow.
Replace the header "Byt lösenord" link with an Inställningar menu for
changing email or password. Email changes are two-step: request with
password, confirmation link to the new address, then password again on
confirm so a wrong inbox cannot take over the account.

- Backend: EmailChangeService, V10 email_change_tokens, confirm API
- Frontend: ChangeEmailPage, ConfirmEmailChangePage, header dropdown
- E2E: account-settings round-trips, Mailpit verification, wrong-password guard
- Flyway: V9 restore for dev DBs, CI migration checks, V10 for email tokens

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 14:33:06 +02:00

182 lines
6.2 KiB
TypeScript

import { test, expect, type Page, type APIRequestContext } from '@playwright/test'
import {
clearMailpit,
countMessagesTo,
waitForEmailChangeToken,
} from './helpers/mailpit'
test.describe('Account settings', () => {
test('can change password and change back', async ({ page, request }) => {
const email = `pw-change-${Date.now()}@bilhej.se`
const originalPassword = 'original1234'
const changedPassword = 'changed12345'
await registerUser(request, email, originalPassword)
await loginViaUi(page, email, originalPassword)
await changePasswordViaUi(page, originalPassword, changedPassword)
await expect(page.getByText('Lösenordet har uppdaterats.')).toBeVisible()
await logoutViaHeader(page)
await expectLoginFails(page, email, originalPassword)
await loginViaUi(page, email, changedPassword)
await changePasswordViaUi(page, changedPassword, originalPassword)
await expect(page.getByText('Lösenordet har uppdaterats.')).toBeVisible()
await logoutViaHeader(page)
await expectLoginFails(page, email, changedPassword)
await loginViaUi(page, email, originalPassword)
})
test('can change email after confirming link sent to new address', async ({
page,
request,
}) => {
const suffix = Date.now()
const originalEmail = `email-change-${suffix}@bilhej.se`
const tempEmail = `email-change-${suffix}-new@bilhej.se`
const password = 'password1234'
await clearMailpit(request)
await registerUser(request, originalEmail, password)
await loginViaUi(page, originalEmail, password)
await page.goto('/andra-epost')
await changeEmailViaUi(page, tempEmail, password)
await expect(
page.getByText(
'Vi har skickat en bekräftelselänk till din nya e-postadress.',
),
).toBeVisible()
expect(await countMessagesTo(request, tempEmail)).toBe(1)
expect(await countMessagesTo(request, originalEmail)).toBe(0)
const token = await waitForEmailChangeToken(request, tempEmail, {
publicBaseUrl: 'http://frontend',
})
await confirmEmailChangeViaUi(page, token, password)
await expect(
page.getByText('Din e-postadress har uppdaterats.'),
).toBeVisible()
await expect(page.locator('header')).toContainText(tempEmail)
await clearMailpit(request)
await page.goto('/andra-epost')
await changeEmailViaUi(page, originalEmail, password)
await expect(
page.getByText(
'Vi har skickat en bekräftelselänk till din nya e-postadress.',
),
).toBeVisible()
expect(await countMessagesTo(request, originalEmail)).toBe(1)
const restoreToken = await waitForEmailChangeToken(request, originalEmail, {
publicBaseUrl: 'http://frontend',
})
await confirmEmailChangeViaUi(page, restoreToken, password)
await expect(
page.getByText('Din e-postadress har uppdaterats.'),
).toBeVisible()
await expect(page.locator('header')).toContainText(originalEmail)
})
test('does not change email when confirm password is wrong', async ({
page,
request,
}) => {
const suffix = Date.now()
const originalEmail = `email-wrongpw-e2e-${suffix}@bilhej.se`
const tempEmail = `email-wrongpw-e2e-${suffix}-new@bilhej.se`
const password = 'password1234'
await clearMailpit(request)
await registerUser(request, originalEmail, password)
await loginViaUi(page, originalEmail, password)
await page.goto('/andra-epost')
await changeEmailViaUi(page, tempEmail, password)
const token = await waitForEmailChangeToken(request, tempEmail, {
publicBaseUrl: 'http://frontend',
})
await page.goto(`/bekrafta-epost?token=${token}`)
await page.locator('#password').fill('wrongpassword')
await page.getByRole('button', { name: 'Bekräfta ny e-postadress' }).click()
await expect(page.getByText('Lösenordet är felaktigt')).toBeVisible()
await expect(page.locator('header')).toContainText(originalEmail)
const login = await request.post('/api/auth/login', {
data: { email: originalEmail, password },
})
expect(login.ok()).toBeTruthy()
const loginWithNewEmail = await request.post('/api/auth/login', {
data: { email: tempEmail, password },
})
expect(loginWithNewEmail.ok()).toBeFalsy()
})
})
async function registerUser(
request: APIRequestContext,
email: string,
password: string,
) {
const response = await request.post('/api/auth/register', {
data: { email, password },
})
expect(response.ok()).toBeTruthy()
}
async function loginViaUi(page: Page, email: string, password: string) {
await page.goto('/logga-in')
await page.getByLabel('E-postadress').fill(email)
await page.getByLabel('Lösenord').fill(password)
await page.getByRole('button', { name: 'Logga in' }).click()
await expect(page).toHaveURL('/')
}
async function expectLoginFails(page: Page, email: string, password: string) {
await page.goto('/logga-in')
await page.getByLabel('E-postadress').fill(email)
await page.getByLabel('Lösenord').fill(password)
await page.getByRole('button', { name: 'Logga in' }).click()
await expect(page.getByText('Felaktig e-post eller lösenord')).toBeVisible()
}
async function logoutViaHeader(page: Page) {
await page.locator('header').getByRole('button', { name: 'Logga ut' }).click()
await expect(page).toHaveURL('/')
}
async function changePasswordViaUi(
page: Page,
currentPassword: string,
newPassword: string,
) {
await page.goto('/andra-losenord')
await page.locator('#current-password').fill(currentPassword)
await page.locator('#password').fill(newPassword)
await page.locator('#confirm-password').fill(newPassword)
await page.getByRole('button', { name: 'Spara nytt lösenord' }).click()
}
async function changeEmailViaUi(page: Page, newEmail: string, password: string) {
await page.locator('#new-email').fill(newEmail)
await page.locator('#password').fill(password)
await page.getByRole('button', { name: 'Spara ny e-postadress' }).click()
}
async function confirmEmailChangeViaUi(
page: Page,
token: string,
password: string,
) {
await page.goto(`/bekrafta-epost?token=${token}`)
await page.locator('#password').fill(password)
await page.getByRole('button', { name: 'Bekräfta ny e-postadress' }).click()
}