From 82d22ae5fc613d12cdc8fb4333c3cda4b4b3ca7b Mon Sep 17 00:00:00 2001 From: probe Date: Fri, 23 Oct 2026 13:34:10 +0800 Subject: [PATCH 1/2] Install Chromium system dependencies in the browser gate The Linux browser gate installed the pinned Chromium revision but none of its system dependencies, so the runner had no CJK font available. The WebUI is Chinese, and every Chinese glyph in the committed Linux baselines rendered as a tofu box. `playwright install --with-deps chromium` installs Playwright's own dependency set for the platform, which for ubuntu24.04-x64 includes fonts-wqy-zenhei. The command lives in the npm script rather than in a workflow file on purpose: both `Source verification` and `WebUI npm preview` reach the gate through `pnpm verify --profile full`, so the script is the single place that decides what the gate installs. Putting it in one workflow would let the other path drift. On macOS and Windows `registry.installDeps` has no matching branch and returns without installing anything, so local development is unaffected. --- package.json | 2 +- .../webui/docs/verification/browser-suite.md | 18 ++++++++++++++++-- packages/webui/test/browser/README.md | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index adc2ff44d..5a6b19e17 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "check:webui-dependency": "node scripts/check-webui-dependency.mjs", "test:webui": "node scripts/run-vitest-suite.mjs webui", "test:webui-package": "node scripts/verify-webui-package.mjs", - "test:webui-browser": "playwright install chromium && playwright test packages/webui/test/browser", + "test:webui-browser": "playwright install --with-deps chromium && playwright test packages/webui/test/browser", "typecheck:webui": "pnpm --filter @mavis/webui typecheck:server && pnpm --filter @mavis/webui typecheck:client && pnpm --filter @mavis/webui typecheck:test", "check:source": "node scripts/source-inventory.mjs", "check:plugin": "node tools/plugins/validate-plugin.mjs", diff --git a/packages/webui/docs/verification/browser-suite.md b/packages/webui/docs/verification/browser-suite.md index 865583c36..2c2ee1021 100644 --- a/packages/webui/docs/verification/browser-suite.md +++ b/packages/webui/docs/verification/browser-suite.md @@ -4,7 +4,9 @@ `styles.css` and files under `assets/` — in a real browser. The deterministic harness server serves those built assets from the client output directory and refuses paths outside that directory. Run `pnpm build:webui` first; the script installs the -lockfile-pinned Chromium revision when it is missing. The deterministic cases are evidence level 4 in +lockfile-pinned Chromium revision when it is missing, and on Linux also installs Chromium's system +dependencies, which include the CJK fonts the Chinese UI renders with. The deterministic cases are +evidence level 4 in [`gates.md`](gates.md): the built client and its transport wiring are real, the server behind them is not in the deterministic fixture cases. `native-runtime.spec.mjs` additionally boots the real assembly, loopback service and native PTY bridge in a separate process with an isolated, @@ -99,10 +101,22 @@ pnpm build:webui pnpm test:webui-browser ``` +The screenshots are only comparable within one font environment. The gate installs Chromium's +Linux system dependencies, which include the CJK fonts the Chinese surfaces need; on a runner +without them the browser has no glyph to draw for Chinese text and renders tofu boxes instead. +The committed baselines therefore record real Chinese glyphs, and a reviewer reading a diff can +read the Chinese in it. The same applies in reverse: regenerating the baselines anywhere that +does not install those fonts produces boxes, and the comparison against the committed baselines +fails. Treat a font change on the runner as a baseline change, not as a rendering difference to +be accepted. + To deliberately regenerate only these baselines, use `pnpm exec playwright test packages/webui/test/browser/visual-baselines.spec.mjs --update-snapshots` on Linux CI Chromium, inspect every resulting image, then run the same command without -`--update-snapshots` to compare. A passing fixture screenshot proves the built client's rendered +`--update-snapshots` to compare. Regenerate on a Linux environment that has the gate's fonts +installed — `pnpm exec playwright install --with-deps chromium` before the run — so the new +baselines carry the same glyphs the committed ones do. A passing fixture screenshot proves the +built client's rendered state under the in-page deterministic transport; it does not prove the production service or runtime path. Those boundaries require the integration suites described above. diff --git a/packages/webui/test/browser/README.md b/packages/webui/test/browser/README.md index c6c1f68a8..8baab7d8a 100644 --- a/packages/webui/test/browser/README.md +++ b/packages/webui/test/browser/README.md @@ -5,7 +5,7 @@ in-page transport). Run the directory, not a hand-kept list: Playwright collects ```sh pnpm build:webui # the suite drives the built client -pnpm test:webui-browser # installs the pinned Chromium revision when missing +pnpm test:webui-browser # installs the pinned Chromium revision and, on Linux, its system dependencies ``` How the fixture works, what the suite proves and what it does not, and which environment variables From b7bb7f26b8d53fc49df97ec01c62eb861095250a Mon Sep 17 00:00:00 2001 From: probe Date: Fri, 23 Oct 2026 13:48:54 +0800 Subject: [PATCH 2/2] Regenerate the Linux visual baselines with real Chinese glyphs The baselines merged in #236 were rendered on a runner that had no CJK font at all, so every Chinese glyph in them is a tofu box. This commit replaces all 14 Linux screenshots with the same surfaces rendered with Playwright's Linux system dependencies installed, which include fonts-wqy-zenhei. The Chinese in the new baselines is readable text. Regenerated on ubuntu-latest with `playwright install --with-deps chromium` and `--update-snapshots`, three times. Under Playwright's own comparison (pixelmatch, threshold 0.1) the three runs are identical: zero differing pixels in all 14 images, in every pair of runs. At the byte level two images differ between runs, by one and by nine raw pixels respectively; both are far below the comparator threshold, so the gate result does not depend on which run supplied the file. No assertion, expectation or pixel threshold changed, and the file names and count are unchanged. --- ...d-and-backdrop-dismissal-works-1-linux.png | Bin 30465 -> 49598 bytes ...nd-previews-local-fixture-data-1-linux.png | Bin 32536 -> 53743 bytes ...and-sends-advanced-HTTP-config-1-linux.png | Bin 32700 -> 53190 bytes ...mits-through-plugin-management-1-linux.png | Bin 28738 -> 48502 bytes .../composer-command-overlay-linux.png | Bin 25938 -> 40487 bytes .../embedded-browser-panel-linux.png | Bin 12336 -> 19257 bytes .../mcp-management-linux.png | Bin 22030 -> 33268 bytes .../plugin-marketplace-linux.png | Bin 18833 -> 34216 bytes .../progress-panel-linux.png | Bin 6176 -> 7368 bytes .../settings-dialog-linux.png | Bin 27103 -> 103346 bytes .../shell-and-rail-linux.png | Bin 36639 -> 49111 bytes .../skill-management-linux.png | Bin 20714 -> 29531 bytes .../skill-marketplace-linux.png | Bin 17314 -> 23755 bytes .../transcript-and-composer-linux.png | Bin 45331 -> 62571 bytes 14 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/webui/test/browser/plugin-catalogue-layout.spec.mjs-snapshots/Agent-add-form-is-centered-fields-are-aligned-and-backdrop-dismissal-works-1-linux.png b/packages/webui/test/browser/plugin-catalogue-layout.spec.mjs-snapshots/Agent-add-form-is-centered-fields-are-aligned-and-backdrop-dismissal-works-1-linux.png index a187c9db7aca73ef97bd2d730db74ad83ed0ed09..ebf63c4f7c8be70c70062e4ec5f2aad17a9411da 100644 GIT binary patch literal 49598 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