Skip to content

ci(e2e): turn the shared E2E Tests (Playwright) job on - #111

Open
rubenvdlinde wants to merge 1 commit into
developmentfrom
feat/enable-e2e-playwright
Open

ci(e2e): turn the shared E2E Tests (Playwright) job on#111
rubenvdlinde wants to merge 1 commit into
developmentfrom
feat/enable-e2e-playwright

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

enable-playwright was never set in .github/workflows/code-quality.yml, so quality / E2E Tests (Playwright) reported skipped on every run. In the PR checks list a skipped job is visually indistinguishable from a passing one — 48 spec files carrying 249 collected tests existed in this repo and not one of them had ever run in CI.

Turning the input on alone would not have produced a green job, or an honest one. Four things had to change first, and each of them fails silently when it is not addressed.

1. tests/e2e/playwright.config.ts — a CI-only, one-project config

The shared workflow runs npx playwright test --config=<path> with no --project, so every project in whichever config it picks will run. The ROOT config declares three, two of which must not run on a PR:

  • docs-capture — re-shoots every documentation screenshot (ADR-030 journeydoc).
  • visual — compares PNG baselines that its own note says a CI Linux runner cannot byte-match.

playwright-test-path: tests/e2e makes the workflow's first config lookup hit the new file, which declares only chromium. The root config is deliberately untouched — the separate Journeydoc Capture job hard-fails unless the root config still declares a project named docs-capture, and tests/e2e/docs-screenshots.spec.ts is likewise kept.

Why it stays serial

This is a measured conclusion, not a cautious one. A file-by-file survey found four independent classes of cross-talk that rule out fullyParallel: true + workers: 4:

  1. support/appFixture.ts's ensureApp() takes a caller-supplied slug with no per-worker uniquifier and decides whether to create by list-probe — a textbook TOCTOU. Twelve fixture slugs are hardcoded; hello-world alone is shared by nine spec files and three of them write to it (page-editor-coverage and openbuild-runtime replace its manifest, iconUpload persists an icon). pw-verchain is shared by three files, and version-rollback.spec.ts already carries a comment recording that this exact collision corrupted versionRouting.spec.ts once — serially.
  2. Read-then-delta and exact-count assertions: builder-undo-redo and page-designer snapshot a count then assert initial ± 1; openbuild-runtime asserts there is exactly ONE hello-world Application and exactly THREE hello-message objects; hydra-console reads a total from the API then counts DOM rows.
  3. automations-rbac.spec.ts performs three unspaced form logins per run — exactly the pattern Nextcloud's brute-force throttle fires on, which is why global-setup mints the four role sessions once, sequentially, with a 1.5 s spacer.
  4. The backend is already the bottleneck at one worker: workflows/fixtures.ts resolves ids by pulling _limit=5000 and filtering client-side on nearly every operation.

Duration risk is handled instead with globalTimeout set below the job's 45-minute cap, so an over-running suite aborts itself with a non-zero exit and a report naming what was still running — rather than the job being cancelled, which is no verdict at all.

Making the suite genuinely parallel-safe means porting the RUN_ID pattern from workflows/fixtures.ts into appFixture.ts and quarantining the hello-world writers. That is a real change to the fixtures with its own blast radius and does not belong in the commit that merely turns the job on.

