test: Authenticated tests setup
This commit is contained in:
		
							parent
							
								
									d53d1a018c
								
							
						
					
					
						commit
						d7627be056
					
				
					 2 changed files with 115 additions and 0 deletions
				
			
		
							
								
								
									
										5
									
								
								frontend/e2e/basic-learning.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/e2e/basic-learning.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | import { test, expect } from './fixtures.js'; | ||||||
|  | 
 | ||||||
|  | test('myTest', async ({ page }) => { | ||||||
|  |     await expect(page).toHaveURL('/'); | ||||||
|  | }); | ||||||
							
								
								
									
										110
									
								
								frontend/e2e/fixtures.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								frontend/e2e/fixtures.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<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(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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<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' }], | ||||||
|  | }); | ||||||
		Reference in a new issue