test(e2e): pin the browser-catalogue contract from the browser's side - #424
Merged
Conversation
The l10n rollout fixed a defect no existing check could see: `l10n/<locale>.js`
was missing, so `t('<app>', key)` had nothing registered and handed the key
back — the whole interface rendered English regardless of the user's language,
while every server-rendered string was translated. Nothing errored.
Six apps in the fleet ran an l10n check that passed the entire time, because
it reads the JSON — the half that was never broken. A check that validates the
SOURCE cannot see that the ARTEFACT the runtime loads does not exist, so this
test asserts from the browser instead:
1. GET l10n/<locale>.js returns 200, is an OC.L10N.register call, and names
the CURRENT app id. (Raw JSON out of an app directory is a 404, which is
what made every translation unreachable.)
2. The running app has that catalogue registered, and t() resolves a real
key through it rather than falling back to returning the key.
Must-fail verified: delete l10n/nl.js and both scenarios fail — the first on
404, the second on the missing registration.
Written to be identical in every app: the app id is read from
appinfo/info.xml at run time rather than hardcoded, so it survives a rename —
and a catalogue registered under a pre-rename id, which `t()` silently
ignores, fails scenario 1. No fixture strings either: the assertion picks a
translated key out of the app's own registered catalogue at run time, so it
does not need editing when copy changes.
The file is meant to be byte-identical in every app, so it has to satisfy the strictest formatter in the fleet. decidiq's format check objected; this is its prettier output, verified to also satisfy every other app that runs one.
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ✅ | ||||
| phpmd | ❌ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| build | ❌ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| format | ❌ | ||||
| check-l10n-js | ✅ | ||||
| composer | ⏭️ | ✅ 106/106 | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-23 20:32 UTC
Download the full PDF report from the workflow artifacts.
The spec fetched /custom_apps/<app>/l10n/<locale>.js. That is right on a dev box, where apps are bind-mounted under custom_apps — and wrong in CI, which checks the app out under apps/. So it failed for a reason that had nothing to do with the catalogue. It now reads OC.appswebroots[appId] from the running instance and fetches relative to that. The assertion gets stronger rather than weaker: an app that does not resolve at all has no webroot entry, which is how the decidesk -> decidiq mount drift surfaced.
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 625/625 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-23 20:59 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 625/625 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-23 22:01 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 625/625 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-23 22:45 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 625/625 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-24 00:48 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.
Adds the test that would have caught the defect the l10n rollout just fixed.
Why an e2e, when an l10n check already exists
Nextcloud serves only
l10n/<locale>.jsto a browser — raw JSON out of an app directory is a 404. With no.js,t('<app>', key)has nothing registered and returns the key, so the entire interface renders English no matter which language the user picked, while every server-rendered string is translated. Nothing errors and nothing logs.Six apps in the fleet ran a
test:l10nstep that passed throughout — it reads the JSON, the half that was never broken. A check that validates the source cannot see that the artefact the runtime loads does not exist. So this asserts from the browser's side:GET l10n/<locale>.js→ 200, is anOC.L10N.registercall, and names the current app id.t()resolves a real key through it rather than falling back to returning the key.Must-fail control
Delete
l10n/nl.jsand both scenarios fail — the first on the 404, the second on the missing registration:Design
The file is identical in every app. The app id is read from
appinfo/info.xmlat run time instead of hardcoded, so it survives a rename — and a catalogue registered under a pre-rename id, whicht()silently ignores, fails scenario 1. No fixture strings: the assertion picks a translated key out of the app's own registered catalogue at run time, so it needs no edit when copy changes.