From d7627be056a0193c669699df261661cc1fe89a43 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 13 Apr 2025 16:43:23 +0200 Subject: [PATCH] test: Authenticated tests setup --- frontend/e2e/basic-learning.ts | 5 ++ frontend/e2e/fixtures.ts | 110 +++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 frontend/e2e/basic-learning.ts create mode 100644 frontend/e2e/fixtures.ts diff --git a/frontend/e2e/basic-learning.ts b/frontend/e2e/basic-learning.ts new file mode 100644 index 00000000..45851236 --- /dev/null +++ b/frontend/e2e/basic-learning.ts @@ -0,0 +1,5 @@ +import { test, expect } from './fixtures.js'; + +test('myTest', async ({ page }) => { + await expect(page).toHaveURL('/'); +}); diff --git a/frontend/e2e/fixtures.ts b/frontend/e2e/fixtures.ts new file mode 100644 index 00000000..7035bfad --- /dev/null +++ b/frontend/e2e/fixtures.ts @@ -0,0 +1,110 @@ +/* eslint-disable no-await-in-loop */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { test as baseTest, expect } from '@playwright/test'; +import type { Browser } from 'playwright-core'; +import fs from 'fs'; +import path from 'path'; + +/* Based on https://playwright.dev/docs/auth#moderate-one-account-per-parallel-worker */ + +export * from '@playwright/test'; +export const ROOT_URL = 'http://localhost:5173'; + +interface Account { + username: string; + password: string; +} + +/** + * Acquire an account by logging in or creating a new one. + * @param id + * @param browser + */ +async function acquireAccount(id: number, browser: Browser): Promise { + const account = { + username: `worker${id}`, + password: 'password', + } + + const page = await browser.newPage(); + await page.goto(ROOT_URL); + + await page.getByRole('link', { name: 'log in' }).click(); + await page.getByRole('button', { name: 'student' }).click(); + + await page.getByRole('textbox', { name: 'Username' }).fill(account.username); + await page.getByRole('textbox', { name: 'Password', exact: true }).fill(account.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + + let failed = await page.getByText('Invalid username or password.').isVisible(); + + if (failed) { + await page.getByRole('link', { name: 'Register' }).click(); + } + + while (failed) { + // Retry with a different username, based on Unix timestamp. + account.username = `worker${id}-${Date.now()}`; + + await page.getByRole('textbox', { name: 'Username' }).fill(account.username); + await page.getByRole('textbox', { name: 'Password', exact: true }).fill(account.password); + await page.getByRole('textbox', { name: 'Confirm password' }).fill(account.password); + await page.getByRole('textbox', { name: 'Email' }).fill(`${account.username}@dwengo.org`); + await page.getByRole('textbox', { name: 'First name' }).fill('Worker'); + await page.getByRole('textbox', { name: 'Last name' }).fill(id.toString()); + await page.getByRole('button', { name: 'Register' }).click(); + + await page.waitForURL(/localhost/); + + failed = await page.getByText('Username already exists.').isVisible(); + } + + await page.waitForURL(/localhost/); + await page.close(); + + return account; +} + +export const test = baseTest.extend({ + // Use the same storage state for all tests in this worker. + storageState: async ({ workerStorageState }, use) => use(workerStorageState), + + // Authenticate once per worker with a worker-scoped fixture. + workerStorageState: [async ({ browser }, use): Promise => { + // Use parallelIndex as a unique identifier for each worker. + const id = test.info().parallelIndex; + const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`); + + if (fs.existsSync(fileName)) { + // Reuse existing authentication state if any. + await use(fileName); + return; + } + + // Important: make sure we authenticate in a clean environment by unsetting storage state. + const page = await browser.newPage({ storageState: undefined }); + + // Acquire a unique account by creating a new one. + const account = await acquireAccount(id, browser); + + // Perform authentication steps. Replace these actions with your own. + await page.goto(ROOT_URL); + await page.getByRole('link', { name: 'log in' }).click(); + await page.getByRole('button', { name: 'student' }).click(); + await page.getByRole('textbox', { name: 'Username or email' }).fill(account.username); + await page.getByRole('textbox', { name: 'Password' }).fill(account.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + // Wait until the page receives the cookies. + // + // Sometimes login flow sets cookies in the process of several redirects. + // Wait for the final URL to ensure that the cookies are actually set. + await page.waitForLoadState("domcontentloaded"); + // Alternatively, you can wait until the page reaches a state where all cookies are set. + + // End of authentication steps. + + await page.context().storageState({ path: fileName }); + await page.close(); + await use(fileName); + }, { scope: 'worker' }], +});