2. tests/e2e/ci-seed.sh — provisioning the CI instance actually needs

  • The register is not imported by occ app:enable. InitializeSettings runs as an IRepairStep, i.e. with no user session, so OpenRegister's RBAC denies the import; the exception is caught and downgraded to a warning and app:enable still exits 0. The import is done here explicitly over the admin HTTP API (POST /apps/openbuild/api/settings/loadreloadConfiguration(force: true), the only path that also deep-merges the ADR-037 register.d/*.json fragments), and then verified: the register plus all 15 schema slugs.
    The slugs are read out of the repo, not guessed. Every schema in lib/Settings/openbuild_register.json and register.d/*.json carries an explicit slug key, and four of them do not match a mechanical kebab-casing of their titles: applicationVersion (camelCase), rule-test-case (title TestCase), decision-table, condition-action-rule.
  • global-setup.ts's occ steps are dead on CI. It seeds the hello-world fixture and disables the wizard rate limit only by resolving the docker container publishing the port under test. On a runner Nextcloud is a bare php -S, so both are skipped with a console warning nothing reads: ~13 spec files would open an empty app, and ApplicationCreationController::wizard()'s #[UserRateLimit(limit: 10, period: 3600)] would start returning 429 mid-run. Both are done here with occ directly. The fixture is then re-checked over HTTP — the seeder exits 0 for "already present" too, so its exit code alone proves nothing.
  • A missing bundle does not 404. Nextcloud serves its HTML error page with HTTP 200 and Content-Type: text/html, which every status-code check in the pipeline reads as success. The script gates on the served response actually being JavaScript.

3. tests/e2e/support/baseUrl.ts — accept BASE_URL, stop defaulting

The shared workflow exports the target as BASE_URL; the resolver did not read it. It happened to work only by falling through to a hardcoded http://localhost:8080 — which off CI is the shared dev container, into which this suite would then write applications, schemas and users. BASE_URL is now accepted, and off CI an unset target is a hard error naming the fix instead of a silent redirect onto somebody else's box.

4. tests/e2e/global-setup.ts — fail loud on CI instead of warning

A failed admin login left every spec to time out on a selector, burning the whole job budget to report a cause stated only in a console line. Worse, a failed RBAC role session is not merely missing: a spec attaching an absent storage state runs unauthenticated, so rbac-403's "the outsider sees no cards" and versionRouting's "a non-member gets 404" both pass for exactly the wrong reason. On CI both conditions now throw.

Notes

  • database: sqlite is retained. The blocker recorded against it for Newman — [PermissionHandler] no such function: REGEXP — no longer applies: OpenRegister development rewrote that query for three platforms and REGEXP now appears nowhere in its lib/ outside the explanatory comments in RegisterMapper.php.
  • additional-apps already pinned openregister at ref: "development"; unchanged.
  • No spec was skipped, no assertion weakened, no timeout raised to paper over a wait.

🤖 Generated with Claude Code

`enable-playwright` was never set, so `quality / E2E Tests (Playwright)`
reported `skipped` on every run — and in the PR checks list a skipped job
is visually indistinguishable from a passing one. 48 spec files carrying
249 collected tests existed in this repo and not one of them had ever run
in CI.

Turning the input on alone would not have produced a green job, or an
honest one. Four things had to change first, and every one of them fails
SILENTLY when it is not addressed.

1. tests/e2e/playwright.config.ts (new) — a CI-only, ONE-project config.

   The shared workflow runs `npx playwright test --config=<path>` with no
   `--project`, so every project in whichever config it picks will run.
   The ROOT config declares three, and two of them must not run on a PR:
   `docs-capture` re-shoots every documentation screenshot, and `visual`
   compares PNG baselines its own note says a CI Linux runner cannot
   byte-match. `playwright-test-path: tests/e2e` makes the workflow's
   first config lookup hit this new file instead. The root config is
   deliberately untouched — the separate `Journeydoc Capture` job
   hard-fails unless it still declares a project named `docs-capture`.

   The config stays SERIAL, and that is a measured conclusion. A
   file-by-file survey found four independent classes of cross-talk that
   rule out the usual `fullyParallel` + `workers: 4` lever: `ensureApp()`
   takes a caller-supplied slug with no per-worker uniquifier and decides
   whether to create by list-probe (a TOCTOU); `hello-world` is shared by
   nine spec files, three of which write to it; builder-undo-redo,
   page-designer and openbuild-runtime assert read-then-delta and exact
   collection counts; and automations-rbac performs three unspaced form
   logins, which is exactly the pattern Nextcloud's brute-force throttle
   fires on. The duration risk is instead handled with a `globalTimeout`
   set BELOW the job's 45-minute cap, so an over-running suite aborts
   itself with a report naming what was still running rather than being
   CANCELLED — which is no verdict at all.

2. tests/e2e/ci-seed.sh (new) — provisioning the CI instance actually needs.

   - `occ app:enable openbuild` runs InitializeSettings as an IRepairStep,
     i.e. with NO user session, so OpenRegister's RBAC denies the register
     import; the exception is caught and downgraded to a warning and
     `app:enable` still exits 0. The import is therefore done here
     explicitly over the admin HTTP API, forced, and then VERIFIED — the
     register plus all 15 schema slugs, read out of
     lib/Settings/openbuild_register.json and register.d/*.json rather
     than guessed. Those files carry EXPLICIT `slug` keys, and four of
     them (`applicationVersion`, `rule-test-case`, `decision-table`,
     `condition-action-rule`) do not match a mechanical kebab-casing of
     their titles.
   - global-setup.ts seeds the `hello-world` fixture and disables the
     wizard's rate limit only via `docker ps --filter publish=<port>`. On
     a runner Nextcloud is a bare `php -S`, so both are skipped with a
     console warning nothing reads: ~13 spec files would open an empty
     app, and `#[UserRateLimit(limit: 10, period: 3600)]` would start
     returning 429 from the eleventh app creation onwards, mid-run. Both
     are done here with `occ` directly, and the fixture is then re-checked
     over HTTP (the seeder exits 0 for "already present" too, so its exit
     code alone proves nothing).
   - A missing frontend bundle does not 404 — Nextcloud serves its HTML
     error page with HTTP 200 and Content-Type text/html, which every
     status-code check in the pipeline reads as success. The script gates
     on the SERVED response actually being JavaScript.

3. tests/e2e/support/baseUrl.ts — accept `BASE_URL`, and stop defaulting.

   The shared workflow exports the target as `BASE_URL`; the resolver did
   not read it. It happened to work only by falling through to a hardcoded
   `http://localhost:8080` — which off CI is the SHARED dev container,
   into which this suite would then write applications, schemas and users.
   `BASE_URL` is now accepted, and off CI an unset target is a hard error
   naming the fix instead of a silent redirect onto somebody else's box.

4. tests/e2e/global-setup.ts — fail loud on CI instead of warning.

   A failed admin login left every spec to time out on a selector, burning
   the whole job budget to report a cause stated only in a console line.
   Worse, a failed RBAC role session is not merely missing: a spec that
   attaches an absent storage state runs UNAUTHENTICATED, so rbac-403's
   "the outsider sees no cards" and versionRouting's "a non-member gets
   404" both PASS for exactly the wrong reason. On CI both conditions now
   throw.

`database: sqlite` is retained. The blocker recorded against it for Newman
— `[PermissionHandler] no such function: REGEXP` — no longer applies:
OpenRegister `development` rewrote that query for three platforms and
`REGEXP` now appears nowhere in its lib/ outside the explanatory comments
in RegisterMapper.php.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 3b89621

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
composer ✅ 101/101
npm ✅ 654/654
PHPUnit
Newman ⏭️
Playwright

Quality workflow — 2026-08-03 22:30 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants