8.1 KiB
Web UX inspection workbench
tools/web-ux is a development-only Playwright workbench for repeatable visual inspection of the
real Web Workspace. It does not add a Yoi product Skill, Flow, Runtime capability, or browser
automation route.
The workbench produces a review context bundle rather than treating a screenshot as evidence by itself. Every capture records the persona, route, viewport, theme, intended user goal, expected data state, sanitized document URL/status, console/page/request failures, screenshot hashes, an accessibility snapshot, source revision, and browser version.
Environment
Enter the repository dev shell. The shell supplies the Nix-pinned Chromium build and sets
PLAYWRIGHT_BROWSERS_PATH; Playwright does not download a browser at runtime.
nix develop
cd tools/web-ux
deno task check
deno task test
deno task test:browser
test:browser starts a deterministic fixture server owned by the test, captures distinct owner and
non-owner contexts, verifies the review bundle, and proves server/browser cleanup. It must run
inside nix develop so it uses the pinned browser.
The npm Playwright version in deno.json must match pkgs.playwright-driver.version in the pinned
Nixpkgs input. Update both as one toolchain change.
Scenario contract
Scenarios are reviewed JSON files under scenarios/. A scenario fixes:
- personas and whether each uses an isolated anonymous context or a local Playwright storage-state file;
- explicit routes and user goals;
- expected data state, viewports, theme, locale, timezone, and reduced-motion mode;
- an explicit readiness condition for every route and optional interaction/capture-point conditions;
- selectors and exact environment-derived text that must be redacted;
- optional processes owned by the capture command, including an HTTP readiness URL.
${UPPER_CASE_ENV} values are expanded at runtime. URLs with embedded credentials are rejected.
Route readiness is bounded and retried twice; it never relies on a fixed sleep. network-idle is
available but should be used only for screens whose contract actually reaches idle. Prefer a stable
screen-owned selector.
workspace-control-plane.json expects:
export WEB_UX_BASE_URL='http://127.0.0.1:5173'
export WORKSPACE_ID='<workspace-id>'
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
Authentication fixtures
Authentication state is local sensitive material stored under $XDG_STATE_HOME/yoi/web-ux/auth/,
outside the Repository and Workdir. Files are written with mode 0600, state contents are never
copied into a review bundle, and the CLI never prints cookies or credentials. Each profile has a
sidecar binding it to the exact persona and base URL origin with a 12-hour default expiry. Capture
fails explicitly when metadata is missing, the origin differs, or the profile has expired; it never
silently reuses or refreshes that state.
For an interactive Passkey/browser login:
deno task web-ux auth \
--scenario scenarios/workspace-control-plane.json \
--persona owner
The command opens Chromium at the configured login route, waits up to five minutes for the
scenario's success URL, saves storageState, and closes the browser in finally. Repeat for
non-owner using a real account with that permission projection.
A test fixture may already provide Playwright-compatible { cookies, origins } state. Import it
without putting its value on the command line:
deno task web-ux auth \
--scenario scenarios/workspace-control-plane.json \
--persona owner \
--import-state /private/path/owner-state.json \
--expires-in-hours 8
Delete both the profile and its metadata when it is no longer needed:
deno task web-ux auth \
--scenario scenarios/workspace-control-plane.json \
--persona owner \
--delete
Do not place passwords, bearer tokens, private keys, WebAuthn material, or inline cookies in a
scenario, process arguments, a Repository URL, or redact.text. redact.text is only a final
defense for a secret already supplied through an environment-owned fixture; it is not a credential
transport.
Capture and inspect
Capture a stable multi-persona bundle:
deno task web-ux capture \
--scenario scenarios/workspace-control-plane.json \
--output ../../target/web-ux \
--run-id before-change
Use filters for a bounded feedback loop:
deno task web-ux capture \
--scenario scenarios/workspace-control-plane.json \
--output ../../target/web-ux \
--run-id ticket-list-after \
--personas owner,non-owner \
--routes tickets \
--viewports desktop
The command exits 2 when it produced evidence but observed UI/tool errors, and exits 1 when
capture itself failed. It continues other route/persona captures after a bounded route failure.
Inspect:
review-context.jsonfor the exact context, hashes, HTTP status, retained/truncated diagnostic counts, route and capture-point readiness, and the redacted interaction sequence;contact-sheet.pngthrough its manifestworkdirPathwith an image-capable reviewer for composition, hierarchy, density, clipping, empty/error states, and permission-specific affordances;- each
accessibility.mdthrough its manifestworkdirPathfor landmark/name/state evidence that a screenshot cannot prove; process-logs/when the scenario owns a server process. Each stdout/stderr stream is redacted, capped at 1 MiB, and paired with truncation metadata.
The implementing agent must inspect the actual contact sheet (for example with ViewImage), record
concrete findings, fix them, recapture under the same persona/route/viewport filters, and inspect
the new evidence. Playwright success alone is not visual acceptance.
Compare before and after
deno task web-ux compare \
--before ../../target/web-ux/before-change/review-context.json \
--after ../../target/web-ux/after-change/review-context.json \
--output ../../target/web-ux/before-vs-after
comparison.html and comparison.png show before, after, and pixel diff side by side.
comparison.json records changed-pixel counts, dimension mismatches, unmatched capture keys, and
diff hashes. Pixel differences are orientation evidence, not a correctness verdict; explain expected
animation/font/data changes and inspect the actual UI.
Capture keys are stable across runs: persona / route / viewport / capture-point. Keep those
identities unchanged when comparing the same user task.
Process and artifact cleanup
The capture command owns only processes declared in its scenario. It starts them without a shell, records bounded/redacted output, and terminates the process and descendants on success, capture failure, or interruption observed by the command. It never stops an existing Yoi Server or Runtime that it did not start.
Old complete review bundles can be removed without touching auth state or arbitrary directories:
deno task web-ux cleanup --output ../../target/web-ux --keep 5 --older-than-days 14 --dry-run
deno task web-ux cleanup --output ../../target/web-ux --keep 5 --older-than-days 14
Cleanup recognizes only directories containing review-context.json. The repository target/ tree
is ignored by Git, while authentication state remains outside the repository. capture defaults to
target/web-ux when --output is omitted. Keep a bundle outside Git or publish it through the
approved immutable artifact channel when durable review evidence is required.
Adding a scenario
- Name the concrete user task and expected data state; do not write “looks correct”.
- Use the smallest persona/route/viewport matrix that proves the intended contract, including owner/non-owner/anonymous boundaries when permissions affect composition.
- Choose a screen-owned readiness selector or response. Avoid arbitrary sleeps.
- Add capture points only for meaningful visual states (initial, expanded detail, error, empty, and so on).
- Mark sensitive DOM regions with
[data-web-ux-redact]or scenario selectors; never use review artifacts to transport secrets. - Run
deno task check,deno task test, one real capture, and inspectcontact-sheet.pngplusreview-context.json.