ci(e2e): turn the shared E2E Tests (Playwright) job on - #111
Open
rubenvdlinde wants to merge 1 commit into
Open
Conversation
`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.
Contributor
Quality Report — ConductionNL/openbuild @
|
| 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
enable-playwrightwas never set in.github/workflows/code-quality.yml, soquality / E2E Tests (Playwright)reportedskippedon 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 configThe 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/e2emakes the workflow's first config lookup hit the new file, which declares onlychromium. The root config is deliberately untouched — the separateJourneydoc Capturejob hard-fails unless the root config still declares a project nameddocs-capture, andtests/e2e/docs-screenshots.spec.tsis 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:support/appFixture.ts'sensureApp()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-worldalone is shared by nine spec files and three of them write to it (page-editor-coverageandopenbuild-runtimereplace its manifest,iconUploadpersists an icon).pw-verchainis shared by three files, andversion-rollback.spec.tsalready carries a comment recording that this exact collision corruptedversionRouting.spec.tsonce — serially.builder-undo-redoandpage-designersnapshot a count then assertinitial ± 1;openbuild-runtimeasserts there is exactly ONE hello-world Application and exactly THREE hello-message objects;hydra-consolereads a total from the API then counts DOM rows.automations-rbac.spec.tsperforms three unspaced form logins per run — exactly the pattern Nextcloud's brute-force throttle fires on, which is whyglobal-setupmints the four role sessions once, sequentially, with a 1.5 s spacer.workflows/fixtures.tsresolves ids by pulling_limit=5000and filtering client-side on nearly every operation.Duration risk is handled instead with
globalTimeoutset 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_IDpattern fromworkflows/fixtures.tsintoappFixture.tsand quarantining thehello-worldwriters. 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 needsocc app:enable.InitializeSettingsruns as anIRepairStep, i.e. with no user session, so OpenRegister's RBAC denies the import; the exception is caught and downgraded to a warning andapp:enablestill exits 0. The import is done here explicitly over the admin HTTP API (POST /apps/openbuild/api/settings/load→reloadConfiguration(force: true), the only path that also deep-merges the ADR-037register.d/*.jsonfragments), 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.jsonandregister.d/*.jsoncarries an explicitslugkey, and four of them do not match a mechanical kebab-casing of their titles:applicationVersion(camelCase),rule-test-case(titleTestCase),decision-table,condition-action-rule.global-setup.ts's occ steps are dead on CI. It seeds thehello-worldfixture and disables the wizard rate limit only by resolving the docker container publishing the port under test. On a runner Nextcloud is a barephp -S, so both are skipped with a console warning nothing reads: ~13 spec files would open an empty app, andApplicationCreationController::wizard()'s#[UserRateLimit(limit: 10, period: 3600)]would start returning 429 mid-run. Both are done here withoccdirectly. The fixture is then re-checked over HTTP — the seeder exits 0 for "already present" too, so its exit code alone proves nothing.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— acceptBASE_URL, stop defaultingThe shared workflow exports the target as
BASE_URL; the resolver did not read it. It happened to work only by falling through to a hardcodedhttp://localhost:8080— which off CI is the shared dev container, into which this suite would then write applications, schemas and users.BASE_URLis 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 warningA 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" andversionRouting's "a non-member gets 404" both pass for exactly the wrong reason. On CI both conditions now throw.Notes
database: sqliteis retained. The blocker recorded against it for Newman —[PermissionHandler] no such function: REGEXP— no longer applies: OpenRegisterdevelopmentrewrote that query for three platforms andREGEXPnow appears nowhere in itslib/outside the explanatory comments inRegisterMapper.php.additional-appsalready pinned openregister atref: "development"; unchanged.🤖 Generated with Claude Code