Files
computer-playbook/roles/test-e2e-playwright/files/playwright.config.js
kevinveenbirkenbachandClaude Opus 5.5 c35f2409a4 fix(web-app-odoo): make the OAuth addon work on Odoo 20
Nobody could sign in to Odoo through Keycloak. Odoo sent an http redirect
URI, and Keycloak refused it as "Invalid parameter: redirect_uri".

The addon auth_oauth_https builds that URI from web.base.url. Two things
kept it from doing so after the image moved to Odoo 20.

Its manifest declared version 19.0.1.8.0, and Odoo marks a module whose
version names another series as not installable. It only warns about that,
so the install task stayed green and the addon was missing. The manifest
now declares 1.8.0, which Odoo prefixes with its own series.

Once installed, the addon read the parameter with get_param, which Odoo 20
replaced by typed getters. The call raised AttributeError, a broad except
logged it and fell back to the URL of the request, which is http behind
the proxy. The addon reads the parameter with get_str now and no longer
catches the error.

A deploy now fails when the addon is not installed afterwards. The check
reads ir_module_module, because the output of "odoo -i" does not tell an
installed module from a skipped one.

The spec logged out by navigating to /web/session/logout. Odoo 20 accepts
POST only on that route and answered 405, so both SSO tests failed after
a successful login. The spec logs out through the user menu now.

A host that runs the suite with several workers still failed it. Odoo
computes the session token of an account from its OAuth access token, so
every new sign-in ends the other sessions of that account, and the spec
files share the administrator. The role caps its suite at one worker
with PLAYWRIGHT_MAX_WORKERS, which the central config now honours, and
the spec asserts the cap.

Closes #822

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 11:37:41 +02:00

84 lines
3.1 KiB
JavaScript

const { defineConfig } = require("@playwright/test");
// nocheck: env-default a role may export its own base variable and no APP_BASE_URL
const baseURL = process.env.APP_BASE_URL || "http://127.0.0.1";
const keepAll = (process.env.INFINITO_PLAYWRIGHT_KEEP || "").toLowerCase() === "true";
function onionSecureOrigins() {
const origins = new Set();
for (const value of Object.values(process.env)) {
if (typeof value !== "string") continue;
for (const match of value.match(/https?:\/\/[^/,\s"']+\.onion/gi) || []) {
try {
origins.add(new URL(match).origin);
} catch {
/* not a parseable URL — skip */
}
}
}
return [...origins];
}
const onionSecure = onionSecureOrigins();
/**
* The timeout the harness renders into the staged .env under this name.
*
* Args:
* name: the PLAYWRIGHT_*_TIMEOUT variable to read.
*/
function requiredTimeout(name) {
const value = Number(process.env[name]);
if (!Number.isFinite(value) || value <= 0) {
throw new Error(
`${name} is missing from the environment. The harness writes every ` +
"timeout into the staged .env, so this run was started outside it."
);
}
return value;
}
const globalTimeout = parseInt(process.env.INFINITO_PLAYWRIGHT_GLOBAL_TIMEOUT_MS || 0, 10);
module.exports = defineConfig({
testDir: "./tests",
testMatch: "**/*.@(spec|test).js",
timeout: requiredTimeout("PLAYWRIGHT_TEST_TIMEOUT"),
expect: { timeout: requiredTimeout("PLAYWRIGHT_EXPECT_TIMEOUT") },
...(globalTimeout > 0 ? { globalTimeout } : {}),
retries: 2,
workers: Math.min(
Number(process.env.PLAYWRIGHT_WORKERS) || 1,
Number(process.env.PLAYWRIGHT_MAX_WORKERS) || Infinity
),
fullyParallel: (process.env.PLAYWRIGHT_FULLY_PARALLEL || "").toLowerCase() === "true",
outputDir: "/reports/test-results",
reporter: [
["list"],
// `github` emits ::error file=...,line=...::-annotations for failed
// tests when the runner exports GITHUB_ACTIONS=true, which surfaces
// failures inline on the workflow run page.
["github"],
["junit", { outputFile: "/reports/playwright-junit.xml" }],
["html", { outputFolder: "/reports/playwright-report", open: "never" }]
],
use: {
baseURL,
// Route the browser through a SOCKS proxy when set (e.g. Tor for .onion
// targets, which Chromium cannot resolve over normal DNS). Empty/unset =
// direct connection (unchanged default for clearnet targets).
proxy: process.env.PLAYWRIGHT_PROXY ? { server: process.env.PLAYWRIGHT_PROXY } : undefined,
launchOptions: onionSecure.length
? { args: [`--unsafely-treat-insecure-origin-as-secure=${onionSecure.join(",")}`] }
: undefined,
// Fail fast instead of hanging until the per-test timeout when a target is
// unreachable (e.g. an onion service that is not yet published).
navigationTimeout: requiredTimeout("PLAYWRIGHT_NAVIGATION_TIMEOUT"),
actionTimeout: requiredTimeout("PLAYWRIGHT_ACTION_TIMEOUT"),
trace: keepAll ? "on" : "retain-on-failure",
screenshot: keepAll ? "on" : "only-on-failure",
video: keepAll ? "on" : "retain-on-failure"
}
});