116 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /* 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<Account> {
 | |
|     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();
 | |
|     }
 | |
| 
 | |
|     const MAX_RETRIES = 5;
 | |
|     let retries = 0;
 | |
|     while (failed && retries < MAX_RETRIES) {
 | |
|         // 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();
 | |
|         retries += failed ? 1 : 0;
 | |
|     }
 | |
| 
 | |
|     await page.waitForURL(/localhost/);
 | |
|     await page.close();
 | |
| 
 | |
|     return account;
 | |
| }
 | |
| 
 | |
| export const test = baseTest.extend<object, { workerStorageState: string }>({
 | |
|     // 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<void> => {
 | |
|             // 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" },
 | |
|     ],
 | |
| });
 |