`process.env.X || <value>` is a second source for something the deployment
already renders, and the two drift without a sound. playwright.config.js read
`process.env.PLAYWRIGHT_ACTION_TIMEOUT || 30_000` while nothing ever set that
variable, so every run used the unscaled 30 s and no failure said so.
Normalising an absent variable stays allowed, because it substitutes nothing:
`|| ""`, `|| '[]'`, `|| false`, `|| 0` and falling through to another
`process.env` entry only let the caller test absence itself.
The six sites the rule found:
n8n and flowise built a Postgres port from `|| '5432'` while env.j2 renders it
from `lookup('database', application_id, 'port')`. Node throws
ERR_SOCKET_BAD_PORT on the resulting NaN, so an unfed port now names itself
instead of connecting somewhere plausible.
The bluesky broker did the same with BROKER_PORT, which compose.yml.j2 renders
unconditionally. Its INSECURE_TLS default was `"false"`, which is what `""`
already evaluates to.
matomo defaulted MATOMO_TRACKING_SCOPE to "sub"; the only comparison is against
"root", so `""` takes the identical branch.
moodle and simpleicons defaulted a name their playwright.env.j2 always renders.
simpleicons already asserts the value is truthy; moodle now fails on the scope
it cannot find rather than on a name nobody configured.
Four values the code itself owns keep a default under `// nocheck: env-default`:
PDS_INTERNAL_URL (which falls through to requireEnv, not to a literal),
HANDOFF_COOKIE_NAME, HANDOFF_COOKIE_MAX_AGE and PASSWORD_SALT_HASH_ROUNDS. No
role renders any of them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a per-deployment Matomo tracking scope, defaulting to per-subdomain:
- services.matomo.tracking.scope 'sub' (default) registers one Matomo site per
consumer subdomain with an isolated cookie; 'root' keeps the prior behaviour
of one shared site per registrable base domain with cross-subdomain linking.
- sys-front-inj-matomo derives matomo_site_domain (full host vs registrable
base) and the shared matomo_site_url from it; getSitesId / addSite and the
tracking snippet (setCookieDomain / setDomains / enableCrossDomainLinking) all
follow the scope.
- The matomo Playwright env emits MATOMO_TRACKING_SCOPE; the SitesManager test
matches the consumer's full host in 'sub' and its base domain in 'root'.
- variants.yml variant 0 sets scope=root so the matrix exercises both flavours
(every other deploy runs the sub default).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Break the single 457-line playwright.spec.js into topic files discovered by the
harness testMatch "**/*.@(spec|test).js", mirroring the pihole / gitea layout:
- _shared.js env vars, the setup hook, loginAsAdmin, attachDiagnostics,
hostOf / baseDomainOf and the MATOMO_TARGET_ROLES_JSON list
- playwright.spec.js CSP + canonical-domain smoke (filename kept; harness asserts it)
- auth.spec.js admin login/logout and biber denied at the admin surface
- tracking.spec.js SitesManager site registration and per-consumer _paq injection
- persona-flows.spec.js guest / biber / admin universal-logout flows
Test logic is unchanged: same titles, selectors, regexes, timeouts and
assertions (verified by title parity and an adversarial diff against HEAD). The
only behavioural delta is that the extracted loginAsAdmin waits for the username
field to be visible before filling, which the SitesManager login previously
skipped (hardening only). The duplicated admin-login form-fill and the old
beforeEach are now the shared loginAsAdmin and setupMatomoPage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>