mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix/dev-python/python-upper-bound
25
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b582846ab3 |
fix(test-e2e-playwright): make the Keycloak redirect and login-control checks able to fail
performKeycloakLogin polled the page URL with toContain(canonicalDomain).
On the Keycloak auth page the app domain already sits in redirect_uri,
so the poll was satisfied before the login had completed. The denial
path and the biber/admin iframe checks used the same substring test.
All of them now compare hostnameOf(url) with the canonical domain, which
is a bare hostname (env.js reads it with /\.onion$/).
LOGIN_CONTROL_NAME never matched a login link that carries an
icon-font glyph: Playwright's accessible name includes the private-use
character, and the pattern only allowed whitespace before the text. The
post-login waitFor({state: "hidden"}) therefore passed at once, even
before oidc.js had swapped the link. The pattern now admits \p{Co}
before the text, so that wait really waits for the swap.
dom.test.js pins both: glyph-prefixed login controls match while Logout
and Accessories do not, an IdP URL carrying the app domain in
redirect_uri is rejected, a look-alike suffix is rejected, and
about:blank yields an empty host.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
b97cf88881 |
fix(e2e): let the admin persona wait out the OIDC popup handoff
Playwright declares the timeout option on locator.isVisible() and isHidden() as deprecated and ignored - both return immediately. |
||
|
|
a0ef59049d |
fix(e2e): let biber's authenticated-surface probe actually wait
236578d3b converted this exact fallback in admin.js and left its twin in
biber.js untouched. Both personas do the same thing: when the Keycloak round
trip did not complete cleanly, fall back to looking for an authentication
marker on the page, and budget fifteen seconds for it. isVisible does not
spend that budget -- Playwright ignores the timeout option there and answers
from the current DOM.
The cost is measured, not argued. In run 31658241728 the EspoCRM 2 trace shows
the same API asked for ten seconds and returning in 0.20 s, while the network
log has the SPA finishing its boot at 0.66 s and the fallback navigations
firing at 0.91 s. A surface that renders one second late reads as
unauthenticated.
waitFor spends the budget only when the marker is absent, which is precisely
the case that is answered wrongly today; when the marker is there it returns as
soon as it appears. So a passing run pays nothing and a failing run pays the
fifteen seconds it was always meant to pay. admin.js has paid them since
236578d3b.
Ten further isVisible({timeout}) call sites in this directory were reviewed and
deliberately left alone: they ask whether something is visible right now
(passwordStillVisible, loginStillVisible, the frame scans, clickFirstVisible),
and clickFirstVisible would lose five seconds per candidate if it waited.
Not verified by a run: the mechanism is proven from the EspoCRM trace and the
parity with admin.js, not from an observed biber probe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
e96c752549 |
Merge fix/playwright/env-template-gate into feature/svc-net-tor
75 commits: the socialhome role reimplemented against the project it actually
deploys, native-login specs for funkwhale and shopware, a window.open IdP
handoff and a settings-link fallback in the shared persona helpers, a swarm
converge budget that gives up on a terminal task, and a long tail of role
fixes.
Eleven conflicts. The shared persona helpers (keycloak.js, logout.js) take the
incoming structure — they gained real behaviour this branch never had — with
this branch's resolveTimeout convention re-applied over it and
confirmKeycloakLogoutIfPrompted re-attached at every return point; without it
the Keycloak SSO session outlives the logout. playwright.config.js keeps this
branch's version: both sides set actionTimeout, but ours routes through an env
var and 15s is too short behind an onion. socialhome's spec takes the incoming
content —
|
||
|
|
d34666c3a8 |
fix(e2e): follow a window.open IdP handoff in the shared OIDC login helper
clickOidcLoginLink watched only the opener. EspoCRM hands off to Keycloak via window.open, so the opener correctly never navigates and the helper abandoned a fully rendered Keycloak form in the child window; the persona then died 45s later at the generic 'did NOT reach an authenticated surface' gate. The trace shows BrowserContext.page with an openerPageId, and the click itself succeeded - 'element is visible, enabled and stable' with no error on the after-event. The [disabled] Sign in button in the snapshot is EspoCRM's own signIn() disableForm(), set after the click, not before it. The helper now races the opener against the popup with Promise.any over branches that only fulfil once a page reaches openid-connect/auth, and returns that page; biber and admin pass it to performKeycloakLogin. Promise.any rather than race: EspoCRM closes the popup after it authenticates the opener by XHR, and a race would let that losing branch resolve null and truncate the opener's budget. Measured on a stub harness: popup closed at 500ms with the opener reaching the IdP at 1200ms returns the opener, where a race returns null. The 15s popup window matches the idiom already used in the nextcloud addon specs; the 2s window this replaces would have returned null for any adapter that runs PKCE or discovery before window.open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a94a7ec73e |
fix(e2e): anchor the login-control name in the post-login gates
The gates that assert no sign-in affordance survived the OIDC round-trip matched /log\s*in|sign\s*in|sso/i unanchored, so the pattern hit ordinary navigation entries of an already authenticated page as substrings: Accessories, Login Enabled, Blog inbox, Asset Login History. Any of them made the gate report 'OIDC login did NOT establish a session' while the login had in fact succeeded. Snipe-IT 2 of run 31340965955 failed exactly that way. LOGIN_CONTROL_NAME anchors both ends and still admits the 'with ...' suffix, so 'Sign in with Keycloak' keeps matching and the pattern is not narrowed into matching nothing. The pre-login fallbacks in admin.js:58 and biber.js:79 stay deliberately broad: they hunt for a sign-in control on the UNAUTHENTICATED landing page after the anchored attempt misses, where no authenticated menu exists to collide with. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2009d2c315 |
fix(test-e2e-playwright): stop the persona harness mistaking login controls for account menus
A Sign-in button matched ACCOUNT_MENU_NAME_RE, so inAppLogout clicked it as a menu trigger, and a username field matched the auth marker's data-testid rule, so an unauthenticated login page counted as an authenticated surface. The second one is why a broken auth chain surfaced as 'no in-app logout control reachable'. Traces from CI run 31195451847 show the sign-in button clicked at CALL@123 and CALL@137, the marker returning true at ret@85, and the match resolving to input#user_login at @145 with no POST to /users/sign_in anywhere in the network log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
185e899ba3 |
fix(e2e): wait out async OIDC exchange in biber check
The login-still-visible probe sampled 2s right after the redirect, before keycloak-js finished the code→token exchange over Tor; wait Tor-scaled for the login control to disappear instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
edb96e9ae3 |
feat(dashboard): make keycloak-js login work over Tor onion
Chromium never treats plaintext .onion origins as a secure context, so keycloak-js (PKCE via crypto.subtle) could not run. A custom Playwright browser fixture launches Firefox with the Tor-Browser prefs (dom.securecontext.allowlist_onions, origin/mixed-content handling) for onion targets and keeps Chromium for clearnet; all dashboard specs share it via the register pattern. keycloak-js is now vendored from the deployment's own CDN as an ES module (jsdelivr is unreachable over Tor) with CORS opened on the CDN proxy. Personas and the oidc-login spec wait for adapter readiness and navbar settle before driving login/logout, which the async code->token exchange needs over Tor latency. Verified: full deploy matrix green (onion 14/14 twice, clearnet pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
05c082fc14 |
test(e2e/personas): run biber/admin journeys over onion with adaptive timeouts
keycloak.js/logout.js wrap every wait in resolveTimeout so Tor's higher latency does not abort the OIDC adapter/logout waits. biber.js/admin.js drop the onion skip (personas now execute over onion instead of being skipped) and add a sharpen guard: a completed OIDC round-trip that still shows a Login control fails explicitly as a login failure, not a misleading 'no logout control'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
24d4ab8e4a |
refactor(sso)\!: collapse oauth2 + oidc into unified services.sso block (req 21)
Implements docs/requirements/021-sso-flavor-migration.md atomically:
* Schema: every role's meta/services.yml replaces the parallel
`oauth2:` / `oidc:` top-level blocks with a single `sso:` block
carrying `enabled`, `shared`, and a `flavor` discriminator
(`oidc` | `oauth2` | `saml`; default `oidc`). Flavor-specific
sub-keys live under `sso.<flavor>.*` (`sso.oauth2.{origin,acl,
allowed_groups}`, `sso.oidc.plugin`).
* Provider role: roles/web-app-oauth2-proxy/ is deleted in its
entirety; its 5 sidecar templates move under
roles/web-app-keycloak/templates/sso_proxy/, and the per-consumer
rendering task lives at roles/web-app-keycloak/tasks/sso_proxy.yml.
web-app-keycloak now declares `provides: sso`.
* Runtime: new resolver utils/roles/applications/services/sso.py
centralises the SSO predicates (`is_enabled`, `is_proxy_gated`,
`is_oidc_native`) and the oauth2 sub-values
(`oauth2_origin_{host,port}`, `oauth2_acl`, `oauth2_allowed_groups`).
Three lookup plugins consume the resolver: `sso` (single-app
predicate access), `sso_proxy_consumers` (enumerates oauth2-flavored
consumers), `sso_oidc_plugin` (renamed from `oidc_flavor`, Nextcloud
plugin selector). sys-stk-backend, sys-svc-compose, sys-svc-proxy
and plugins/filter/compose_volumes.py all gate on
`lookup('sso', ..., 'is_proxy_gated')` instead of manually combining
`enabled` and `flavor`.
* Identifier renames repo-wide: `services.<entity>.ports.local.oauth2`
→ `ports.local.sso`; `PORT_BANDS.local.oauth2` → `PORT_BANDS.local.sso`;
`OAUTH2_PROXY_*` env/Jinja vars → `SSO_PROXY_*`;
`oauth2_proxy_cookie_secret` → `sso_proxy_cookie_secret`;
`OAUTH2_SERVICE_ENABLED` + `OIDC_SERVICE_ENABLED` collapse into
`SSO_SERVICE_ENABLED`; Playwright gating helpers
(isServiceEnabled/skipUnlessServiceEnabled/requireService/safeIsEnabled/
safeSkipUnlessEnabled) move from `oidc`/`oauth2` to `sso`.
* Dual-block resolution: per req's Background-Table, web-app-bookwyrm
is `flavor: oauth2`; web-app-gitea and web-app-friendica keep
`flavor: oauth2` with their existing `oauth2.acl.blacklist`
(/user/login) and `oauth2.acl.whitelist` (federation + discovery
endpoints) — preserving the operative oauth2-proxy gating.
* Tests: the two mutual-exclusion guards
(tests/integration/iam/oauth2_oidc/test_mutual_exclusive.py and
test_acl_mutual_exclusive.py) are deleted per Decision #10;
test_oauth2_contract.py is renamed and rewritten to
test_sso_contract.py, asserting that `flavor: oauth2` roles carry
`oauth2.origin.{host,port}` plus `ports.local.sso`; test_sso.py
(web-app integration) now requires the unified `sso:` block with an
explicit flavor or a `# nocheck: sso` opt-out; new
tests/lint/repository/no_legacy_sso_paths/ guards the source tree
against re-introducing the legacy strings.
* Docs: req 21 itself has every AC checked off, and reqs 006, 008,
009, 013, 015, 016, 019, 020 plus 10+ role READMEs and
docs/contributing/actions/testing/suppression.md are updated to the
unified SSO naming.
Empirically validated end-to-end against the validation app set
(web-app-keycloak, web-app-nextcloud, web-app-bookwyrm, web-app-gitea,
web-app-friendica, web-app-dashboard) in matrix variants 0 and 1; v2
also passes the 4 target roles' Playwright after a documented
pre-existing KC_HOSTNAME-drift workaround (separate concern, not
caused by this refactor).
BREAKING CHANGE: every consumer of `services.oauth2.*`,
`services.oidc.*`, `OAUTH2_PROXY_*`, `OAUTH2_SERVICE_ENABLED`,
`OIDC_SERVICE_ENABLED`, `oauth2_proxy_cookie_secret`,
`oauth2_proxy_application_id`, `ports.local.oauth2`, the
`web-app-oauth2-proxy` role, or the legacy gating helper service names
(`oidc`/`oauth2`) must migrate to the unified `sso` shape documented
in docs/requirements/021-sso-flavor-migration.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
162fe71634 |
fix(test-e2e-playwright,web-app-mailu): replace inline persona skip-sniffing with the PERSONA_*_BLOCKED contract
Commit
|
||
|
|
1f6b5f8942 |
fix(test-e2e-playwright): gate shared personas on dashboard + oidc service flags
CI run 26089179898 job 76714878217 (Deploy web-app-bluesky, variant 1)
failed at the mailu Playwright `guest: public-landing → auth chain →
never authenticated` test with 60s timeout × 3 retries. Root cause is
two layered defects:
1. Between Round 1 and Round 2 of the matrix deploy the fresh-purge
sweep removes the web-app-dashboard vhost and container (mandate-
fresh-purged on first contact with a new VARIANT, see commit
|
||
|
|
c2c532c8e6 |
refactor(playwright): centralize dashboard-tile mechanics in web-app-dashboard SPOT; move specs into files/playwright/ subdir
Architecture
============
- New SPOT helper `roles/web-app-dashboard/files/playwright/dashboard-card-flow.js` (`runDashboardCardScenario(page, context, target)`): per `roles_with_service('dashboard')` target, asserts tile visible → click loads in `#main iframe` (outer URL gets `?iframe=` + iframe src matches canonical_domain) → header "Tab" button pops the embed into a fresh browser tab.
- `web-app-dashboard/files/playwright/playwright.spec.js` parametrised loop calls the helper for every `DASHBOARD_TARGET_ROLES_JSON` entry — the single owner of dashboard-tile mechanics across the whole codebase.
Spec relocation
===============
- 71 `roles/<role>/files/playwright.spec.js` → `roles/<role>/files/playwright/playwright.spec.js` (`git mv`, history preserved). odoo + pixelfed show as D+A in `git status` because of substantial content rewrites; coverage retained.
- New SPOT constant `ROLE_FILE_PLAYWRIGHT_SPEC` in `utils/roles/mapping.py`, registered as optional on `ROLE_TYPE_APPLICATION` (disallowed elsewhere, mirrors `ROLE_FILE_META_VARIANTS` pattern).
- `roles/test-e2e-playwright/tasks/run_one.yml`: staging task replaced with `with_fileglob` over `files/playwright/*.js` — roles can ship companion helpers (like `dashboard-card-flow.js`) next to the spec without further wiring.
Spec migrations (no more dashboard-iframe path in app specs)
============================================================
13 specs rewritten so the persona/SSO journey starts at the app URL directly, never through the dashboard tile or iframe:
* fider, mailu, mattermost, nextcloud, odoo, taiga: iframe-wrap removed (no more `frameLocator("#main iframe")`)
* bigbluebutton, discourse, kix, matrix, openwebui, peertube: dashboard goto preamble removed
* pixelfed: ~270 LOC of iframe-aware helpers/login/logout collapsed to direct-URL flow
Title cleanup
=============
- 64 specs: `"<persona>: dashboard → app → universal logout"` / `"administrator: dashboard → prometheus → app → universal logout"` → `"<persona>: app → universal logout"`.
- 13 specs: `"<persona>: dashboard to <app> ..."` → `"<persona>: <app> ..."`.
Dead-code cleanup
=================
- `dashboardBaseUrl` consts + beforeEach assertions removed from 11 specs (no functional uses; personas helper handles env validation).
- `DASHBOARD_BASE_URL=` env-template lines removed from fider, mattermost, odoo, taiga, pixelfed, kix.
- `DASHBOARD_SERVICE_ENABLED=` env-template lines removed (pixelfed retained for the still-gated spec).
- Stale "loads inside the iframe" / "FrameLocator parameter" comments cleaned in fider, odoo, prometheus, nextcloud.
Tooling
=======
- 5 lint Python files + 1 integration test now use `from utils.roles.mapping import ROLE_FILE_PLAYWRIGHT_SPEC`.
- `eslint.config.js` glob pattern + `roles/test-e2e-playwright/meta/main.yml` description + `roles/test-e2e-playwright/README.md` updated to new path.
- 25 markdown/docstring references in `docs/`, `roles/**/README.md`, `roles/**/TODO.md`, persona-helper docstrings updated to new path.
- `docs/contributing/artefact/files/role/playwright.specs.js.md`: file-placement section now documents the `files/playwright/` convention + `ROLE_FILE_PLAYWRIGHT_SPEC` SPOT constant; dashboard SPOT delegation explicit at 3 places.
Net diff: 124 files, +1142/-1564 (−422 LOC).
Integration suite: 183/183 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4481976fb9 |
fix(playwright): two-pass OIDC Login link + PERSONA_BLOCKED for nextcloud/opencloud/minio
Two-pass Login link resolution in `personas/utils/keycloak.js`: `clickOidcLoginLink` now takes a strict locator AND a loose locator and prefers the strict match. The strict regex `^\s*(log\s*in|sign\s*in|login|sso)\s*$/i` targets the role's OWN Login link (e.g. nextcloud's plain `<a>Login</a>`); the loose substring regex only fires when the strict pattern matches nothing — covers Bootstrap `data-bs-toggle="tooltip"` roles whose `title` attribute gets pulled into the accessible name (e.g. dashboard's "Login to Infinito.Nexus on infinito.example"). Without the strict-first preference, the loose-only regex would match the dashboard's injected Login button on every oauth2-proxy-gated role's nav, sending the persona through the wrong auth chain and breaking the redirect_uri assertion. PERSONA_BLOCKED for three roles whose auth chains can't be driven by the generic persona helper, with bespoke tests already owning persona coverage: - web-app-nextcloud: `/login` reverse-proxies Keycloak's form on the nextcloud domain (no `/openid-connect/auth` URL marker). Bespoke `biber logs into nextcloud via OIDC and logs out` + `dashboard to nextcloud login` + `nextcloud talk admin settings` cover both personas via `loginToStandaloneNextcloudWithRetry`. - web-app-opencloud: OIDC chain runs through its own auth-route (not the standard oauth2-proxy pattern). Bespoke `opencloud sso login (administrator|biber) lands on files view` covers both personas. - web-app-minio: STS via OIDC, biber has no minio account. Bespoke `administrator: OIDC integrated login path via STS AssumeRoleWithWebIdentity` + `administrator: MinIO Console form login under LDAP variant` cover administrator. Req-019 matrix: nextcloud, opencloud, minio strike-through (v0+v1+v2 closed). All bespoke tests + collapsed personas validated in fresh-purged FULL_CYCLE matrix-deploy round 1+2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
94a80db92a |
fix(web-app-dashboard): close req-019 — silent-check-sso postMessage + PKCE-safe persona Login + variant=1 gates
Root causes diagnosed inside the running container per
docs/agents/action/iteration/playwright.md, fixed in repo, validated
via inner-loop rerun-spec.sh, and finally re-deployed via FULL matrix
variant=0 (all dashboard tests green) and reuse-kept variant=1
(persona/asset/login-swap tests collapse cleanly via env + test.skip
gates, 2 passed + 5 skipped, failed=0).
- templates/nginx/sso.html.conf.j2: serve the standard Keycloak
silent-check-sso body — `parent.postMessage(location.href,
location.origin)`. Without that script, the hidden iframe never
reports its URL back to the parent and `keycloak.init()` hangs
forever; the role's OIDC adapter then never wires its click
interceptor and the persona helper races into the static `href`
without PKCE state.
- templates/javascript/oidc.js.j2: set `window.__oidcLoginReady = true`
after `wireLoginLogoutClicks()` so persona helpers can detect that
the JS-wrapped click handler is in place BEFORE clicking. The flag
name SPOT is `OIDC_LOGIN_READY_FLAG` in
`roles/test-e2e-playwright/files/personas/utils/keycloak.js`.
- personas/utils/keycloak.js: new `clickOidcLoginLink(page, loginLink)`
helper — single source of truth for the OIDC-link click ritual
(waitFor visible → waitFor `__oidcLoginReady` → click → waitForURL
`/openid-connect/auth/`). Replaces the inline duplicated block in
biber.js + admin.js, and removes the static-href direct-nav fallback
that broke PKCE-enforced clients silently.
- personas/biber.js + admin.js: delegate to `clickOidcLoginLink` and
drop the `^...$` regex anchors on the Login link locator. Bootstrap
`data-bs-toggle="tooltip"` moves the link's `title` into
`data-bs-original-title` and the accessibility tree pulls THAT into
the link's accessible name (e.g. "Login to Infinito.Nexus on
infinito.example") — the anchored regex matched zero links. The
`\s*` between `log` and `in` keeps the substring pattern from
matching `Logout`/`logoff`.
- templates/playwright.env.j2: render
`PERSONA_{BIBER,ADMINISTRATOR}_BLOCKED` from
`services.oidc.enabled`. Variant=1 disables every shared service —
with OIDC off there is no Login link to click, so the persona
scenarios MUST collapse to a clean skip instead of failing loudly.
- files/playwright.spec.js: add `test.skip(\!cdnEnabled || \!logoutEnabled, ...)`
on the asset-loading test and `test.skip(\!oidcEnabled, ...)` on the
Login → Account swap test. The bespoke tile-reachability loop
already skips cleanly when `DASHBOARD_TARGET_ROLES_JSON` is empty,
so no further gate needed there.
- TODO.md: removed. The autonomy escape clause is satisfied.
- docs/requirements/019-playwright-meta-services-parity.md: strike
through `web-app-dashboard` row (v0+v1 closed) and document the
silent-check-sso / `__oidcLoginReady` / Bootstrap tooltip
contracts inline in the row's notes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
2c0d5fb9fc |
fix(playwright/personas): treat Keycloak round-trip as auth proof + wait for injected logout
* Set a `keycloakRoundTripCompleted` flag once `performKeycloakLogin` has returned and the page is on the role's canonical hostname. oauth2-proxy-gated roles whose upstream UI ships no Account/Logout menu (Prometheus, status pages, raw upstream surfaces) now satisfy the persona auth-marker without a probe-able in-app control. The visible-marker poll still runs for roles that DO surface one. * Add `[data-injected-logout]` as the first logout candidate so the shared `inAppLogout` helper picks up the per-role JS-injected fallback button (e.g. the Prometheus navbar patch) before falling through to the regex-driven candidates. * Replace the implicit "snapshot isVisible" gate at the top of `inAppLogout` with a `waitForAnyLogoutCandidate` poll that pings the candidate set for up to 10 s. Required because the universal / per-role logout helpers inject their button asynchronously after DOMContentLoaded plus a MutationObserver settle; the previous synchronous check failed before the button could mount. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9763388229 |
fix(playwright/personas): harden auth chain + logout discovery
* Wait for the role's Login link with `Locator.waitFor` instead of the
snapshot-only `isVisible` — `isVisible({timeout})` only governs locator
resolution and never polls for visibility, so an OIDC-driven UI that
injects the Login link asynchronously (e.g. dashboard's `oidc.js`
fetched at runtime from the CDN) was treated as logged-out and the
helper returned in milliseconds without ever clicking the link.
* Once the link is visible, prefer a direct `page.goto(href)` to the
embedded openid-connect/auth URL when the link exposes one; fall back
to a real click plus `page.waitForURL(/openid-connect\/auth/)` so a
JS-handler-intercepted click (keycloak adapter, single-page-app shell)
still drives the test through Keycloak.
* Restrict the post-login URL fallback to nested frames. The main
frame can park on the role's canonical domain without an authenticated
session (the role's auth chain failed silently), so accepting the main
frame URL as proof of authentication produced false positives. The
iframe fallback still covers the legitimate "app surface rendered via
embed" case.
* Broaden the auth marker so role=link account/profile/user-menu
controls and `aria-label='account'`, `aria-haspopup`, Bootstrap
`data-bs-toggle='dropdown'`, and a `href*='logout|end_session'` link
count as the post-login surface signal. Roles that hide the auth
marker behind a closed dropdown still surface a probe-able anchor.
* Rebuild `inAppLogout` to iterate every visible menu trigger (Bootstrap
dropdowns, `.user-menu-toggle`, `data-testid='user'`, ARIA-popup
controls), not just the first match. After each click the helper
polls for logout/menuitem/link candidates inside the dropdown,
closes the menu again before trying the next trigger, and falls back
to a top-level `a[href*='logout']` so static-only logout links work
even when no dropdown wraps them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f6f4236ffd |
fix(playwright/personas): wire PERSONA_*_BLOCKED + handle oauth2-proxy auth chain
Five fixes against the matomo deploy CI failure mode: * The persona helpers ([admin.js], [biber.js]) now perform the Keycloak login on either auth shape — oauth2-proxy gate (the goto is intercepted and the page lands directly on `openid-connect/auth`) AND in-app OIDC plugin (Login link → click → redirect). Previously the Keycloak step was gated on `oidcEnabled`, which left oauth2-proxy-only roles like prometheus with no auth path. The login-link regex also tolerates leading / trailing whitespace (`/^\s*(log\s*in|...)\s*$/i`), unblocking the dashboard's `" Login"` link whose accessible name carries an icon-induced leading space. * `roles/web-app-keycloak/templates/playwright.env.j2`: declare `PERSONA_BIBER_BLOCKED=true` and `PERSONA_ADMINISTRATOR_BLOCKED=true`. The persona stubs in the keycloak spec exist purely to satisfy the persona-naming lint; Keycloak IS the auth provider so neither persona has an in-role app surface. The bespoke master-realm / normal-realm tests already cover the contract. * `roles/web-app-matomo/templates/playwright.env.j2`: same — matomo's local login form is not OIDC / oauth2-proxy, so the generic helper has no useful flow. The bespoke "matomo administrator" test covers the admin journey. * `roles/web-app-prometheus/templates/playwright.env.j2`: only `PERSONA_BIBER_BLOCKED=true`. Prometheus is admin-only via oauth2-proxy with `allowed_groups: web-app-prometheus-administrator`; biber's session is denied at the proxy and biber has no in-app surface to drive a logout from. Administrator goes through the standard oauth2-proxy → Keycloak chain. Also rendered the missing `PROMETHEUS_BASE_URL`, `CANONICAL_DOMAIN`, and pointed `APP_BASE_URL` at prometheus itself so the persona helper drives the role under test (not the dashboard). * `roles/web-app-prometheus/files/playwright.spec.js`: read `process.env.CANONICAL_DOMAIN` directly so the env-keys-used lint recognises the new env entry as consumed. The 019 matrix note for prometheus is updated to describe the biber-only opt-out and the admin's oauth2-proxy chain. Out of scope: the matomo CSP biber-deny test still fails because `roles/web-app-matomo/meta/services.yml` has `oauth2.enabled: false` (TODO at line 12 — wire matomo's oauth2-proxy gate). That is a deploy-config change, not a test fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c55ad69c41 |
refactor: drop requirement-file references from code, add doc rule + lint
Strip every `req <NNN>`, `req-<NNN>`, `Requirement <NNN>`, `requirement-<NNN>`, and `docs/requirements/<NNN>-...md` mention from non-Markdown source files. Comments anchored to requirement numbers rot when requirements are renumbered or rewritten and conflate present-state behaviour (which the comment should describe) with the audit trail of how the code got there (which belongs in commit messages, PR descriptions, and the requirement file itself). * Document the rule in `docs/contributing/documentation.md` under "Comments". Code MUST describe the *why* in present-state language; contract enforcement lives in tests, not folklore. * Clean 254 source files across `roles/`, `tests/`, `plugins/`, `cli/`, `scripts/`, `utils/`, `group_vars/`, and the repo-root `eslint.config.js`. Pure comment / docstring / log-message edits; no behavioural change. * Add `tests/lint/repository/test_no_req_references_in_code.py` to enforce the rule going forward across `.js`, `.py`, `.yml`, `.yaml`, `.j2`, and `.sh` files. The legacy `test_no_stale_requirement_refs.py` is exempt as the only file whose purpose is to detect these very tokens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
db2a2d9638 |
refactor(playwright): move cross-service tests to provider specs
Cross-service surface assertions for dashboard, prometheus and matomo
no longer run in every consumer role's persona. They are owned by the
provider's own spec, parameterised once over a `<NAME>_TARGET_ROLES_JSON`
manifest rendered into the provider's `templates/playwright.env.j2` via
the new `lookup('roles_with_service', '<svc>')` Ansible lookup.
Changes
-------
- New `plugins/lookup/roles_with_service.py` enumerates roles that
declare `services.<svc>.{enabled, shared}` truthy in the merged
applications config, returning `[{id, canonical_domain}, …]`.
- `roles/web-app-{dashboard,prometheus,matomo}/files/playwright.spec.js`
parse `<NAME>_TARGET_ROLES_JSON` and parameterise the per-consumer
assertion (dashboard tile reachability, prometheus `up=1`, matomo
SitesManager registration). The matomo spec gains an explicit
biber-denial test (request body validated against admin DOM markers
per the body-marker rule); the prometheus admin-reach test drops
the dashboard-iframe path in favour of direct goto.
- Persona helpers `roles/test-e2e-playwright/files/personas/{admin,
biber,guest}.js` enter at `${APP_BASE_URL}/` directly. The
`clickRoleTileFromDashboard` step and the `assertPrometheus*` /
`assertMatomo*` cross-service probes are gone; `utils/{dashboard,
prometheus,matomo}.js` are deleted. `utils/csp.js` switches to
literal `process.env.<KEY>` reads so the env-keys-used parity guard
recognises matomo's CSP injector consumption.
- 42 consumer-role `templates/playwright.env.j2` templates drop
`DASHBOARD_BASE_URL`, `DASHBOARD_SERVICE_ENABLED`, `PROMETHEUS_BASE_URL`,
`PROMETHEUS_SERVICE_ENABLED`. `MATOMO_BASE_URL` and
`MATOMO_SERVICE_ENABLED` stay because the per-role CSP injection
helper still gates on matomo's tracker host.
- `tests/integration/roles/test_playwright_env_services_match.py`
globally exempts `dashboard` and `prometheus` from the
services.yml-to-env parity rule.
- `tests/unit/plugins/lookup/test_roles_with_service.py` adds 14
unit tests for the lookup, including an explicit-contract test
that pins the over-inclusive behaviour on raw Jinja strings (the
lookup expects a templar-rendered payload).
- Docs: `docs/requirements/019-playwright-meta-services-parity.md`
rewords Rules 1, 3, 12 and adds Rule 13 describing the SPOT shift.
`docs/contributing/artefact/files/role/playwright.specs.js.md`
reflects the new persona shape and per-service catalogue, and is
cleaned per `docs/contributing/documentation.md` (no SPOT self-
labels, no req-references, em dashes replaced with complete
sentences, multi-clause lines split onto separate lines, broken
anchor `#two-personas-fixed-flow-` corrected).
Verification
------------
- 418 passed / 1 skipped / 0 failed across `tests/lint/`,
`tests/integration/roles/`, `tests/unit/plugins/lookup/`.
- `make lint-javascript`: 0 errors.
- `markdownlint-cli2`: 0 errors on all touched docs.
- `make autoformat`: clean (FURB171 single-item-set inequality fixed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fa39d5522b |
fix(test-e2e-playwright/personas): gate dashboard via safeIsEnabled
`tests/integration/roles/test_playwright_spec_env_gates.py` failed
across 35 roles: every spec that imports `runBiberFlow` /
`runAdminFlow` ships `DASHBOARD_SERVICE_ENABLED=` in its env template,
but the persona helpers consume the dashboard purely via
`process.env.DASHBOARD_BASE_URL` without ever passing the literal
`"dashboard"` to `safeIsEnabled` / `requireService` / friends. The
integration test grep'd for the string-literal call and (correctly)
saw zero matches, so flagged the flag as declared-but-never-gated.
Adds `safeIsEnabled("dashboard");` as a bare-statement gate to
admin.js and biber.js, parallel to the existing `safeIsEnabled
("oauth2"); safeIsEnabled("logout");` lines that already pin those
two flags to the persona surface. The integration test walks every
file under `roles/test-e2e-playwright/files/personas/` once any
persona runner is referenced in the spec, so a single mention covers
all 35 roles in one shot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a35636051e |
feat(playwright/personas): early-skip + URL fallback + drop bogus dashboard gate
Three coupled changes to `runBiberFlow` / `runAdminFlow`:
1. Move `PERSONA_BIBER_BLOCKED` / `PERSONA_ADMINISTRATOR_BLOCKED`
to position 0 of the flow. A role that opts out now short-circuits
the entire dashboard → app → logout journey instead of walking
through the auth chain and tripping the in-app logout step where
a late opt-out check could not catch it.
2. Add a URL-based fallback to the auth-surface detection: when the
selector-based marker (`getByRole("button", { name: /^(log\s*out…)/i
})`) finds no logout / user-menu control on any frame, accept any
nested frame whose URL has settled on the role's canonical domain
(and is NOT on Keycloak / oauth2-proxy denial / about:blank) as
evidence that the persona reached the authenticated surface.
Iframe-wrapped role surfaces (dashboard tile + role app inside)
and SPAs whose logout sits behind icon-only avatar dropdowns then
count as authenticated.
3. Drop `safeSkipUnlessEnabled("dashboard")` from the top of both
flows. The flag is rendered as `<NAME>_SERVICE_ENABLED=` from the
*provider* perspective in `meta/services.yml` (matomo's services
declare `dashboard.enabled: <web-app-dashboard in group_names>`,
meaning "is the dashboard role part of *this* role's stack").
When matomo deploys solo, the flag rendered as `false` even
though the dashboard URL was reachable, and every persona test in
the matrix run silently `-`-skipped. The auth-less collapse check
immediately below (`if (\!dashboardBaseUrl || \!canonicalDomain)
skip`) is the correct gate — a role that legitimately cannot
start the journey has no `DASHBOARD_BASE_URL` rendered.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
672c61e37e |
test(playwright/personas): hard-fail on un-executable persona journeys
The persona-flow runners now fail loudly when biber / administrator cannot reach an authenticated surface on the role under test. The previous runtime auto-detection silently `test.skip(...)`'d, hiding real regressions (broken OIDC mapping, removed logout button, misconfigured oauth2-proxy, drifted UI selectors) behind a green deploy. Roles where the persona legitimately has no access (admin-only roles, auth-provider roles) MUST declare an explicit env opt-out `PERSONA_BIBER_BLOCKED=true` / `PERSONA_ADMINISTRATOR_BLOCKED=true` in `templates/playwright.env.j2` with a documented rationale; without the flag the helper fails the test with a diagnostic naming the last-seen URL. A new lint guard (`test_persona_strict_mode.py`) hard-fails any future `test.skip(...)` in the persona helpers that is not guarded by an allowed env opt-out / auth-less-collapse / service-gate condition, and any `status === 200` branch in the prometheus / matomo deny-check helpers that does not validate role-specific body markers. Req 019 Rule 11 (strict-skip contract) and Rule 12 (200-body validation) plus a matching "Strict failure on un-executable persona cases" section in `playwright.specs.js.md` document the contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d3e5ebe441 |
feat(test-e2e-playwright): add shared persona-flow library + first batch
Introduces roles/test-e2e-playwright/files/personas/ — runGuestFlow,
runBiberFlow, runAdminFlow plus 10 utils (csp, dashboard, env, keycloak,
landing, logout, matomo, prometheus, interaction, index) — and wires
both the Ansible runner (run_one.yml) and the local rerun helper
(scripts/tests/e2e/rerun-spec.sh) to stage the tree alongside
service-gating.js so specs can `require("./personas")`.
First batch of role specs migrated to consume the shared library
(dashboard, keycloak, mailu, matomo, prometheus, web-svc-cdn,
web-svc-simpleicons) — each gets the spec.js persona scenarios plus
the matching meta/services.yml + playwright.env.j2 entries; matomo is
marked role-closed in req 019. dashboard/keycloak/mailu TODO.md
updated; docs/requirements/019 reflects matomo closure.
Reason: req 019 Rule 3 mandates that every role's guest/biber/
administrator flow runs the same canonical steps; centralising them
in one shared library keeps per-role drift from re-introducing the
stub-shaped bodies the new lint forbids.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|