| name | playwright-cli-setup |
| description | THE DEFAULT tool for ad-hoc / interactive Vela browser work — explore or test the app, screenshot a state, reproduce a bug, poke a selector, drive presenter/gallery flows, verify a UX change, run bench/vela-interaction-bench.sh. Drives a live offline Vela render via Playwright CLI (@playwright/cli), a persistent warm browser you steer one command at a time (open, snapshot, click, press, eval), inspecting state + output between steps. Prefer this over the committed `vela-drive.js` scripts (skill: vela-live-render) — those are for repeatable, committed automation only (CI, benchmark, recorded demo), NOT one-off exploration. Also prefer over writing throwaway Playwright .js files. Covers container setup (blocked CDNs, file:// block, pinned Chromium) and per-agent session isolation for parallel browser tests. |
Playwright CLI ↔ Vela setup (this container)
@playwright/cli keeps a persistent browser as a background session (-s=<name>)
and you drive it one command at a time — open, goto, snapshot, click e15,
press ArrowRight, eval "…" — reading state between steps instead of running a
blind script. window globals survive across invocations, so a helper installed
once (e.g. a timing harness) stays live. Output (snapshots, console logs) is written
to .playwright-cli/ (gitignored); read only what you need.
It is a different package from playwright and from Playwright MCP. Prefer it
here for interactive/exploratory driving; it's cheaper on context than MCP (nothing
streams into the conversation unless you read a file).
Setup — do this once per fresh container
1. Restore the locked deps, then install the CLI isolated (see Supply-chain
safety below for why it's not committed):
npm ci --ignore-scripts
npm install --no-save --no-audit --no-fund --ignore-scripts @playwright/cli@0.1.15
2. Config is already committed at .playwright/cli.config.json (the default
config path, read from the repo root). It solves the two things that otherwise fail:
{
"allowUnrestrictedFileAccess": true,
"browser": {
"browserName": "chromium",
"launchOptions": {
"executablePath": "/opt/pw-browsers/chromium-1194/chrome-linux/chrome",
"headless": true,
"args": ["--no-sandbox"]
}
}
}
Recreate it if missing. Update executablePath if the image's Chromium moves
(ls /opt/pw-browsers/).
Supply-chain safety (why @playwright/cli is NOT committed)
This repo pins its supply chain via a committed package-lock.json (exact
versions + sha512), npm ci --ignore-scripts in CI, npm audit gating, and a
7-day release-cooldown (.npmrc: ignore-scripts=true, see docs/SECURITY.md).
@playwright/cli@0.1.15 transitively depends on a days-old alpha of
playwright (nested in its own node_modules, so it does not override the
repo's locked top-level playwright). Committing it would pull that fresh alpha
into the audited, shipped dependency tree — against the cooldown policy. So it is
deliberately kept out of package.json / package-lock.json and installed as
an isolated, dev/test-only tool. The install is hardened accordingly:
@playwright/cli@0.1.15 — exact version pin, no range (no surprise upgrades).
--ignore-scripts — blocks pre/post-install hooks (belt-and-suspenders with
.npmrc: ignore-scripts=true); the #1 npm supply-chain vector.
--no-save — never writes to package.json/package-lock.json; the audited,
shipped tree stays exactly as locked. npm still verifies the registry sha512 on
download.
- Ephemeral & unshipped —
node_modules/ is gitignored; the CLI never enters
the Vela artifact, a release, or CI's locked install.
- Always run
npm ci --ignore-scripts first so the top-level playwright/etc.
match the lockfile (an ad-hoc npm install earlier can drift them).
Before bumping the pin, check the new version's transitive playwright dep
(npm view @playwright/cli@<v> dependencies) and prefer a version that resolves a
stable playwright once one ships.
Drive a Vela deck
node tools/vela-dev/scripts/render-offline.js examples/vela-demo.vela /tmp/vout
npx playwright-cli -s=vela open "file:///tmp/vout/render.html"
npx playwright-cli -s=vela eval "({b:!!window.__velaBooted, blocks:document.querySelectorAll('[data-block-type]').length})" --raw
npx playwright-cli -s=vela press f
npx playwright-cli -s=vela press ArrowRight
npx playwright-cli -s=vela screenshot --filename=/tmp/slide.png
npx playwright-cli -s=vela eval "(document.querySelector('[data-block-type=heading]')||{}).textContent" --raw
npx playwright-cli list
npx playwright-cli -s=vela close
npx playwright-cli kill-all
Tip: node_modules/.bin/playwright-cli skips npx resolution overhead in loops.
Parallel agents — isolating concurrent browser tests
Multiple agents can drive Vela at once, but they share one machine-wide session
registry (~/.cache/...) and one per-cwd output dir. Verified isolation ladder,
weakest → strongest:
| axis | how | what it isolates | gotcha |
|---|
| session name | -s=agentA vs -s=agentB | the browser + page state (independent — one advancing slides doesn't move the other) | same registry & same .playwright-cli/ output dir |
| registry | PWTEST_DAEMON_SESSION_DIR=/tmp/agentA-daemon (per agent) | the whole session daemon — an agent's sessions are invisible to another's list/close-all/kill-all | must set it on every command for that agent |
| workspace/cwd | run each agent from its own dir / git worktree | session namespace and the .playwright-cli/ output dir (both keyed off findWorkspaceDir(cwd)) | needs a separate checkout |
Recommended patterns
- Best: give each parallel agent its own git worktree (the Agent tool's
isolation: "worktree"). Different cwd ⇒ different workspace ⇒ separate session
namespace and separate .playwright-cli/ output, for free. Add a unique
-s=<name> per agent too.
- Same cwd: give each agent a unique
-s=<name> and a unique
PWTEST_DAEMON_SESSION_DIR — otherwise one agent's close-all/kill-all kills
every agent's browsers (the single biggest footgun). Output files still land in
the shared .playwright-cli/ (timestamped names rarely collide, but they intermix).
Two things that make this easy here: the offline file:// render needs no
port, so parallel sessions never collide on a port (unlike serving over HTTP); and
each deck can render to its own /tmp/<agent>-render dir. Cost: each session is a
full Chromium (~200-300 MB RAM) — cap concurrency to the box, and never use the global
close-all/kill-all from a parallel agent unless its registry is isolated.
Vela-specific hooks & selectors (for eval / interaction)
| what | how |
|---|
| boot done | window.__velaBooted === true and #root innerText non-empty |
| headless UI battery | await window.__velaRunUITests() (see part-uitest.jsx) |
| current slide blocks | [data-block-type] elements (text signature changes on nav) |
| current heading | [data-block-type=heading] |
| in presentation? | <header> is removed while presenting |
| gallery open? | [data-testid=gallery-close] present |
| gallery scroll rail | [data-scroll-container] |
| enter/exit present | key f (or F5); Escape exits |
| next / prev slide | ArrowRight / ArrowLeft |
| open gallery | key g |
Note: window.__velaGetCurrentSlide is gated behind local mode (off in offline
renders) — use the DOM signals above instead.
Gotchas already solved (don't rediscover)
file:// is blocked by default → "Access to file: protocol is blocked".
Fixed by allowUnrestrictedFileAccess: true in the config (or env
PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS=1). Config is read at open
time — if you change it, close and re-open the session.
- Bundled playwright-core wants a newer Chromium than the image ships
(expects ~1229, image has 1194) → the CDN download is blocked, so the config's
executablePath points at the pinned build. Don't run playwright install.
- CDNs (esm.sh / React / lucide / Babel) are blocked → never use
serve.py's
default importmap HTML; always render-offline.js (vendored UMD + Node transpile).
- One harmless console error on every load (
ERR_INVALID_URL / font fetch) —
ignore it; the app renders fully.
- Don't
pkill background jobs from a Bash tool here — the sandbox may SIGKILL
the shell (exit 144). Use playwright-cli close / kill-all for browsers.
Ready-made benchmark
bench/vela-interaction-bench.sh uses exactly this setup to measure slide-nav /
interaction latency and thumbnail-scroll FPS. See bench/README.md.