Files
computer-playbook/docs/contributing/actions/testing/playwright.md
T
kevinveenbirkenbachandClaude Opus 5.5 c9e7f950d9 Merge branch 'feature/updater' into feature/store-i18n
68 conflicts, resolved per hunk; resolutions agreed with the Updater
session were applied as agreed:

- svc-db-postgres (superuser password script and core) and
  sys-svc-rdbms realign_dedicated_db: updater's argv/stdin redesign with
  expand_argument_vars: false, nested into store-i18n's block structure.
- svc-db-redis 01_init: store-i18n's ACL password on stdin.
- sys-svc-proxy forwarded.conf.j2: store-i18n's handover-aware file,
  without the forwarded_base include (Host and X-Forwarded-Proto would
  be sent twice); forwarded_base serves the inline consumers, e.g.
  web-app-seaweedfs nginx.conf.j2.
- basic.conf.j2 and web-app-openproject proxy.conf.j2: issuer redirect,
  then handover, else updater's location/sso_acl.conf.j2; the
  server-level webserver_extra_configuration hook stays.
- web-app-keycloak: updater's admin-REST settings step and renumbered
  update steps; update/01_client.yml removed; store-i18n's design step
  and LDAP block kept; a73435cc9d arrived through rename detection.
- web-app-nextcloud OpenProject provisioning: updater's rewrite, all
  credentials on the Ruby runner's stdin.
- web-app-semaphore: store-i18n's canonical web root (a1d7835863).
- Role READMEs: no generated Cosmos/Architecture/Quick Setup/Credits
  sections; desk-* paths resolved to the dsk-* renames.

Integration fixes for what merged cleanly but did not fit together:

- web-app-matrix ansible vars: drop updater's second
  matrix_client_element_configuration_extension_json, which silently
  overrode store-i18n's design-aware value.
- url.origin instead of a stripped url.base in 19 places store-i18n
  added since 44aaa68924, same lookup targets (the double-slash lint).
- Package batch (18ea75049e, cb4d340de9): installs the deploy-start
  batch covers are removed (caffeine, cursor, blender, npm in dsk-gnt-*,
  with shared references), the rest carry batch: false and a nocheck
  with the verified reason (PyPI fallback, before the batch, vendor
  repository, configured flavor only, addon gate); dev-npm references
  the root npm id.
- sys-token-store write_many.yml: store-i18n's change-gated block and
  IS_SWARM_MODE.
- web-app-openproject: IS_COMPOSE_MODE in 02_migrate/run.yml; the
  seeded-admin lock (ccbf8f168d) ported into 04_admin/provision.yml and
  covered by the admin-state digest.
- Bucket policy (3b4ac40df5 and follow-ups) moved to web-svc-seaweedfs,
  where store-i18n's object store lives; the S3 admin secret travels on
  stdin instead of a task-level environment.
- act workflow.sh: no Exception-labelled comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 02:28:16 +01:00

7.8 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-playwright role.
  • There is no standalone make target 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.css trigger), see the Triggers section of playwright.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. The rendered package.json also installs @axe-core/playwright at playwright.axe_version from the same file, so a spec MAY run WCAG checks with const { default: AxeBuilder } = require("@axe-core/playwright");.

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:

  1. stages the test project
  2. renders .env from playwright.env.j2
  3. copies files/playwright/playwright.spec.js into the project as tests/playwright.spec.js
  4. renders the central templates/package.json.j2 and copies the central files/playwright.config.js into the staging dir
  5. waits until the app is reachable
  6. 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:

  1. The host environment variable INFINITO_PLAYWRIGHT_KEEP (set on the host shell, by act --env INFINITO_PLAYWRIGHT_KEEP=true, or by the GitHub Actions repository variable forwarded into each test-deploy-*.yml workflow as INFINITO_PLAYWRIGHT_KEEP: ${{ vars.INFINITO_PLAYWRIGHT_KEEP }}).
  2. roles/test-e2e-playwright/defaults/main.yml reads the host value into test_e2e_playwright_keep via lookup('env', 'INFINITO_PLAYWRIGHT_KEEP'). Inventory MAY override the role variable per environment in group_vars or host_vars.
  3. roles/test-e2e-playwright/tasks/03_run_pass.yml forwards the resolved value into the Playwright container as -e INFINITO_PLAYWRIGHT_KEEP={{ test_e2e_playwright_keep }}.
  4. roles/test-e2e-playwright/files/playwright.config.js switches trace, screenshot, and video to on when the container value is true. It otherwise leaves the failure-only defaults (retain-on-failure for trace and video, only-on-failure for screenshot).

Set the variable for the contexts you need:

  • Local deploy: prefix every make compose-deploy invocation with INFINITO_PLAYWRIGHT_KEEP=true.
  • Local act runs: append --env INFINITO_PLAYWRIGHT_KEEP=true to the act command.
  • CI: set the INFINITO_PLAYWRIGHT_KEEP repository variable. For the GitHub click path see CI Configuration.
  • Inventory pin: set test_e2e_playwright_keep: "true" in group_vars or host_vars of 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: