Skip to content

Commit 61a455e

Browse files
authored
Merge pull request #1018 from digitalservicebund/flaky-e2e
E2E tests: keep token in memory instead of writing to file
2 parents a84cfaa + 5b59c90 commit 61a455e

File tree

4 files changed

+64
-62
lines changed

4 files changed

+64
-62
lines changed

frontend/e2e/globalSetup/login-and-create-sample-data.setup.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ import { test as setup } from "@e2e/utils/test-with-auth"
33
import fs from "fs"
44
import path from "node:path"
55

6-
setup("login", async ({ page }) => {
6+
setup("login", async ({ page, appCredentials }) => {
77
await page.goto("/")
88
await page.waitForURL(/localhost:8443/)
99

1010
await page
1111
.getByRole("textbox", { name: "Username or email" })
12-
.fill("jane.doe")
12+
.fill(appCredentials.username)
1313

14-
await page.getByRole("textbox", { name: "Password" }).fill("test")
14+
await page
15+
.getByRole("textbox", { name: "Password" })
16+
.fill(appCredentials.password)
1517

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

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

20-
await page.waitForURL("/")
21-
await page.unrouteAll({ behavior: "wait" })
22+
await page.waitForURL(/\/amending-laws/)
2223
})
2324

2425
setup("create sample data", async ({ authenticatedRequest: request }) => {

frontend/e2e/utils/test-with-auth.ts

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import {
22
APIRequestContext,
33
test as base /* eslint-disable-line no-restricted-imports -- We need this here to extend it */,
44
} from "@playwright/test"
5-
import { readFile, writeFile } from "node:fs/promises"
6-
import { fileURLToPath, URL } from "node:url"
75

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

16-
export type AuthorizationHeader = { Authorization: string }
14+
let savedToken: Token
15+
16+
async function getToken(
17+
username: string,
18+
password: string,
19+
request: APIRequestContext,
20+
): Promise<Token> {
21+
if (!savedToken) {
22+
const tokenRequest = await request.post(
23+
"http://localhost:8443/realms/ris/protocol/openid-connect/token",
24+
{
25+
form: {
26+
grant_type: "password",
27+
client_id: "ris-norms-local",
28+
client_secret: "ris-norms-local",
29+
username,
30+
password,
31+
},
32+
},
33+
)
34+
35+
if (!tokenRequest.ok) {
36+
throw new Error("Failed to fetch a token for the E2E tests", {
37+
cause: tokenRequest,
38+
})
39+
}
40+
41+
savedToken = await tokenRequest.json()
42+
}
43+
44+
return savedToken
45+
}
1746

1847
/**
1948
* Drop-in replacement for Playwright's regular test method that also handles
2049
* the authorization token. This should be used for all tests in order to
2150
* guarantee that the token is always up to date.
2251
*/
2352
export const test = base.extend<{
24-
/**
25-
* Hooks into the requests made by the page. If one of them contains a new token,
26-
* the token will automatically be saved to a shared location, so it can be used
27-
* by other tests.
28-
*
29-
* This behavior is managed by Playwright automatically. The method should not be
30-
* called manually.
31-
*/
32-
updateToken: void
33-
3453
/**
3554
* Provides an API context that can be used to make requests just like `page.request`,
3655
* except that those requests will be authenticated with a previously saved token.
3756
*/
3857
authenticatedRequest: APIRequestContext
39-
}>({
40-
updateToken: [
41-
async ({ page }, use) => {
42-
await page.route(/token$/, async (route) => {
43-
const response = await page.request.fetch(route.request())
44-
if (response.ok()) await saveToken(await response.json())
45-
await route.fulfill({ response })
46-
})
4758

48-
await use()
49-
},
50-
{ auto: true },
59+
/**
60+
* Username and password of the example user that should be used in E2E tests.
61+
*/
62+
appCredentials: { username: string; password: string }
63+
}>({
64+
appCredentials: [
65+
{ username: "jane.doe", password: "test" },
66+
{ option: true },
5167
],
5268

53-
authenticatedRequest: async ({ playwright }, use) => {
54-
const token = await restoreToken()
69+
authenticatedRequest: async (
70+
{ playwright, request, appCredentials },
71+
use,
72+
) => {
73+
const token = await getToken(
74+
appCredentials.username,
75+
appCredentials.password,
76+
request,
77+
)
5578

56-
const request = await playwright.request.newContext({
79+
const authenticatedRequest = await playwright.request.newContext({
5780
extraHTTPHeaders: {
5881
Authorization: `Bearer ${token.access_token}`,
5982
},
6083
})
6184

62-
await use(request)
85+
await use(authenticatedRequest)
6386
},
6487
})
65-
66-
const storagePath = fileURLToPath(
67-
new URL("../storage/token.json", import.meta.url),
68-
)
69-
70-
/**
71-
* Takes a token and saves it to a shared location that can be used across tests.
72-
*
73-
* @param token Token to save
74-
*/
75-
export function saveToken(token: Token): Promise<void> {
76-
return writeFile(storagePath, JSON.stringify(token, undefined, 2), {
77-
encoding: "utf-8",
78-
})
79-
}
80-
81-
/**
82-
* Loads a previously saved token from the shared location. This will throw if
83-
* no token has been saved.
84-
*
85-
* @returns Saved token
86-
*/
87-
export async function restoreToken(): Promise<Token> {
88-
const raw = await readFile(storagePath, { encoding: "utf-8" })
89-
return JSON.parse(raw)
90-
}

frontend/playwright.config.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { devices, PlaywrightTestConfig } from "@playwright/test"
1+
import { defineConfig, devices } from "@playwright/test"
22
import dotenv from "dotenv"
33

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

6-
const config: PlaywrightTestConfig = {
6+
const config = defineConfig<{
7+
appCredentials: { username: string; password: string }
8+
}>({
79
testDir: "./e2e",
810
timeout: 10000,
911
retries: process.env.CI === "true" ? 1 : 0,
@@ -85,6 +87,6 @@ const config: PlaywrightTestConfig = {
8587
testMatch: "e2e/login-and-logout.spec.ts",
8688
},
8789
],
88-
}
90+
})
8991

9092
export default config

local/keycloak/realm.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"realm": "ris",
44
"revokeRefreshToken": true,
55
"refreshTokenMaxReuse": 0,
6+
"accessTokenLifespan": 1800,
67
"sslRequired": "none",
78
"ssoSessionIdleTimeout": 432000,
89
"ssoSessionMaxLifespan": 7776000,
@@ -20,6 +21,7 @@
2021
"clientId": "ris-norms-local",
2122
"name": "ris-norms-local",
2223
"enabled": true,
24+
"directAccessGrantsEnabled": true,
2325
"publicClient": true,
2426
"rootUrl": "http://localhost:8080",
2527
"baseUrl": "http://localhost:8080",

0 commit comments

Comments
 (0)