Skip to content

e2e: BDD/Stagehand suite + E2E-on-local-k8s CI workflow - #4

Open
xiduzo wants to merge 62 commits into
mainfrom
explore/BDD-stagehand
Open

e2e: BDD/Stagehand suite + E2E-on-local-k8s CI workflow#4
xiduzo wants to merge 62 commits into
mainfrom
explore/BDD-stagehand

Conversation

@xiduzo

@xiduzo xiduzo commented Jul 21, 2026

Copy link
Copy Markdown

What

Ports the e2e suite to playwright-bdd with a vertical bdd/ structure and Stagehand AI scenarios, and adds a CI workflow that runs the whole suite against the real Helm charts in a kind cluster — the same hack/up.sh flow used locally.

Why open this PR

The E2E on local k8s workflow (.github/workflows/e2e-k8s.yml) lives only on this branch, so it wasn't visible in the Actions tab or runnable via workflow_dispatch (both require the default branch). This PR triggers it via on: pull_request, making the first run appear here.

Highlights

  • .github/workflows/e2e-k8s.yml — spins up kind, brings up the GPP stack from charts (USE_EXISTING_CLUSTER=1 hack/up.sh), raises vm.max_map_count for ES/ECK, runs Playwright, uploads report + dumps cluster state on failure.
  • BDD rework: bdd/_core fixtures, per-app role registry, Stagehand routing by role×cost tier, @todo coverage stubs.
  • Removes legacy pre-BDD spec tests and login helpers.

Note

The workflow pins GPP-app and GPP-burgerportaal to ref: feat/local-dev-stack — tracking those feature branches, not their mains.

🤖 Generated with Claude Code

xiduzo and others added 24 commits July 14, 2026 13:41
Switch the e2e project to playwright-bdd (Gherkin features generated into
.features-gen), add Stagehand/OpenRouter + zod + otpauth deps, and point the
`@/*` path alias at the repo root.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Delete the old spec-based tests and Azure/Maykin login helpers superseded by
the playwright-bdd suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gherkin features + steps for testscripts 1,3,4,5,9,10,11 (plus @Blocked stubs
for 6/7/8), driven by Stagehand for admin/beheer UI mutations and verified
deterministically through the session-authenticated page or public API.

Organised vertically by GPP-Woo bounded context: each bdd/@<app>/ holds its
features, steps and support/ helpers; bdd/_core/ holds the shared harness
(fixture graph, ENV, resource-manager, Keycloak, Stagehand plumbing, signIn).
Deterministic own-and-cleanup via a makeResourceManager() factory + an
adminResource()/adminDriver() seam over the Django admin; run-wide E2E-data
sweep in global-teardown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The @admin steps each rebuilt an adminDriver locally (`function org(sh){…}`)
and threaded `adminStagehand` through every step. Promote the three nouns to
`orgAdmin`/`topicAdmin`/`pubAdmin` fixtures so steps receive a ready-built
driver by destructuring, per the playwright-bdd add-fixtures pattern. Noun→URL
configs move to support/admin-uis.ts. Step text unchanged; typecheck + lint
clean; bddgen 11 features → 11 specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The TOPIC_IMAGE path doubled its prefix after the vertical restructure moved
topic.ts into support/ (ENOENT at runtime). Also enable playwright-bdd's aiFix
promptAttachment so failing tests carry a ready-to-paste fix prompt in the report.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unblock the three parked eindgebruiker/document testscripts.

TS6/7 (gpp-app publicatie create/withdraw): the blocker was data — no account
was in an authorised gebruikersgroep, so /api/mijn-gebruikersgroepen was empty
and "Nieuwe publicatie" errored. New authProfile fixture seeds the prerequisite
over the odpc API (identity from /api/me); publicatie-ui.ts fills the form
deterministically by input value=uuid (option-groups are custom widgets),
Stagehand drives navigation + Publiceren/Intrekken. Verified via the deterministic
publicatiestatus read through the publicatiebank admin.

TS8 (document beheer): provision the Documenten API (setup/provision-documenten-api.sh
wires the woo-publications DRC service + GlobalConfiguration; OpenZaak was already
set up + also applies the token-auth fix live). document.ts seeds a published
document over the token API (Host header via APIRequestContext so OpenZaak resolves
the informatieobjecttype URL; upload URLs rewritten to localhost), docAdmin driver
withdraws/deletes it, verified via the admin changelist. Needs --workers=1.

Also: documents added to the global-teardown sweep; plan doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stagehand 3.6 supports zod 4; migrate the env/user schemas to the zod-4
API (z.email()/z.url()). openai@4 still declares an unsatisfiable optional
peer on zod@3, so .npmrc sets legacy-peer-deps to keep clean/CI installs
flag-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the single @Anthropic model tag with a role×tier routing table
(ROLE_MODELS in bdd/_core/stagehand.ts): each Stagehand op (act→worker,
extract→verification, observe→planner) picks a model by the scenario's
cost tier. Scenarios are tagged @ai (skip-without-OPENROUTER_API_KEY
guard); @cheap-ai/@expensive-ai shift the tier and @model:<id> pins one
model. Per-request routing is done in the OpenRouter fetch wrapper, so a
single llmClient serves all roles.

The worker/verification default is gpt-4o-mini: gemini-2.5-flash
intermittently returns action JSON that fails Stagehand's zod schema
(observed as recurring "Zod schema validation failed" flake); gpt-4o-mini
is comparably cheap without it. Claude Sonnet stays the @expensive-ai tier.

checks/stagehand-routing.check.ts is a standalone node self-check for the
tier/override/table resolution and the model-rewriting fetch; tsconfig and
eslint exclude *.check.ts (standalone run-scripts) and untyped markdown
code samples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the "AI model routing" section: the role×tier table, which tag to
reach for (@Cheap-AI / default / @expensive-ai), and the @model: /
withModel() per-scenario and per-step overrides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep ad-hoc run reports/logs (scratch/) out of the tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Formatting only — normalise table column widths and emphasis markers
(*x* → _x_) to satisfy the repo's prettier config. No content change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stagehand used to launch its own Chromium, so Playwright's trace only
captured the near-empty page/API context — the AI browser (where all the
UI work happens) was invisible: about:blank, no filmstrip.

Now Stagehand attaches to the Playwright-launched Chromium over CDP
(localBrowserLaunchOptions.cdpUrl → the chromium project's
--remote-debugging-port, per worker) and adopts its existing page. So the
AI drives the SAME target Playwright authenticated and traces: one
trace.zip per scenario with screencast + DOM snapshots + network + the
AI's actions. Cookie injection and the duplicate Keycloak host-arg are
gone — the shared context already carries both.

Consequences:
- The `page` context must carry the session the AI needs, so storageState
  maps @beheer → burgerportaal beheer-admin and other @ai → admin.
- CDP is Chromium-only, so @ai/@beheer are now chromium-only (the AI
  browser was always Chromium regardless; only verification-page reads
  were cross-browser). This also removes the flakiest firefox/webkit AI
  runs and the odrc 500s from 3-browser concurrent load.
- @beheer is now tagged @ai and shares the single @ai guard (skip without
  key + chromium-only); its bespoke guard is removed.

Validated: unified trace confirmed (33 screencast frames, 21 DOM
snapshots, the AI's admin navigations as trace actions); full chromium
project 38 passed / 0 Zod errors / no non-AI regressions. Residual @ai
mutation flakiness is unchanged and tracked separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exclude @ai (Stagehand) scenarios from the firefox/webkit projects with
grepInvert: /@ai/ — Stagehand drives Chromium over CDP, which firefox and
webkit don't expose, so those scenarios errored (fixture CDP fetch) instead of
skipping, and could clobber shared global resources when run in parallel.

Enable a single local retry (CI stays at 2). The @ai act()/SPA-teardown flake
rotates across scenarios run-to-run, so per-scenario tagging can't catch it; a
retry absorbs the transient miss while a real failure still fails twice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- adminDriver.open() now settles the change page after the row-click act, like
  every other driver method. Without it, callers querying form fields (e.g.
  #id_publicatiestatus) via the no-auto-wait Stagehand understudy locator could
  hit the DOM before it rendered (StagehandElementNotFoundError).
- openBeheer waits up to 15s (was the 5s default) for the admin Dashboard link:
  the Beheer redirect + admin render exceeds 5s on firefox/webkit against an
  emulated backend.
- document.ts getJson: status-checked GET with one retry on 5xx, replacing blind
  res.json() — turns an opaque "Unexpected token '<'" into "GET x -> 500: <body>".
  Documents that the token seed needs provision-documenten-api.sh (wires the
  Documenten API + patches ODRC token auth to AnonymousUser; a down/up reverts it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
act() on the publicatiestatus dropdown is the single flakiest Stagehand step.
Set it with a native selectOption on #id_publicatiestatus instead (Stagehand
still opens + saves). The publicaties step previously used act(); documenten
already did this. Both use the Stagehand understudy locator on the same tab
after adminDriver.open() settles the change page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- @expensive-ai (Claude Sonnet) on the repeat AI-flaky scenarios: the whole
  gebruikersgroepen feature (serial-first flake cascades), onderwerpen "Edit
  omschrijving", organisaties "Rename".
- @fixme quarantine on the documenten (TS8) feature: the seed works once
  provision-documenten-api.sh is run, but the withdraw step is unreliable —
  adminDriver.open()'s row-click doesn't consistently reach the document change
  page (its changelist link differs from the publicatie one). Pre-existing gap;
  real fix is to open the change page by URL.
- Drop folder-name tags repeated on Feature lines (@publicatiebank/@admin,
  @gpp-app, @burgerportaal) — tags-from-path already applies them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add an explicit rule in "Authentication by tag": don't repeat folder-name tags
  on the Feature line; tags-from-path already applies every @-segment.
- Update the TS8 entry: it's implemented (seed works) but @fixme-quarantined on
  the withdraw step, and needs provision-documenten-api.sh per fresh stack (a
  down/up reverts the ODRC auth patch, reintroducing the user-less-token 500).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Stagehand act() row-click did not reliably land on the document change
page; the changelist search + row click now runs via the session page
(openDocumentAdmin). Stagehand still drives the save/delete mutations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uard, declared sweep order

Architecture pass to keep the suite scalable as features multiply:

- _core/fixture.ts becomes a composition root; fixture bodies move to
  _core/core-fixtures.ts and per-app fixtures.ts (publicatiebank ->
  gpp-app -> burgerportaal chain; gpp-app extends publicatiebank because
  authProfile seeds through its organisations fixture). Step files are
  untouched.
- _core/roles.ts replaces setup/paths.ts: storage-state paths plus the
  tag -> session table (stateForTags) consumed by the storageState
  fixture, auth.setup.ts and global-teardown.ts. New role = one row +
  one login line.
- _core/singleton-guard.ts captures the snapshot/restore pattern for
  shared server-side singletons (beheer config), stating the
  @mode:serial / chromium-only contract once.
- global-teardown sweep order is declared per row via dependsOn and
  topo-sorted (_core/topo.ts, loud on unknown labels/cycles) instead of
  encoded in array position; checks/teardown-order.check.ts covers it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Give every feature a matching <feature>.steps.ts and reserve _shared.steps.ts
for steps genuinely reused across features. Step files are glob entrypoints
(matched to Gherkin by text, never imported), so renames are safe.

- rename <feature>-steps.ts -> <feature>.steps.ts; single-feature steps.ts
  files renamed to their feature (sitemap, configuratie, organisaties, ...)
- move informatiecategorieen steps next to their feature under @admin/
- split @gpp-app/steps.ts and @admin/steps.ts: feature steps to their file,
  the reusable login precondition and cross-vertical @ai hook to _shared.steps.ts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Spin up the full GPP-Woo stack from the Helm charts on a kind cluster (the
same charts/hack/up.sh used locally) and run the Playwright suite against it,
so the real charts are validated on every PR. Checks out all app repos as
siblings (up.sh builds every image from source; open-zaak is cloned by up.sh)
and points the suite at the NodePort URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Identify and scaffold significant E2E test coverage gaps from manual test scripts
by introducing `@todo` tagged scenarios.

- New `.feature` files contain `@todo` scenarios outlining missing
  assertions for Burgerportaal rendering, sitemap details, search
  functionality, and GPP-app user group/publication management.
- Corresponding stubbed step definitions (`.steps.ts`) are added to keep
  `bddgen` green.
- A new `bdd/_core/todo.steps.ts` hook automatically skips `@todo` scenarios
  at runtime, enabling progressive implementation without blocking CI.
- Adds a `dev` script to `package.json` for quicker local development.
```
@xiduzo
xiduzo had a problem deploying to maykin and icatt dev environments July 21, 2026 11:49 — with GitHub Actions Failure
- e2e-k8s.yml: charts checkout needs ref explore/local-via-k3d — hack/
  (up.sh, kind-config.yaml) isn't on charts' main, so the kind config
  was missing and cluster creation failed.
- tsconfig: add lib ES2022+DOM so Error(msg, { cause }) typechecks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xiduzo
xiduzo had a problem deploying to maykin and icatt dev environments July 21, 2026 11:56 — with GitHub Actions Failure
- e2e-k8s.yml: add azure/setup-helm@v4 (v3.18.0). up.sh's zoeken install
  uses `helm upgrade --force-conflicts` (server-side apply), added in
  Helm 3.18; the runner's pre-installed Helm was older and errored out.
- Clear 86 eslint errors in BDD step files: drop unused fixture keys
  (use `_`/`()` where the destructure empties, keep Cucumber capture
  args), split multi-statement guard lines, single quotes.
- Auto-format docs/skills markdown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xiduzo
xiduzo had a problem deploying to maykin and icatt dev environments July 21, 2026 12:47 — with GitHub Actions Failure
azure/setup-helm@v4 cached the binary in a nested linux-amd64/ dir it
doesn't add to PATH, so up.sh still ran the runner's older Helm and hit
`unknown flag: --force-conflicts`. Install over /usr/local/bin/helm
directly and print `helm version` to confirm 3.18 is the one used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit refactors several BDD test suites to use plain Playwright locators instead of AI-driven browser automation (Stagehand + OpenRouter).

*   **Motivation:** AI browser automation proved flaky and costly for testing structured UIs like the Django admin and Vue SPAs, where stable locators are readily available.
*   **Changes:**
    *   `@beheer`, `@publicatiebank/@admin` (onderwerpen, documenten, publicaties), and `@gpp-app` (publicaties) scenarios are updated to use direct Playwright `page` interactions.
    *   The `@ai` tag and associated skips are removed from these features.
    *   The `AdminDriver` and related fixtures are refactored to operate directly on the Playwright `page`.
    *   The `@chromium-only` tag is introduced for scenarios mutating shared global resources to prevent race conditions in parallel runs.
*   **Outcome:** Improves test stability, reduces external API costs, and simplifies test logic by leveraging direct locator capabilities. The Stagehand plumbing is retained but currently unused, for potential future use cases with genuinely unaddressable UIs.
```
This commit completes several previously skipped BDD test scenarios and enhances existing ones for improved reliability and accuracy.

*   **Expand BDD scenario coverage:** Implement end-to-end tests for document and information category management in the Publicatiebank admin, removing `@todo` tags from numerous scenarios.
*   **Improve GPP-app UI interaction reliability:** Introduce dedicated Playwright helper functions (`confirmWrite`, `fillTitel`, `selectProfiel`) to robustly handle common UI flakiness in the GPP-app, such as timing issues with form updates and button clicks.
*   **Refine WebKit skips:** Extend the `@no-webkit` tag to the GPP-app SPA and clarify the underlying `Content-Security-Policy: upgrade-insecure-requests` issue that prevents both SPAs from booting on `http://localhost` in WebKit.
*   **Increase authentication timeout:** Adjust the admin authentication setup timeout to mitigate potential flakiness with one-time codes during test runs.
xiduzo and others added 2 commits August 10, 2026 11:15
Co-authored-by: Cursor <cursoragent@cursor.com>
…tching

Co-authored-by: Cursor <cursoragent@cursor.com>
Wire seed+index helpers, short sitemap-cache waits, and exact DiWoo tag matching so previously @Blocked search and sitemap coverage runs deterministically.

Co-authored-by: Cursor <cursoragent@cursor.com>
Implement previously `@blocked` scenarios by wiring up document content search via Elasticsearch attachment ingest and enabling document withdrawal in the GPP-app UI.

This resolves long-standing blocks related to:
*   **Document content search:** Configures OpenZaak SENDFILE and `zoeken` download services for document body ingestion by Elasticsearch. Seeds documents with unique `fileContent` to verify hits.
*   **Document withdraw:** Ensures ODPC→ODRC document mutations work, allowing the GPP-app to withdraw documents via the UI. Adds dedicated Playwright helpers for document management.
Baseline was 235 passed / 22 failed / 12 flaky / 35 did-not-run; now 304/304
with no flakes. Details and per-item evidence in E2E-FIXES-2026-08-10.md.

Most of the "flakiness" was a starved backend, not test code: the kind node
ran at 340 MB free of 7935 MB and the kernel SIGKILLed gpp-publicatiebank
(exit 137), which surfaced as `POST documenten -> 502`, a Beheermenu that
never rendered and beforeEach timeouts — all of which passed in isolation.
Cap local workers so the stack is not pushed into swap.

Test-side root causes:

- The sitemap is eventually consistent with the token API (ODRC registers an
  uploaded document with the Documenten API asynchronously), so a just-seeded
  document is briefly absent and a withdrawn one briefly still present. Poll
  in support/sitemap.ts instead of reading once, and give the feature
  @timeout:120000 — the monthly-sitemap fetch is O(n) in seeded documents.
- zoeken: the filter assertion snapshotted an article count before the list
  re-rendered, so it could read a Publicatie on its way out. Poll the
  rendered type labels, scoped to the result list.
- intrekken: PromptModal.vue renders a bare <dialog> with no aria-label, and
  a <dialog> does not take its name from an inner heading, so
  getByRole('dialog', {name: /intrekken/i}) matched nothing at all. Locate by
  heading, and confirm via confirmWrite — a bare click lands before the app
  wires its handler and is silently dropped, so nothing was withdrawn.
- The ingetrokken assertion resolved to the hidden <option value="ingetrokken">
  of the status filter; read the row's role="status" instead.
- organisaties: the Django "Actief: Ja" filter is a full navigation, so
  reading th.field-naam a immediately could return []. Also stop requiring
  every active organisatie in a global snapshot to resolve in the GPP-app —
  other workers create and delete their own E2E rows mid-run.

Stack provisioning:

- Provision the two GlobalConfiguration publication URL templates. They ship
  empty, so the API serves urlPublicatieExtern as "" and the GPP-app renders
  "Bekijk online" with href="" — which reloads the GPP-app instead of opening
  the burgerportaal. This is the long-standing :8130-instead-of-:8140 failure;
  it was a provisioning gap, not a GPP-app deploy bug.
- provision-documenten-api.sh was not idempotent despite saying so: it keyed
  Service.objects.update_or_create on slug while api_root is the UNIQUE
  column, so re-running died on api_root_key before reaching the config save.

Also: repo-wide lint is clean again (missing node: imports for process and
Buffer, plus formatting).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…auth fix

Two reasons the pipeline failed 81 scenarios while the same suite ran 304/304
locally. Neither was test logic — the identical 81 failures, with identical
grouping, are present on the previous commit too.

webkit (43 failures, all `browserType.launch: Target page, context or browser
has been closed`): the Chromium-only `--host-resolver-rules` flag was set in the
shared `use.launchOptions`, so it was passed to every browser. WebKit on Linux
hard-errors on an unknown option:

    Cannot parse arguments: Unknown option --host-resolver-rules=...

so the browser never launches. macOS WebKit tolerates the flag, which is exactly
why this only ever showed up on CI. Firefox cannot use it either, and the
scenarios that need it are @chromium-only. Moved to the two chromium projects —
including `setup`, which performs the real Keycloak sign-in and is the one place
that genuinely needs the rule.

GPP-publicatiebank was checked out at its default branch, where
TokenAuthentication still returns `(None, token)`; SessionProfileMiddleware then
dereferences request.user, so every token API call 500s while an admin session
is live. Pinned to fix/token-auth-anonymoususer until that lands on main.

The remaining 35 CI failures were `Bad Request (400)` DisallowedHost pages from
ODRC, fixed on the charts side (the e2e seed's Host header was not in
ALLOWED_HOSTS there).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
52 of the remaining 55 CI failures were `POST documenten -> 500` (all three
browsers, almost all of sitemap.feature). The pipeline went straight from
`hack/up.sh` to the suite, so nothing ever wired the Documenten API: no DRC
Service, no GlobalConfiguration.documents_api_service, and no ORC service in
OpenZaak for the publicatiebank catalogi. Every scenario that seeds a document
therefore failed at the seed.

This is the same once-per-fresh-stack step the README already documents for a
local bring-up, so run the existing script rather than duplicating the wiring in
the workflow. It is idempotent, and it also sets the publication URL templates,
which ship empty and otherwise make the gpp-app "Bekijk online" scenario open
the gpp-app instead of the burgerportaal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xiduzo and others added 2 commits August 11, 2026 09:26
…pty sitemaps

The dump selected pods with `app.kubernetes.io/instance=odrc|zoeken`, but the
publicatiebank pods carry no instance label and those selectors match only the
*redis* pods — so on every failure it collected redis logs and never the app or
worker logs. `-l app=openzaak` matched nothing either. Select by deployment name
instead.

Then add setup/dump-odrc-state.sh. The current CI failure is 44 sitemap scenarios
against a sitemap the run itself reports as holding 0 documents, and none of the
three possible reasons is visible in any pod log: the burgerportaal lists a
document only when it is gepubliceerd AND upload_complete (the worker registered
it with the Documenten API) AND its publisher is not zelf_toegevoegd. The script
prints all three, plus the GlobalConfiguration wiring, so the next run says which
one it is instead of leaving it to inference. Useful locally for the same
question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18 sitemap scenarios failed with "SITEMAP_CACHE_WAIT_MS is unset". That guard is
deliberate — waitForSitemapCacheExpiry refuses to guess, because the production
output cache is ~23h and sleeping that long is never what a test wants. The
workflow just never set it. charts dev/values/odbp.yaml pins
sitemapCacheDurationHours to 0, so 0 is the correct wait here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
36 of the 38 remaining CI failures are "document … present in a sitemap", and the
message does not distinguish the two causes, which need opposite responses:

- ODRC has not yet flipped isGereedVoorPublicatie (the worker registers the
  uploaded file with the Documenten API asynchronously) — a stack/timing problem;
- or it is gereed and the burgerportaal still does not list it — a filtering or
  cache issue in ODBP.

So both sitemap assertions now report the document's ODRC state and how many
entries were actually searched. "not found among 0 entries, ODRC says
isGereedVoorPublicatie=false" and "not found among 40, gereed=true" are different
bugs; the report should not leave that to inference. documentReadiness never
throws — it exists to explain a failure, not to add one.

Also note in dump-odrc-state.sh that it runs after globalTeardown, so its document
counts legitimately read 0 in a CI dump and only the wiring lines mean anything
there. That dump did confirm the wiring is correct (documents_api_service and
gpp_search_service set, rsin set, url template set, one actieve gemeentelijst
organisatie), which rules out provisioning.

Local sitemap.feature stays 24/24.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@xiduzo
xiduzo force-pushed the explore/BDD-stagehand branch from 0a4242a to a0d8f14 Compare August 11, 2026 09:18
The suite was 39 min of a 54 min job, and the stack bring-up another 10.
Three changes, in order of what they buy:

- workers: 2 on CI. ubuntu-latest is 4 vCPU / 16 GB and the kind stack sits
  at roughly half of it, so there is room for a second browser. If the
  backend starves it shows up as 502s on documenten / exit 137 / beforeEach
  timeouts that pass in isolation — drop back to 1 rather than chase them.

- Pull requests run chromium only. firefox and webkit are 138 of the 310
  tests and catch engine differences, which move on the browsers' release
  schedule and not on a PR. All three run nightly at 02:00 UTC and on a
  workflow_dispatch with browsers: all.

- Cache the five app images, keyed on the app repos' resolved HEADs plus a
  hash of up.sh (which holds the build commands and the OPENZAAK_VERSION
  default). A hit skips ~8 of up.sh's ~10 min via its existing SKIP_BUILD.
  Staged only after a clean bring-up, written temp-then-renamed, so a
  half-built or half-saved tar can never be restored under a valid key.

The report needed to keep up: a chromium-only run must not read as a green
cross-browser one. The Confluence summary now says which browsers the run
covered, splits the count into scenarios (170) versus scenario × browser
runs (310), and breaks each row's status down per browser — 🔵 chromium,
🦊 firefox, 🧭 webkit, with ➖ for a browser the scenario never ran in
(excluded by tag, or the run was scoped). Skip reasons keep the glyph of
the browser they belong to. Single-browser runs drop the breakdown, which
would just repeat the status.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Playwright's JSON reporter does not merge a spec across projects: a
three-browser run emits the same scenario three times as sibling specs
sharing a location, each holding a single `test`. The publish script read
`spec.tests` as "this scenario on every browser", so every row showed one
lit glyph and the summary reported 304 scenarios for 304 test runs.

Fold specs on file:line:column before computing status and breakdown, and
link the merged row to the browser whose status won rather than to
whichever project serialised first.

Also drop `setup` from the browser list: it is a project, not a browser,
and it was hanging a dead `setup-` off all ~300 rows. Its own spec stays
in the table, without a breakdown.

The selfcheck fixture was hand-written in the merged shape, which is why
this slipped through; it now uses the shape the reporter really emits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

3 participants