test(e2e): 21 real Playwright tests + 4 exclusions close gate-19 137 -> 112 - #486
Merged
Conversation
…-> 112 gate-19 (e2e-coverage) was softwarecatalog's only failing gate. Baseline reproduced locally byte-identical to CI — 137 — against the CANONICAL gate package (freshly cloned .github@main), scoped HYDRA_GATE_BASE_REF=origin/beta, which is what CI resolves for the open development -> beta PR #197. start 137 -> end 112 (21 tests + 4 exclusions) Negative control, run on the final tree: with suite-wizard.spec.ts 112 file removed 118 (+6 = its exactly-6 @e2e anchors) file restored 112 Every one of the 14 new tests was proven able to FAIL by a planted true positive, each plant chosen so only its own test went red — e.g. `applicaties: []` in buildSuitePayload reddened only the submit test; renaming the third wizard step reddened only the step-labels test. Two needed the plant to remove TWO layers: Nextcloud core's SecurityMiddleware rejects an unauthenticated request before the controller body runs, so the anon-POST test only reddened once #[PublicPage] + #[NoCSRFRequired] were added as well. Full suite on an isolated single-owner rig: 90 passed, 4 skipped, 0 failed. The 4 skipped are pre-existing test.fixme cases, untouched. WHAT THE TESTS FOUND (all filed, none worked around) * #481 — every write through src/utils/adminApi.js failed CSRF. The helper sent no Nextcloud requesttoken, and none of its target controllers is #[NoCSRFRequired], so SEVEN shipped actions were unreachable from the UI: review submit, moderation approve/reject, federation peer add/remove/pull, EOL config save and "Sync now". GET is exempt, so every settings section rendered fine and only its buttons were dead. Fixed here, because the coverage could not exist without it: the same unchanged test goes green once the token is sent. src/store/modules/facets.js already used @nextcloud/axios and was never affected. * #482 — the suite wizard's success result is never rendered. onSuiteCreated sets showWizard = false in the same synchronous tick setResult() runs, and the dialog is under v-if, so the result phase is destroyed before any render flush. Not a race. The test asserts the two THENs that hold and DISCLOSES the third in a comment block rather than quietly dropping it. * #483 — two gemma-faceted-search requirements are unimplemented: empty facet dimensions are never disabled, and the facet cache is TTL-only with no invalidation path. Both are browser-observable, so both are left IN the count rather than excluded. * #484 — sbom-import.spec.ts carries 16 file-level @e2e tags in a docblock that says, verbatim, "excluded from Playwright coverage below". gate-19 credits them (.github#343), so the honest debt is 153, not 137. Deliberately NOT "fixed" here — converting 16 positive claims into exclusions inside a PR that lowers the gate count is indistinguishable from gaming it. * #485 — 23 whole-spec @e2e exclude markers retire 212 of 696 scenarios (30%), several on reasons that do not hold ("no live deployment in this pass"; Vue modals excluded from browser testing; "is an interaction"). THE 4 EXCLUSIONS All scenario-level, all in gemma-faceted-search, each with its own reason, and each verified not to leak: the sibling scenario sharing a requirement with the code-path-identity exclusion is still counted as uncovered (.github#356). They are the cases where no observation could decide the claim — a PHP array key between two services (the endpoint's _meta echoes no query), paging vs. scanning (byte-identical response, signal is a log line), code-path IDENTITY (two paths agreeing on every output are indistinguishable from one), and l10n KEY names (the browser only ever sees resolved values). Refused the exclusion where the requirement is merely broken or absent — those are issues above, not waivers. DEAD CODE src/views/gemmaviews/GemmaViewIndex.vue deleted. Not concluded from "no references found" — that is an absence claim. Proven positively: the bundle built WITH the file present is BYTE-IDENTICAL to the bundle built without it (md5 b6cc3b81..., 5,215,895 bytes), so webpack could not reach it from any entry. Positive control in the same bundle: OrganisationMergePanel 9 hits, "Preview merge" 3, SuitesIndexView 6 — the search does find live components. GemmaViewIndex: 0, and its unique string "No GEMMA views are available": 0. Cross-checked against the six ways a component can be reached in these apps — registry.js, customComponents.js, every string-keyed component/widgetKey/ target/handler value in manifest.json + manifest.d/*.json (13 names, enumerated in full), router.js, dynamic imports, and the bundle. The only surviving mention was a proposal that had already flagged it as orphaned. src/store/modules/view.js is now its only-consumer-less sibling and is also absent from the bundle; left in place deliberately — it fronts a backend view-enrichment API and deserves its own decision. Refs #481 #482 #483 #484 #485
Contributor
Quality Report — ConductionNL/softwarecatalog @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 128/128 | |||
| npm | ✅ | ✅ 718/718 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-11 16:42 UTC
Download the full PDF report from the workflow artifacts.
…stays honest
Follow-up to the previous commit on this branch. Two changes:
1. ADDS tests/e2e/spec-coverage/gemma-faceted-search.spec.ts — 5 tests covering
the facet CONTRACT and the panel's presence:
- all four GEMMA dimensions present, empty ones as [] not omitted
- an unsupported schema is rejected 400, naming the supported set
- a text query narrows the aggregated set
- a repeated identical request is served from cache; a different one is not
- the GEMMA panel renders beside the still-working free-text search box
2. REVERTS the four @e2e exclude markers added to
openspec/specs/gemma-faceted-search/spec.md in the previous commit.
WHY THE EXCLUSIONS ARE BEING DEFERRED
Editing that spec file pulls ALL 25 of its scenarios into this PR's diff scope
(gate-19 scopes by FILE, not by hunk). CI proved it: the previous commit made
this PR's own gate-19 report FAIL - 21, reproduced locally byte-identical.
Two of those 25 are unimplemented outright (#483: empty facet dimensions are
never disabled; the facet cache is TTL-only with no invalidation path). Both are
browser-observable, so neither can honestly carry an `@e2e exclude` — an
exclusion asserts "a browser cannot observe this", and for these two that
sentence is false. And no passing test can be written against behaviour that
does not exist.
So no honest version of this PR can be green while it edits that file. The four
exclusions — which are good, scenario-level, and individually reasoned — land
together with the #483 fixes instead. Cost: 4 scenarios of headline number.
Benefit: the gate keeps meaning what it says.
With the spec file untouched, gate-19 for this PR SKIPs as `na` ("the diff
touched NO spec file"). That is not a pass and is not claimed as one; the
honest figure remains the development -> beta scope.
beta scope: 137 -> 111 (26 scenarios, 19 test cases, 0 exclusions)
WHAT THE FACET FIXTURES CANNOT DO, AND WHY THAT IS RECORDED
The facet VALUE scenarios (per-dimension counts, OR-within / AND-across,
URL round-tripping of a selection, saved views) are NOT claimed. POSTing a
module with {"standaardVersies":["x"],"referentieComponenten":["y"]} returns 200
with BOTH arrays silently emptied — they are relation fields and OpenRegister
drops bare strings without erroring. Seeding them needs real `element` objects
plus `relation` rows. A facet test seeded with data the product cannot produce
would prove nothing, so those scenarios stay uncovered and counted.
`no-text-query-returns-facets-over-the-full-rbac-scoped-set` is also left
uncovered on purpose: it needs the unparameterised aggregate, which is the exact
entry the cache never invalidates. Measured — findAll saw 3 modules while the
endpoint answered {"cached":true,"totalMatched":1}. The three available ways to
make it green (wait out the 1800s TTL, assert >= instead of ==, or add a
cache-busting param and test a different scenario) were all rejected.
Refs #483
Contributor
Quality Report — ConductionNL/softwarecatalog @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 128/128 | |||
| npm | ✅ | ✅ 718/718 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-11 17:40 UTC
Download the full PDF report from the workflow artifacts.
…ache backend
CI caught this, not the dev rig: the test passed locally and failed on the
runner, on the first attempt and its retry.
The facet cache is `ICacheFactory::createDistributed(...)`, which degrades to a
NULL cache when Nextcloud has no memcache backend configured — nothing is
stored, so `_meta.cached` is always false and "a repeated identical request is
served from cache" has no observable behaviour there.
Measured on both sides rather than assumed:
dev rig `occ config:system:get memcache.local` -> \OC\Memcache\APCu, APCu
present; the flag flips false -> true reliably.
CI the shared workflow configures no memcache at all (zero mentions of
memcache/apcu in the job log); both calls returned `cached: false`.
The two tempting repairs were rejected. A `test.skip` guard on "is caching
available" can never be false on CI, so it would credit coverage that never
executes — the exact shape flagged fleet-wide. Asserting only that `cached` is
a boolean is an assertion that cannot fail.
So the scenario is left uncovered and COUNTED, with the reasoning recorded at
the point where the test used to be. It becomes coverable when the CI instance
configures a cache backend.
beta scope: 137 -> 112 (25 scenarios, 18 test cases, 0 exclusions)
Negative control on the final tree:
with tests/e2e/spec-coverage/gemma-faceted-search.spec.ts 112
file removed 116 (+4 = its 4 anchors)
file restored 112
Refs #483
Contributor
Quality Report — ConductionNL/softwarecatalog @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 128/128 | |||
| npm | ✅ | ✅ 718/718 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-11 18:02 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 this is
gate-19 (
e2e-coverage) was softwarecatalog's only failing gate. Thiscloses a quarter of it with real Playwright tests, not with a lower number.
Baseline reproduced locally byte-identical to CI using the canonical
gate package (freshly cloned
.github@main, 1735 lines), scopedHYDRA_GATE_BASE_REF=origin/beta— what CI resolves for the opendevelopment → betaPR #197 (run 31478267110).This PR targets
development, so gate-19 here runs against a diff containingone spec file and will pass over a nearly-empty scope. That green is not
the win. The number that matters is the
development → betascope above, andit is 112, not 0.
Negative control
Run on the final tree, canonical checker:
suite-wizard.spec.ts@e2eanchors)Every test was proven able to fail
Each of the 14 got a planted true positive, chosen so that only its own test
went red —
applicaties: []inbuildSuitePayloadreddened only the submittest; renaming the third wizard step reddened only the step-labels test. All
plants removed and verified absent from source and from the built bundle.
Two tests needed the plant to remove two layers: Nextcloud core's
SecurityMiddlewarerejects an unauthenticated request before the controllerbody runs, so the anon-POST test only went red once
#[PublicPage]+#[NoCSRFRequired]were added as well. Worth knowing generally — a plantinside the controller cannot prove your auth test works.
Full suite on an isolated single-owner rig: 90 passed, 4 skipped, 0 failed.
The 4 skipped are pre-existing
test.fixmecases, untouched.What the tests found — all filed, none worked around
adminApi.jsfailed CSRF. Norequesttokenwas sent and no target controller is
#[NoCSRFRequired], so seven shippedactions were unreachable from the UI: review submit, moderation
approve/reject, federation peer add/remove/pull, EOL config save and
"Sync now". GET is exempt, so every settings section rendered fine and only
its buttons were dead. Fixed in this PR because the coverage could not
exist without it — the same unchanged test goes green once the token is sent.
unmounts in the same tick
setResult()runs). The test asserts the two THENsthat hold and discloses the third in the file rather than quietly
dropping it.
facet dimensions never disabled; cache is TTL-only, never invalidated). Both
are browser-observable, so both are left in the count rather than excluded.
sbom-import.spec.tscarries 16 file-level@e2etags in adocblock that says, verbatim, "excluded from Playwright coverage below".
gate-19 credits them (
.github#343), so the honest debt is 153, not 137.@e2e excludemarkers retire 212 of 696 scenarios(30%), several on reasons that do not hold.
#484 and #485 are deliberately not touched here: converting positive
coverage claims into exclusions inside a PR that lowers the gate count is
indistinguishable from gaming it.
The 4 exclusions
All scenario-level, all in
gemma-faceted-search, each with its own reason,and each verified not to leak — the sibling scenario sharing a requirement with
the code-path-identity exclusion is still counted as uncovered, so
.github#356does not apply. They are the cases where no observation coulddecide the claim: a PHP array key between two services (the endpoint's
_metaechoes no query), paging vs. scanning (byte-identical response, signal is a log
line), code-path identity, and l10n key names.
Where a requirement is merely broken or absent, an issue was filed instead.
Dead code:
GemmaViewIndex.vueNot concluded from "no references found" — that is an absence claim. Proven
positively: the bundle built with the file is byte-identical to the
bundle built without it (
md5 b6cc3b81…, 5,215,895 bytes), so webpack cannotreach it from any entry.
Positive control in the same bundle:
OrganisationMergePanel9 hits,"Preview merge"3,SuitesIndexView6 — the search does find livecomponents.
GemmaViewIndex: 0, and its unique string"No GEMMA views are available": 0.Cross-checked against all six ways a component can be reached here —
registry.js,customComponents.js, every string-keyedcomponent/widgetKey/target/handlervalue acrossmanifest.json+manifest.d/*.json(13 names, enumerated in full),router.js, dynamicimports, and the bundle.
src/store/modules/view.jsis now its consumer-less sibling and is also absentfrom the bundle — left in place deliberately, it fronts a backend
view-enrichment API and deserves its own decision.
Newman verdict
Integration Tests (Newman)reports skipped, and a skipped job is noverdict — so I checked rather than accepting the comment. It should stay
off, and the recorded reason is accurate. The only
*.postman_collection.jsonunder the configured path is titled *"OpenRegistersoftwarecatalog; its
base_urlis the dev container's hostname, the workflowoverrides it with
http://localhost:8080without the/index.php/apps/openregister/apisuffix (so every request would 404), and onerequest uploads a file from a
/var/www/html/custom_apps/...path that doesnot exist on the runner.
Separately worth knowing: the app's own collection,
postman/softwarecatalogus-tests.json, can never run under this workflow atall — the run step is a flat glob on a filename suffix it does not have, in
a directory that is not the configured path, while the validator counts
collections recursively. A repo can satisfy the validator and run zero
collections.
Not done
Nothing was weakened: no baselines, no skipped or deleted tests, no
mode: 'serial', no widened timeouts, nowaitForLoadState('networkidle').A first-run-wizard dismiss helper was written, found not to work when tested
against the enabled app, and reverted rather than shipped with a docblock
claiming a capability it did not have. Local rigs need
occ app:disable firstrunwizard; CI does not have it enabled.Refs #481 #482 #483 #484 #485
1. RETRACTED: "this PR's gate-19 passes over an empty scope"
That was wrong, and CI proved it. The first push made this PR's own gate-19
report
FAIL — 21, not a pass. gate-19 scopes by file: touchingopenspec/specs/gemma-faceted-search/spec.mdto add four exclusions pulledall 25 of that spec's scenarios into this PR's diff. A gate-19 remediation
PR is exactly the case where the "empty scope" intuition fails, because closing
findings means editing spec files.
Resolution: the four exclusions were removed from this changeset. Two of
that spec's 25 scenarios are unimplemented (#483), and an
@e2e excludeasserts"a browser cannot observe this" — a sentence that is false for both. So no
honest version of this PR could be green while editing that file. The exclusions
land with the #483 fixes instead.
With no spec file touched, gate-19 now SKIPs as
na— "the diff touched NOspec file". That is not a pass and is not claimed as one. The number that
means anything is the
development → betascope.2. Updated numbers
development → betascopena(skip) — no verdictNegative control re-run on the final tree, perturbing a metric the gate counts:
gemma-faceted-search.spec.ts3. Two traps hit while proving the tests can fail
Both first read as "my tests are blind" — the wrong conclusion was one step away.
opcache.revalidate_freq = 60on thenextcloud:34-apacheimage. A PHPplant is invisible for up to a minute; the first planted run passed 5/5 over
genuinely broken code. Verify the plant is LIVE before running the assay.
(
FacetService::DIMENSIONS, a bare literal inFacetController::parseFilters(),and the per-object map literal). The plant was real and changed nothing on the
response path.
4. gate-16 re-measured with an explicit base (
.github#361)check_spec_coverage.py:508defaultsHYDRA_GATE_BASE_REFtoorigin/development, so a full-scope run on development diffs against nothingand prints PASS. Re-measured here with the base named:
origin/developmentorigin/betasoftwarecatalog's gate-16 is honestly clean — the 0 is over a large real scope,
not over an empty one.
#361is real in the package but does not flatter thisapp.