Skip to content

Commit

Permalink
Merge pull request #1018 from digitalservicebund/flaky-e2e
Browse files Browse the repository at this point in the history
E2E tests: keep token in memory instead of writing to file
  • Loading branch information
andreasphil authored Feb 20, 2025
2 parents a84cfaa + 5b59c90 commit 61a455e
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 62 deletions.
11 changes: 6 additions & 5 deletions frontend/e2e/globalSetup/login-and-create-sample-data.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ import { test as setup } from "@e2e/utils/test-with-auth"
import fs from "fs"
import path from "node:path"

setup("login", async ({ page }) => {
setup("login", async ({ page, appCredentials }) => {
await page.goto("/")
await page.waitForURL(/localhost:8443/)

await page
.getByRole("textbox", { name: "Username or email" })
.fill("jane.doe")
.fill(appCredentials.username)

await page.getByRole("textbox", { name: "Password" }).fill("test")
await page
.getByRole("textbox", { name: "Password" })
.fill(appCredentials.password)

await page.getByRole("button", { name: "Sign In" }).click()

await page.context().storageState({ path: `e2e/storage/state.json` })

await page.waitForURL("/")
await page.unrouteAll({ behavior: "wait" })
await page.waitForURL(/\/amending-laws/)
})

setup("create sample data", async ({ authenticatedRequest: request }) => {
Expand Down
105 changes: 51 additions & 54 deletions frontend/e2e/utils/test-with-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import {
APIRequestContext,
test as base /* eslint-disable-line no-restricted-imports -- We need this here to extend it */,
} from "@playwright/test"
import { readFile, writeFile } from "node:fs/promises"
import { fileURLToPath, URL } from "node:url"

/**
* Authentication token returned by the OAuth flow.
Expand All @@ -13,78 +11,77 @@ export type Token = {
// Omitted additional properties that are not needed for tests
}

export type AuthorizationHeader = { Authorization: string }
let savedToken: Token

async function getToken(
username: string,
password: string,
request: APIRequestContext,
): Promise<Token> {
if (!savedToken) {
const tokenRequest = await request.post(
"http://localhost:8443/realms/ris/protocol/openid-connect/token",
{
form: {
grant_type: "password",
client_id: "ris-norms-local",
client_secret: "ris-norms-local",
username,
password,
},
},
)

if (!tokenRequest.ok) {
throw new Error("Failed to fetch a token for the E2E tests", {
cause: tokenRequest,
})
}

savedToken = await tokenRequest.json()
}

return savedToken
}

/**
* Drop-in replacement for Playwright's regular test method that also handles
* the authorization token. This should be used for all tests in order to
* guarantee that the token is always up to date.
*/
export const test = base.extend<{
/**
* Hooks into the requests made by the page. If one of them contains a new token,
* the token will automatically be saved to a shared location, so it can be used
* by other tests.
*
* This behavior is managed by Playwright automatically. The method should not be
* called manually.
*/
updateToken: void

/**
* Provides an API context that can be used to make requests just like `page.request`,
* except that those requests will be authenticated with a previously saved token.
*/
authenticatedRequest: APIRequestContext
}>({
updateToken: [
async ({ page }, use) => {
await page.route(/token$/, async (route) => {
const response = await page.request.fetch(route.request())
if (response.ok()) await saveToken(await response.json())
await route.fulfill({ response })
})

await use()
},
{ auto: true },
/**
* Username and password of the example user that should be used in E2E tests.
*/
appCredentials: { username: string; password: string }
}>({
appCredentials: [
{ username: "jane.doe", password: "test" },
{ option: true },
],

authenticatedRequest: async ({ playwright }, use) => {
const token = await restoreToken()
authenticatedRequest: async (
{ playwright, request, appCredentials },
use,
) => {
const token = await getToken(
appCredentials.username,
appCredentials.password,
request,
)

const request = await playwright.request.newContext({
const authenticatedRequest = await playwright.request.newContext({
extraHTTPHeaders: {
Authorization: `Bearer ${token.access_token}`,
},
})

await use(request)
await use(authenticatedRequest)
},
})

const storagePath = fileURLToPath(
new URL("../storage/token.json", import.meta.url),
)

/**
* Takes a token and saves it to a shared location that can be used across tests.
*
* @param token Token to save
*/
export function saveToken(token: Token): Promise<void> {
return writeFile(storagePath, JSON.stringify(token, undefined, 2), {
encoding: "utf-8",
})
}

/**
* Loads a previously saved token from the shared location. This will throw if
* no token has been saved.
*
* @returns Saved token
*/
export async function restoreToken(): Promise<Token> {
const raw = await readFile(storagePath, { encoding: "utf-8" })
return JSON.parse(raw)
}
8 changes: 5 additions & 3 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { devices, PlaywrightTestConfig } from "@playwright/test"
import { defineConfig, devices } from "@playwright/test"
import dotenv from "dotenv"

dotenv.config({ path: [".env.local", ".env"] })

const config: PlaywrightTestConfig = {
const config = defineConfig<{
appCredentials: { username: string; password: string }
}>({
testDir: "./e2e",
timeout: 10000,
retries: process.env.CI === "true" ? 1 : 0,
Expand Down Expand Up @@ -85,6 +87,6 @@ const config: PlaywrightTestConfig = {
testMatch: "e2e/login-and-logout.spec.ts",
},
],
}
})

export default config
2 changes: 2 additions & 0 deletions local/keycloak/realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"realm": "ris",
"revokeRefreshToken": true,
"refreshTokenMaxReuse": 0,
"accessTokenLifespan": 1800,
"sslRequired": "none",
"ssoSessionIdleTimeout": 432000,
"ssoSessionMaxLifespan": 7776000,
Expand All @@ -20,6 +21,7 @@
"clientId": "ris-norms-local",
"name": "ris-norms-local",
"enabled": true,
"directAccessGrantsEnabled": true,
"publicClient": true,
"rootUrl": "http://localhost:8080",
"baseUrl": "http://localhost:8080",
Expand Down

0 comments on commit 61a455e

Please sign in to comment.