Commit Graph
37 Commits
Author SHA1 Message Date
Alejandro Roman b160d07008 fix(lint): annotate ClassVar in test_email and reorder pixelfed playwright hooks 2026-09-26 19:27:31 +04:00
kevinveenbirkenbachandClaude Opus 5 6fee7ca136 feat(quality): parse every shipped language and run its unit suite
Four parse checks join the `lint-*` family, each using the language's own
parser rather than a reimplementation: `php -l`, `ruby -c`, `sqlfluff parse`,
and stylelint with an empty rule set (it raises CssSyntaxError whatever the
rules say, so the check stays syntax-only and grows no style opinion). Three
unit runners join `test-unit`: node:test, PHPUnit, minitest -- the first and
third ship with their runtime, so only PHPUnit adds a dependency.

Provisioning is best effort and the lint script decides whether it can run. A
machine without PHP must not fail the gate, but it must not look like success
either: the skip names how many files went unchecked. The first version of
these scripts read `git ls-files`, which inside the compose container returns
nothing (git refuses the bind-mounted repo as "dubious ownership") and reported
"No SQL files found." over 52 files -- green, and a lie. They use `find` now,
as the surrounding lint scripts already did.

sqlfluff needed two corrections before it parsed anything real. `%(name)s` is a
psycopg parameter marker, not SQL, so the placeholder templater is configured
rather than the files exempted; and the dialect follows each role's declared
engine (`meta/services.yml`), which is postgres for six roles and mysql for
svc-db-mariadb and pixelfed.

Recipes stay in scripts, not in the Makefile: the targets delegate, matching
every other target in the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 01:31:10 +02:00
kevinveenbirkenbachandClaude Opus 5 575a5a87ac refactor(layout): sort shipped scripts by language and split the unit suite
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).

