Files
clide/tools/ui
jpmschweitzerandClaude c8d019e630 add web WASM Playwright harness for UI driving
tools/ui/ — scripts + Playwright config that let Claude Code (and
humans) drive the Flutter WASM build in a real browser. The point is
to avoid screenshot round-trips: every interactive widget in clide
ships a Semantics wrapper anyway (a11y requirement), so the automation
layer just queries the `flt-semantics[aria-label]` DOM.

  * build.sh — `flutter build web --wasm` from app/
  * serve.sh — `python3 -m http.server 4280` in the background.
    Before binding, kills any stale listener on the port (orphans
    from earlier failed runs no longer accumulate).
  * stop.sh — port-based kill; escalates to SIGKILL after 300ms.
    The pidfile is now advisory — port ownership is the source of
    truth.
  * driver.ts — `ClideDriver` class with `byLabel`, `click`, `type`,
    `readText`, `screenshot`, `dumpSemanticsTree`, and
    `waitUntilReady` that auto-clicks the `flt-semantics-placeholder`
    so the semantic tree is populated before queries.
  * tests/smoke.spec.ts — first driver test; asserts welcome +
    disconnected labels surface in the Semantics DOM.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-21 15:48:07 +02:00
..

clide UI harness

A Playwright-based driver that drives the Flutter WASM build of clide through its semantic DOM tree (the same tree screen readers use). Used by Claude Code to "actually use the app" without screenshot round-trips, and by CI to catch web-regression bugs.

One-time setup

cd tools/ui
npm install
npx playwright install chromium

Dev loop

# From repo root:
make ui-dev                       # build web + start local server :4280
cd tools/ui && npx playwright test

# When done:
make ui-stop                       # kill the local server

Or the one-shot smoke:

make ui-smoke                      # build + serve + run smoke + stop

Driver surface

import { ClideDriver } from '../driver';

test('...', async ({ page }) => {
  const clide = new ClideDriver(page);
  await clide.goto('/');

  await clide.click('Open project');
  await clide.type('Name', 'My project');
  const text = await clide.readText('disconnected');

  await clide.screenshot('out/my-state.png');
  const tree = await clide.dumpSemanticsTree();
});

All lookups go through Flutter's semantics tree (flt-semantics[aria-label]). This only works because every interactive widget in clide emits a Semantics(label:, hint:, button:) wrapper — a requirement that's baked in for screen-reader support and gets enforced by test/a11y/semantic_coverage_test.dart.

CI

make ui-smoke is the CI entry — builds the WASM bundle, runs the harness, cleans up. Enabling Gitea Actions will start running this on every push.