Resolves 68 conflicted files. The governing rule was our structure with their
network semantics: the tor boolean becomes a network mode (clearnet | tor |
multi) throughout the variant layer, while this branch's architecture and mesh
axes, its extracted modules and its i18n content stay.
Where the two sides covered different ground, both are kept rather than one
picked:
- ca_injected answers a ca_client on the deployment's TLS mode OR on an SSO
issuer that resolves self-signed. 3a3e9ed2c9 records why: an issuer is not
an object store, so keeping only one side drops the other's coverage.
- get_merged_domains keeps the loud refusal when svc-net-tor is deployed
without a provisioned node, now gated on their tor_serves_host; their
node_onion_of returns an empty string there, which would have served every
onion-exclusive app on clearnet in silence.
- sso_cookie_domains stays the single source for oauth2-proxy's cookie scope.
Their per-network branch omitted the issuer domain on the onion arm, which
breaks the callback leg.
- The clearnet CSP batch carries --clearnet-timeout again; their batch dict
hardcoded zero while the role still passes the flag.
- The dashboard keeps its i18n content dir, image, version and design
resolution; their side reduced the logo sources to the bare service
provider values.
Their side wins where it is measurably better: the onion mirror rewrite is
gated per vhost rather than per app, the Playwright onion decision reads the
pass's own domain instead of the role's canonical one, and the matomo
reinjection and dashboard config render per network sibling.
Eight files were broken by the merge without ever carrying a conflict marker,
because git marks only the lines both sides touched:
- utils/cache/domains.py lost the onionize block that sat between two hunks
- roles/sys-front-inj-all/templates/body_filter.lua.j2 called it
- web-app-bluesky, keycloak sso_proxy: the get_entity_name filter was renamed
- trigger/__main__.py, playwright_coverage.py and its test: the deleted tor
module
- cli/administration/deploy/ci/timings.py read Label.tor
- plugins/filter/csp_filters.py called _aligned_url with four of six arguments
- tests/unit/python/utils/cache/test_domains.py referenced a renamed class
- tests/.../sys-ctl-hlth-csp/files/test_script.py mocked a function main no
longer calls
Verified: the variant layer (155 tests, 65 subtests), ca_injected (18),
the CI layer (175), the domains cache including the restored guard (32),
retrigger inputs (9), timings and the CSP health script (53). make
quality-high clears autoformat; its remaining findings are tracked separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.6 KiB
Playwright Tests 🎭
This page covers Playwright end-to-end test requirements, runner integration, and file contracts.
For implementation guidance when writing or updating role-local files, see
Agent playwright.spec.js and
Agent playwright.env.j2.
For what the files/playwright/playwright.spec.js of a role MUST contain, see
playwright.spec.js (role file rules).
Framework 🧰
- Playwright runs inside Docker via the shared
test-e2e-playwrightrole. - There is no standalone
maketarget for Playwright. Tests run as part of local deploy flows (e.g.make compose-deploy mode=reinstall apps=web-app-matomo full_cycle=true). For spec-only iteration after the first successful deploy, see Playwright Spec Loop.
When to Write ✍️
- You MUST provide Playwright tests for every
web-*role. - For when a spec MUST be added or updated (including the
javascript.js/style.csstrigger), see the Triggers section ofplaywright.spec.js.
Role-Local Files 📁
Every Playwright-enabled role MUST provide exactly these two files:
| File | Purpose |
|---|---|
files/playwright/playwright.spec.js |
Test scenarios executed by the Playwright runner |
templates/playwright.env.j2 |
Rendered .env passed to the Playwright container |
playwright.config.js MUST remain centralized in roles/test-e2e-playwright/files/ and package.json in roles/test-e2e-playwright/templates/package.json.j2 (rendered per-deploy into the staging dir). You MUST NOT duplicate or override either per role.
The @playwright/test version pinned into the rendered package.json comes from playwright.version in roles/test-e2e-playwright/meta/services.yml via the image_version filter. That YAML value is the single source of truth for the Playwright version.
CI Image Source 🪞
The Playwright container image is declared in roles/test-e2e-playwright/meta/services.yml under the playwright service entry (image: + version:). CI mirrors that image into GHCR through the standard mirror pipeline before deploy tests run, so Playwright jobs do not depend on direct pulls from MCR during the test phase.
The version: MUST stay pinned to an exact Playwright version and distro pair such as v1.59.1-noble. You MUST NOT switch this to a mutable tag such as latest, because mutable upstream tags are more vulnerable to CDN propagation delays and make flaky CI failures much harder to reason about.
Runner 🚀
The test-e2e-playwright role discovers Playwright-enabled roles through the presence of
templates/playwright.env.j2. For each discovered role it:
- stages the test project
- renders
.envfromplaywright.env.j2 - copies
files/playwright/playwright.spec.jsinto the project astests/playwright.spec.js - renders the central
templates/package.json.j2and copies the centralfiles/playwright.config.jsinto the staging dir - waits until the app is reachable
- runs Playwright in Docker
Artefact Retention 🎞️
The runner keeps Playwright traces, screenshots, and videos for every test only when the resolved environment variable INFINITO_PLAYWRIGHT_KEEP evaluates to true (case-insensitive).
Any other value, including unset or empty, keeps artefacts only when a test fails.
The propagation chain has four hops:
- The host environment variable
INFINITO_PLAYWRIGHT_KEEP(set on the host shell, byact --env INFINITO_PLAYWRIGHT_KEEP=true, or by the GitHub Actions repository variable forwarded into eachtest-deploy-*.ymlworkflow asINFINITO_PLAYWRIGHT_KEEP: ${{ vars.INFINITO_PLAYWRIGHT_KEEP }}). roles/test-e2e-playwright/defaults/main.ymlreads the host value intotest_e2e_playwright_keepvialookup('env', 'INFINITO_PLAYWRIGHT_KEEP'). Inventory MAY override the role variable per environment ingroup_varsorhost_vars.roles/test-e2e-playwright/tasks/03_run_pass.ymlforwards the resolved value into the Playwright container as-e INFINITO_PLAYWRIGHT_KEEP={{ test_e2e_playwright_keep }}.roles/test-e2e-playwright/files/playwright.config.jsswitchestrace,screenshot, andvideotoonwhen the container value istrue. It otherwise leaves the failure-only defaults (retain-on-failurefor trace and video,only-on-failurefor screenshot).
Set the variable for the contexts you need:
- Local deploy: prefix every
make compose-deployinvocation withINFINITO_PLAYWRIGHT_KEEP=true. - Local
actruns: append--env INFINITO_PLAYWRIGHT_KEEP=trueto theactcommand. - CI: set the
INFINITO_PLAYWRIGHT_KEEPrepository variable. For the GitHub click path see CI Configuration. - Inventory pin: set
test_e2e_playwright_keep: "true"ingroup_varsorhost_varsof the target environment.
| Resolved container value | Trace | Screenshot | Video |
|---|---|---|---|
true (case-insensitive) |
on |
on |
on |
| any other value, unset, or empty | retain-on-failure |
only-on-failure |
retain-on-failure |
File Requirements 📄
See the dedicated SPOT pages for authoring rules:
playwright.spec.js: what the role-local spec MUST contain (entry point, scenarios, selectors, final state)playwright.env.j2: rendered test input surface, variable naming, and entry-point selection
Recording Tests 🎬
Playwright Codegen (built-in) 🔧
Playwright ships a built-in code recorder:
npx playwright codegen https://<app-url>
This opens the Playwright Inspector alongside a browser. Every interaction is recorded and emitted as a runnable spec. Use the output as a starting draft and clean it up according to the authoring rules in playwright.spec.js.
playwright-recorder (Docker-based) 🐳
For infrastructure-heavy setups where Playwright should not live in the main repository, use playwright-recorder. It wraps Playwright codegen in Docker with X11/XWayland forwarding and an ephemeral workspace so no state leaks between recordings.
Requirements: Docker, X11 or XWayland, xhost
# Install
make install
# Record a flow starting at a specific URL
make codegen
# or directly:
./scripts/codegen.sh https://<app-url>/login
# Replay a recorded spec headlessly
make replay
# or directly:
./scripts/replay.sh recordings/login.spec.ts
Generated files are persisted in the local workspace (git-ignored). Copy the relevant parts into files/playwright/playwright.spec.js and adapt them.
Development Procedure 📋
This page owns framework and runner mechanics only. The procedure for writing, deploying, and reviewing a role-local spec lives in the dedicated SPOTs:
- Agent
playwright.spec.js: authoring procedure and live review. - Compose Loop: baseline deploy, redeploy rules, Certificate Authority trust, Inspect-before-redeploy.
- Playwright Spec Loop: spec-only inner loop against the already-running stack.
playwright.spec.js(role file rules): what the spec MUST contain, including logged-out final state and console cleanliness.