This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.

Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:

  * 7 `src:` values built from a role variable holding a bare filename
    (svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
    The variable doubles as the destination name inside the container, so the
    language segment belongs at the call site, not in the variable.
  * `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
    through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
    18 roles ship exactly that and have no template fallback: the play would
    have died on each.
  * `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
    interface now reads `files/javascript/javascript.js`.
  * `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
    path through `path_absolute(... 'files', ...)`.
  * 4 `ansible.builtin.script:` invocations naming a bare command
    (pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
  * `dev-python/files/shell/install.sh` pointed at a sibling role's script.

Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 01:31:09 +02:00
kevinveenbirkenbachandClaude Opus 5 642055a20a style(e2e): drop the timeout argument isVisible discards
Playwright declares the timeout option on locator.isVisible() and isHidden() as deprecated and ignored: both return immediately and never wait. Passing resolveTimeout(10_000) to one therefore changes nothing at runtime and only tells the reader a wait happened that never did. That reading is what let 7538b8a0b conclude two genuine call sites were fine, and what hid the false negative b97cf8888 had to fix.

Sixty call sites across fifty-eight role-local specs lose the argument and nothing else. Every added line in this commit is a bare isVisible(); a grep for added lines that are not one returns zero, so a behaviour change cannot have entered here. Files whose diff also alters waiting semantics are deliberately not part of this commit.

A repository lint added in b97cf8888 keeps the construct out. It matches isVisible and isHidden only, because isChecked, isDisabled, isEditable and isEnabled take a real timeout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:49:18 +02:00
kevinveenbirkenbachandClaude Fable 5 d88f272f9f Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:31:08 +02:00
kevinveenbirkenbachandClaude Fable 5 c505ce7466 test(lint): forbid unmarked '|| true' in shell scripts; grandfather the corpus
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:39:16 +02:00
kevinveenbirkenbachandClaude Fable 5 40e553069d refactor(structure): nest prefix-grouped sibling files into subfolders
Move files that share a delimiter-bounded name prefix within a folder (threshold 2) into a subfolder named after the prefix, dropping the prefix from the filename. Covers plugins/filter (docker, generate, has, merge, get/all), env handler subpackages, utils/roles/entity, utils/tests/swarm/write, role SQL/scripts/templates, keycloak/mailu task-includes, shared tasks/utils/load, and CI scripts. Update every import, query_file/script/include_tasks/src/lookup target, compose bind mount, and python -m path to the new locations.

Extend the prefix-grouped-files lint to threshold 2 with dir-level .nocheck and file-head nocheck opt-outs, and skip groups whose shortest member's stem equals the shared prefix. Keep flat only where a loader forbids nesting: WordPress mu-plugins and the docs/requirements namespace. Ansible filter discovery is recursive, so nested filters stay Jinja-resolvable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:00:03 +02:00
kevinveenbirkenbachandClaude Opus 4.8 6f9142027d fix(e2e): skip seaweedfs filer check on onion targets
When an app rides onion, Playwright routes through the node Tor SOCKS
proxy, which cannot resolve the clearnet-internal SeaweedFS filer UI
domain (net::ERR_SOCKS_CONNECTION_FAILED). Skip the filer storage check
on onion targets in all consumer specs — same pattern already proven in
the nextcloud spec. Clearnet runs are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:34:59 +02:00
kevinveenbirkenbachandClaude Opus 4.8 07c6ad8e7c test(e2e): scale every nav click + timeout literal for onion targets
Over Tor the redirect chain behind a login/logout/OIDC click routinely exceeds Playwright's fixed 30s action timeout, so the click throws before the flow's scaled waits run. Sweep all role Playwright suites and the shared persona helpers: give every navigation/auth-transition click an onion-scaled timeout (.click({ timeout: resolveTimeout(30_000) })) and route the remaining raw millisecond literals (timeout options, waitForTimeout, sleep-promise setTimeout, positional wait args) through resolveTimeout. Local UI clicks (menus, toggles, modal dismissals, DOM clicks inside evaluate) stay untouched.

Two new lints keep it that way: test_no_raw_timeout_literals forbids raw numeric timeout literals in Playwright specs (opt-out: nocheck: raw-timeout), test_persona_clicks_scaled requires every click in the shared persona auth flows to carry a resolveTimeout timeout (opt-out: nocheck: unscaled-click).

Verified: node --check on all 133 changed JS files, all 13 playwright lints green, make autoformat clean, make test green (2745+196+163+4 tests OK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:35:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4c91df5de7 test(e2e): forbid raw page.goto, migrate all specs to Tor-resilient gotoOnion
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:59:38 +02:00
kevinveenbirkenbachandClaude Opus 4.8 35dee215c8 test(playwright): route all timeouts through resolveTimeout for onion-compat
Add the global TIMEOUT_FACTOR and a shared timeouts.js helper: resolveTimeout(base) scales by TIMEOUT_FACTOR and, over a .onion target, an extra multiplier, so Tor's per-request latency no longer trips hard-coded timeouts. Migrate every raw timeout in the specs (timeout:/timeout=/setTimeout/waitForTimeout) to resolveTimeout, stage the helper in all three staging paths, and add test_no_raw_timeouts to forbid raw numeric timeouts. Clearnet behaviour is unchanged (factor 1: resolveTimeout(N) === N).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:38:06 +02:00
kevinveenbirkenbachandClaude Opus 4.8 ce29936ac0 fix(web-app-pixelfed): gate the worker on the app bootstrap marker
The horizon worker started in parallel with the app and hit the storage volume before the bootstrap (env setup, oauth keys, storage:link) finished; swarm ignores depends_on, so ordering needs an in-band gate. A wrapper entrypoint polls for /var/www/storage/.docker.init (touched by the bootstrap as its last step, visible through the shared pixelfed_data volume in both modes) and then execs the upstream /worker-entrypoint.sh; start_period 300s keeps the healthcheck from replacing the waiting task.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:21 +02:00
kevinveenbirkenbachandClaude Opus 4.8 67e7f93bda fix(pixelfed): group the best-effort ca-certificates install (shellcheck SC2015)
The CA-install entrypoint used 'apt-get update && apt-get install ... || true', which shellcheck flags as SC2015 (A && B || C is not if-then-else; C may run when A is true). Group it as '{ apt-get update && apt-get install ...; } || true' — identical best-effort semantics, shellcheck-clean. Verified by make test (lint-shellcheck green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:10:03 +02:00
kevinveenbirkenbachandClaude Opus 4.8 343d54d3c2 fix(pixelfed): install the internal CA into the OS bundle for PHP libcurl OIDC
Pixelfed's Laravel OIDC token exchange to Keycloak uses php-curl, which verifies against libcurl's OS bundle (/etc/ssl/certs/ca-certificates.crt), not the CURL_CA_BUNDLE/SSL_CERT_FILE env vars the compose_ca wrapper exports. The custom docker-entrypoint now installs the injected internal CA via update-ca-certificates (installing ca-certificates first if absent) so the OIDC callback's server-side TLS succeeds instead of failing with cURL error 60.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:17:59 +02:00
kevinveenbirkenbachandClaude Opus 4.8 d44869b547 test(web-app): seaweedfs object-store consumer e2e tests for 7 apps + gitea/opentalk exemptions
Add files/playwright/test-seaweedfs.js (+ SEAWEEDFS_* playwright.env vars, spec require wiring, and removal of the playwright-service-flag nocheck above the seaweedfs service) for baserow, bookwyrm, mattermost, openwebui, peertube, penpot, pixelfed. Each test reuses the role's real login flow and triggers an upload that writes to the app's S3 bucket (file-field, avatar, chat attachment, composer photo, video), verified via the shared runSeaweedfsStorageCheck (bucket grew through the Filer UI).

openwebui: also export dismissAllOpenModals from _shared.js (used by the new test) and match the uploaded file by basename (not the full filename) to avoid the extension-split false-negative.

Exempt gitea and opentalk via meta/services.yml nocheck markers (seaweedfs-playwright + playwright-service-flag): gitea has no automatable UI login in this stack (oauth2-proxy + OIDC account-link / native login both fail; personas blocked); opentalk only writes S3 for recordings/whiteboard exports, which need the recorder service + a live multi-party meeting.

Builds on 9b6c6415f (shared runSeaweedfsStorageCheck helper + consumer lint).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.7 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>
2026-05-23 12:59:59 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0fba43d612 fix(web-app-pixelfed): read post-login CSRF from meta tag, not pre-login locator
The native-login test's logout POST reused `csrfInput.getAttribute("value")`
after submitting the login form. The `csrfInput` locator is scoped to the
`/login` DOM that no longer exists post-auth, so the value resolved to null
(or the locator timed out waiting for an `input[name="_token"]` that the
authenticated layout never renders) and the logout POST went out with an
empty `_token` — Laravel 419 / Test-Timeout 300s in the baseline run.

Read the regenerated CSRF token from the authenticated page's
`<meta name="csrf-token">` head tag instead. Laravel rotates the token on
`Auth::login()` anyway, so the pre-login `_token` would not have been
accepted by the logout endpoint even if the locator had resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:24:14 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2c416cb5c7 refactor(db-tasks): migrate ad-hoc SQL execs to database_query module
- Replace 13 raw `container exec mysql/psql` invocations across
  web-app-pixelfed, web-app-fider, and web-app-mattermost with the
  canonical `database_query` module + `query_file:` + `named_args:`
  pattern. The module already speaks both shared (central `svc-db-*`)
  and non-shared (per-role `<entity>-database`) modes via the
  `database` lookup, so the same task shape works regardless of DB
  topology — the per-role hardcoded container-exec argv was the only
  blocker.
- Extract every inline SQL body into per-role `files/sql/` trees.
  Pixelfed splits further into `files/sql/oidc-mapping/` because the
  OIDC-mapping cluster is a distinct concern from the bootstrap /
  administrator queries. Each SQL file is single-purpose and named by
  verb-noun (count-users, select-admin-id, upsert, etc.) to match the
  precedent set by web-app-odoo.
- Fider's existing `files/{create_admin_user,configure_oidc_provider}.sql`
  move to `files/sql/` and switch from psql-specific `:'var'`
  variable substitution (which forced `stdin:` piping with `-v`
  flags) to engine-neutral `%(name)s` placeholders the module
  escapes per backend.
- Pixelfed loses `PIXELFED_MARIADB_ARGS` + 3 derived DB vars
  (`*_CONTAINER`, `*_USERNAME`, `*_PASSWORD`) from `vars/main.yml` —
  they were the manual argv bundle for the old container-exec
  pattern and become orphan once `lookup('database', application_id)`
  takes over.
- Every refactored task carries `mask_values:
  MASK_CREDENTIALS_IN_LOGS` (module-level credential redaction);
  tasks that previously used `no_log: MASK_CREDENTIALS_IN_LOGS`
  keep both directives so the task-level masking stays alongside
  the module-level one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:07:01 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2794ea383e fix(web-app-pixelfed): swap biber's Keycloak email through .nexus for the OIDC tests; split spec into per-test modules
The bespoke `pixelfed oidc login (biber)` test always timed out at the
1200s Ansible-task wrapper because pixelfed's first-time OIDC user-
creation runs Laravel's `email:strict,filter_unicode,dns,spoof`
validator. Egulias `DNSCheckValidation` hard-rejects any TLD in its
RESERVED_DNS_TOP_LEVEL_NAMES list (`test`, `example`, `invalid`, …)
BEFORE any DNS lookup, so `biber@infinito.example` always fails and
Laravel auto-redirects `back()` to the Keycloak Referer — breaking the
SSO callback chain and never returning to pixelfed.

The fix mutates biber's Keycloak profile to `biber@infinito.nexus` for
the duration of this spec (real TLD, real MX) and restores the original
afterwards via the Keycloak admin API. Verified empirically against
the live stack: `pixelfed oidc login (biber)` was 1200s timeout, is
now 20s pass; the `afterAll` restorer puts `biber@infinito.example`
+ `emailVerified=true` back cleanly.

Also splits the spec into one file per test so each scenario stays
atomar and individually inspectable:

- `playwright.spec.js`   orchestrator: `test.use` + lifecycle + `require()`
- `_shared.js`           env, locator candidates, login/logout flow,
                         keycloak admin helpers, `beforeAll`/`afterAll`
- `test-oidc-login-biber.js`
- `test-oidc-login-administrator.js`
- `test-native-login-administrator.js`  new — exercises pixelfed's
   stock email+password `/login` form (no infinito-specific selectors),
   gated to run only when OIDC is disabled
- `test-guest-persona.js`
- `test-biber-persona.js`
- `test-administrator-persona.js`

The deploy's existing `with_fileglob: "*.js"` copies the companions
into `tests/`; Playwright's testMatch only discovers `playwright.spec.js`
so the `test-*.js` modules are pure `require()` targets, not auto-
discovered specs.

Updates two lints to scan the whole `files/playwright/` directory so
both monolithic and split layouts pass:

- `tests/lint/ansible/roles/web-app/playwright/persona/test_naming.py`
  aggregates persona titles across all sibling `.js` files.
- `tests/integration/roles/playwright/test_spec_env_gates.py`
  globs `*.js` for service-gate helper calls instead of reading only
  the main spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 19:07:00 +02:00
kevinveenbirkenbachandClaude Opus 4.7 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>
2026-05-16 23:24:55 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b8a372ded7 fix(web-app-{fider,mattermost,odoo,pixelfed,taiga}/playwright): consume new env keys to satisfy env-keys-used lint
Adding `DASHBOARD_BASE_URL` (Pattern A) and `PROMETHEUS_BASE_URL` to the
playwright.env.j2 of these roles was required by the
`persona-required-envs` lint when `DASHBOARD_SERVICE_ENABLED` is
rendered. The companion `test_env_keys_used` lint then refused them
because no .js file consumed the keys.

Declare them as const in each spec so `process.env.<KEY>` appears
literally in a tracked source. The personas that the persona-required-
envs lint cites as consumers are blocked for these roles
(`PERSONA_*_BLOCKED=true`), so a benign const declaration with an
`eslint-disable-next-line no-unused-vars` is the smallest viable bridge
between the two lints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:37:12 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ba7a66ed3f fix(web-app-{mattermost,taiga,fider,pixelfed}/playwright): gate dashboard-flow tests on DASHBOARD_SERVICE_ENABLED
Pattern A in CI 25939428268: the "dashboard to X" tests navigate to
`page.goto("/")` and expect an `Explore X` link rendered by the
dashboard. When the matrix-deploy variant excludes web-app-dashboard
from the round's include set (variant 1 pins `dashboard.shared: false`
+ `dashboard.enabled: false`), the dashboard container is not deployed
but the specs still run blind against an undeployed URL and time out.

Render `DASHBOARD_SERVICE_ENABLED` into each role's playwright.env.j2
(plus the persona-required base URLs `DASHBOARD_BASE_URL` and the
conditional `PROMETHEUS_BASE_URL` for closure) and gate every
dashboard-flow test body with `skipUnlessServiceEnabled("dashboard")`
so the test cleanly skips in variants without a dashboard instead of
producing a 5-minute timeout. Consistent with the existing service-
gating pattern already used in baserow/akaunting/gitea specs.

friendica is unchanged here: its spec was already refactored away from
the dashboard flow toward direct oauth2-proxy + friendica /login round
trips (no `page.goto("/")` against the dashboard) in a separate WIP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:37:12 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3c11b67fce fix(playwright/env): wire BIBER+ADMIN credentials so personas actually log in
Render BIBER_USERNAME/BIBER_PASSWORD on the role-local env templates
that drive the shared biber persona helper and, where the
administrator branch shares the same template, ADMIN_USERNAME/
ADMIN_PASSWORD too. Without these the shared persona helper read an
empty credential pair, skipped the Keycloak login step, parked on the
auth page, and the auth-marker assertion failed loudly with no clue
that the cause was a missing env declaration.

For web-app-pixelfed the previous indirection through
PIXELFED_PLAYWRIGHT_FIRST_USER_KEY / _SECOND_USER_KEY rendered the
same biber/administrator credentials under the role-local
LOGIN_USERNAME_{FIRST,SECOND} aliases. Drop the indirection and
consume BIBER_*/ADMIN_* directly in the role's spec so the env
contract matches the rest of the persona-driven roles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 00:06:47 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0e651e4a22 chore(lint): tighten ESLint rules and clean up flagged offenders
Promote four cheap rules in `eslint.config.js`:
* `no-unused-vars`: warn → error (with `argsIgnorePattern: "^_"`)
* `no-var`: error
* `eqeqeq`: error (always)
* `playwright/no-wait-for-navigation`: off → error
* `linterOptions.reportUnusedDisableDirectives`: off → warn

Add a repo-root CommonJS block so `eslint.config.js` itself sees Node
globals (`require`, `module`).

Fix every flagged offender:
* 138 stale persona-helper imports across 70 spec files
  (`isServiceEnabled`, `skipUnlessServiceEnabled`, `runAdminFlow`,
  ... — leftovers from the SPOT consolidation).
* 4 unused catch bindings → modern `catch {}` syntax.
* 2 unused `dashboardBaseUrl` declarations (minio, moodle).
* 1 unused `label` arg → renamed to `_label` to opt out per the
  argsIgnorePattern.
* 1 intentionally-kept legacy helper in web-app-wordpress's spec
  receives a per-line `eslint-disable-next-line` with reason; the
  preceding docstring already explained why it stays.
* 1 `page.waitForNavigation` in web-app-fediwall's Mastodon OIDC flow
  receives a per-line disable with reason — the navigation target
  depends on the runtime OIDC issuer; `waitForURL` would need a
  runtime-built pattern.
* 10 stale `// eslint-disable-next-line no-console` directives now
  surfaced by `reportUnusedDisableDirectives`; removed.

ESLint after this pass: 0 errors, 154 warnings (148 pre-existing
`playwright/expect-expect` plus 6 `playwright/no-force-option` that
were already inherited from `playwright/flat/recommended`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:25:23 +02:00
kevinveenbirkenbachandClaude Opus 4.7 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>
2026-05-10 16:25:22 +02:00
kevinveenbirkenbachandClaude Opus 4.7 eb7344cc04 refactor(playwright): share DOM + regex utility helpers
Five role specs (dashboard, nextcloud, odoo, pixelfed, taiga)
duplicated four small utility helpers across one-cluster-each
inline copies:

  - isVisible(locator)               — `locator.first().isVisible()`
                                       with the strict-mode / detached
                                       error swallowed.
  - waitForFrameUrl(iframe, ...)     — poll the embedded iframe URL
                                       until it contains a matcher.
  - findFirstVisibleCandidate(list)  — pick the first visible
                                       candidate from a label/locator
                                       list and return the resolved
                                       single-element locator.
  - escapeRegex(value)               — escape regex metacharacters
                                       so a raw string can be used as
                                       a `new RegExp(...)` source.

Changes
-------
- New `roles/test-e2e-playwright/files/personas/utils/dom.js` collects
  all four helpers (canonical 1-cluster bodies, byte-equivalent to the
  former inline copies).
- `personas/utils/index.js` re-exports the new module so callers reach
  the helpers via `require("./personas")`.
- The five specs strip their inline definitions and amend the
  destructuring with whichever helpers their body still references.

Drift handling
--------------
None. All four helpers were 1-cluster across the call sites; this is
mechanical substitution.

Verification
------------
- 419 passed / 1 skipped / 0 failed across `tests/lint/`,
  `tests/integration/roles/`, `tests/unit/plugins/lookup/`.
- `make lint-javascript`: 0 errors, 283 warnings (unchanged from
  baseline).
- 0 inline `function isVisible|waitForFrameUrl|findFirstVisibleCandidate|escapeRegex`
  definitions remain in any role spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:25:21 +02:00
kevinveenbirkenbachandClaude Opus 4.7 122c82910b refactor(playwright): share decodeDotenvQuotedValue + normalizeBaseUrl
Both helpers were inlined verbatim in 71 of the role-local
`playwright.spec.js` files (52 with the compact form, 19 with the
verbose form, plus one parameter-name variant). All bodies were
semantically identical.

Changes
-------
- New `roles/test-e2e-playwright/files/personas/utils/dotenv.js` holds
  the canonical pair (`decodeDotenvQuotedValue` and `normalizeBaseUrl`),
  re-exported through `personas/utils/index.js` so specs that
  `require("./personas")` get them via the existing aggregator.
- Every role spec drops its inline copies and pulls the helpers from
  `./personas` instead. Specs that only reference one of the two
  carry only that one in the destructuring (no unused-import noise).

Doc
---
`docs/contributing/artefact/files/role/playwright.specs.js.md` no
longer carries the "once a third spec needs them, MUST be promoted"
directive: the helpers live at the shared path and inline copies are
forbidden.

Verification
------------
- 419 passed / 1 skipped / 0 failed across `tests/lint/`,
  `tests/integration/roles/`, `tests/unit/plugins/lookup/`.
- `make lint-javascript`: 0 errors, 283 warnings (unchanged from
  baseline; no new unused-imports introduced).
- 0 inline `function decodeDotenvQuotedValue|normalizeBaseUrl`
  definitions remain in any role spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:55:05 +02:00
kevinveenbirkenbachandClaude Opus 4.7 44d51744a7 feat(playwright): migrate second batch of roles to shared persona library
Wires runGuestFlow / runBiberFlow / runAdminFlow from
roles/test-e2e-playwright/files/personas/ into another ~28 roles
(akaunting, baserow, bluesky, bookwyrm, bridgy-fed, decidim, discourse,
fider, friendica, fusiondirectory, gitea, hugo, jenkins, joomla, kix,
matrix, mattermost, mig, moodle, odoo, opencloud, peertube, pixelfed,
postmarks, sphinx, taiga, yourls, web-svc-libretranslate, web-svc-xmpp).
Each role gets the spec.js persona scenarios plus matching
meta/services.yml gate entries and playwright.env.j2 env wiring;
admin-flow callers pass a role-specific adminInteraction that drives
an actual management surface so the body is not a stub.

Reason: continuation of d3e5ebe44 — req 019 Rule 3 demands every
role exercise the canonical guest/biber/administrator flows; this
batch closes the long-tail role list against the central library so
per-role drift cannot reintroduce the stub-shaped bodies the new lint
forbids.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:09:46 +02:00
kevinveenbirkenbachandClaude Sonnet 4.6 5b81aaabca fix(pixelfed): use POSIX-compliant -R flag for cp (SC2336)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 00:04:32 +02:00
kevinveenbirkenbach ef71fdede8 refactor(pixelfed): shorten entrypoint filename 2026-04-01 23:08:08 +02:00
kevinveenbirkenbach 8141c4281a fix(pixelfed): stabilize bootstrap with managed entrypoint 2026-04-01 22:09:36 +02:00
kevinveenbirkenbach 6c6f56a160 Stabilize Pixelfed Playwright iframe reload flow 2026-03-31 14:46:39 +02:00
kevinveenbirkenbach cb2d956415 fix(playwright): accept Pixelfed logged-out success state after OIDC logout
Stabilize the Pixelfed Playwright logout flow by accepting the real
post-logout states returned by the OIDC provider instead of requiring
a redirect back to Pixelfed only.

- detect the iframe frame and URL more robustly
- handle optional Keycloak logout confirmation
- treat dashboard login visibility as a valid logged-out signal
- accept Pixelfed login screen, Keycloak login screen, or logged-out success page
- avoid false positives by removing broad Pixelfed base URL matching

This fixes the failing Pixelfed E2E test where logout succeeded but the
iframe remained on the "You are logged out" page.
2026-03-29 12:09:45 +02:00
kevinveenbirkenbach df28be8705 fix(pixelfed): handle Keycloak logout confirmation in Playwright E2E test
Improve Pixelfed Playwright test stability by handling the additional
Keycloak logout confirmation step.

- Detect and confirm Keycloak logout screen after Pixelfed logout
- Switch iframe URL detection to use real frame navigation instead of query params
- Make logout state detection more robust across redirect flows

This fixes failing E2E tests where logout remained stuck on the
Keycloak "Logging out" screen.
2026-03-29 10:27:49 +02:00
kevinveenbirkenbach 1cf110ceb9 fix: stabilize pixelfed playwright logout 2026-03-28 08:15:40 +01:00
kevinveenbirkenbach 153d392322 Bootstrap Pixelfed administrator OIDC login 2026-03-25 20:49:48 +01:00
kevinveenbirkenbach bc7dae2b2a Add Pixelfed Playwright OIDC test 2026-03-25 16:42:02 +01:00