Files
computer-playbook/roles/web-app-kix/files/playwright/_shared.js
kevinveenbirkenbachandClaude Opus 4.8 07c6ad8e7c test(e2e): scale every nav click + timeout literal for onion targets
Over Tor the redirect chain behind a login/logout/OIDC click routinely exceeds Playwright's fixed 30s action timeout, so the click throws before the flow's scaled waits run. Sweep all role Playwright suites and the shared persona helpers: give every navigation/auth-transition click an onion-scaled timeout (.click({ timeout: resolveTimeout(30_000) })) and route the remaining raw millisecond literals (timeout options, waitForTimeout, sleep-promise setTimeout, positional wait args) through resolveTimeout. Local UI clicks (menus, toggles, modal dismissals, DOM clicks inside evaluate) stay untouched.

Two new lints keep it that way: test_no_raw_timeout_literals forbids raw numeric timeout literals in Playwright specs (opt-out: nocheck: raw-timeout), test_persona_clicks_scaled requires every click in the shared persona auth flows to carry a resolveTimeout timeout (opt-out: nocheck: unscaled-click).

Verified: node --check on all 133 changed JS files, all 13 playwright lints green, make autoformat clean, make test green (2745+196+163+4 tests OK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:35:53 +02:00

100 lines
3.8 KiB
JavaScript

// Shared KIX Playwright spec state: env vars consumed by more than one
// scenario, the KIX-form login helper, the full OAuth2-proxy → Keycloak →
// KIX-LDAP login/logout flow used by both the administrator and the biber
// login tests, and the `beforeEach` env-presence guard. Per-test env
// (admin credentials, biber credentials, Keycloak admin API access, etc.)
// stays in the respective `test-*.js` file.
const { expect } = require("@playwright/test");
const { resolveTimeout } = require("./timeouts");
const { decodeDotenvQuotedValue, performKeycloakLoginForm, runBiberFlow, runGuestFlow, gotoOnion } = require("./personas");
const { isServiceEnabled, skipUnlessServiceEnabled } = require("./service-gating");
const ssoEnabled = isServiceEnabled("sso");
const ldapEnabled = isServiceEnabled("ldap");
const appBaseUrl = decodeDotenvQuotedValue(process.env.APP_BASE_URL || "").replace(/\/$/, "");
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN || "");
const oidcIssuerUrl = decodeDotenvQuotedValue(process.env.OIDC_ISSUER_URL || "").replace(/\/$/, "");
const logoutUrl = decodeDotenvQuotedValue(process.env.LOGOUT_URL || "").replace(/\/$/, "");
async function performKixLogin(page, username, password) {
const usernameInput = page.locator('input[type="text"], input[type="email"], input[name="UserLogin"], input[name="username"]').first();
const passwordInput = page.locator('input[type="password"]').first();
const submitButton = page.locator('button[type="submit"], input[type="submit"], button:has-text("Login")').first();
await usernameInput.waitFor({ state: "visible", timeout: resolveTimeout(30_000) });
await usernameInput.fill(username);
await passwordInput.fill(password);
await submitButton.click({ timeout: resolveTimeout(30_000) });
}
async function runKixLoginLogoutFlow(page, username, password) {
const expectedAuthUrl = `${oidcIssuerUrl}/protocol/openid-connect/auth`;
await gotoOnion(page, `${appBaseUrl}/`, { waitUntil: "domcontentloaded" });
await expect
.poll(() => page.url(), {
timeout: resolveTimeout(30_000),
message: `Expected redirect to Keycloak OIDC auth: ${expectedAuthUrl}`,
})
.toContain(expectedAuthUrl);
await performKeycloakLoginForm(page, username, password);
await expect
.poll(() => page.url(), {
timeout: resolveTimeout(60_000),
message: `Expected redirect back to canonical KIX URL on ${canonicalDomain}`,
})
.toContain(canonicalDomain);
await page.waitForLoadState("domcontentloaded");
await performKixLogin(page, username, password);
await expect
.poll(() => page.url(), {
timeout: resolveTimeout(30_000),
message: `Expected SPA to leave /auth after successful LDAP login as ${username}`,
})
.not.toContain("/auth");
await gotoOnion(page, logoutUrl, { waitUntil: "commit" }).catch(() => {});
await page.context().clearCookies();
await gotoOnion(page, `${appBaseUrl}/`, { waitUntil: "domcontentloaded" });
await expect
.poll(() => page.url(), {
timeout: resolveTimeout(30_000),
message: `Expected post-logout request to be re-gated to ${expectedAuthUrl}`,
})
.toContain(expectedAuthUrl);
}
async function beforeEach({ page }) {
expect(appBaseUrl, "APP_BASE_URL must be set").toBeTruthy();
expect(canonicalDomain, "CANONICAL_DOMAIN must be set").toBeTruthy();
expect(oidcIssuerUrl, "OIDC_ISSUER_URL must be set").toBeTruthy();
expect(logoutUrl, "LOGOUT_URL must be set").toBeTruthy();
await page.context().clearCookies();
}
module.exports = {
env: {
ssoEnabled,
ldapEnabled,
appBaseUrl,
canonicalDomain,
oidcIssuerUrl,
logoutUrl,
},
performKixLogin,
runKixLoginLogoutFlow,
beforeEach,
isServiceEnabled,
skipUnlessServiceEnabled,
runBiberFlow,
runGuestFlow,
};