ADR 0002
Edit this pageStatus#
Accepted, 2026-07-28
Context#
VibeLens needs a headless browser to render a dev server page and report four things: pixels, console errors and warnings, uncaught page exceptions, and failed network requests. The realistic candidates were Playwright and Puppeteer; both drive Chromium over the DevTools protocol and both have mature Node APIs.
The requirements that separate them:
- Device emulation as a first-class concept. The three viewport presets need
deviceScaleFactor: 2,isMobileandhasTouchfor mobile and tablet, so pages render as they would on a handset, meta-viewport handling included.browser.newContext()takes all of these as plain options. - Deterministic captures. Playwright contexts accept
localeandtimezoneId, which the code pins toen-USandUTCso two inspections of the same page are comparable. - Network-level and HTTP-level failures as separate events. A 404 image is a
successful request with a failing status, so
requestfailedalone is not enough. Playwright exposes bothrequestfailedandresponse, whichbrowser.tsuses together, deduplicating by URL plus failure text. - A load-state primitive that can be waited on and abandoned. The capture waits
for
networkidlewith a 5 s cap and swallows the timeout, because an app with a websocket never reaches it.page.waitForLoadState(state, { timeout })is exactly that. - A predictable browser install story. One documented command,
npx playwright install chromium, and a missing binary must be detectable so the tool can returnBROWSER_NOT_INSTALLEDwith that command as the hint — Playwright's launch error text is stable enough to match on.
Decision#
Use playwright, pinned to 1.62.0, with Chromium only.
The dependency is confined to src/browser.ts; no other module imports it. All
browser interaction happens through one exported function, captureUIState(), which
returns plain data (CaptureResult). Firefox and WebKit are not installed and not
exposed — Chromium is what most dev-server users target, and each extra engine is
another download and another rendering baseline to support. CLAUDE.md records the
standing instruction: do not swap Playwright for Puppeteer.
Consequences#
- Viewport emulation, locale and timezone pinning, and dual failure signals are configuration rather than code. The mobile preset is four fields.
- Chromium's version is pinned transitively by the Playwright version, so a capture is reproducible from a lockfile, and upgrading Playwright is a deliberate act that also moves the browser.
- The end-to-end tests can launch a real browser against a local fixture server, which is why mocking Playwright is explicitly discouraged: 63 tests include real captures, and a green run means the actual pipeline works.
- Given up: install weight. Chromium is a one-time download of roughly 95 MB on
top of the npm package — the single largest onboarding friction, and one that can
only be documented, not removed. Hence the dedicated
BROWSER_NOT_INSTALLEDcode. - Given up: reusing a browser the developer already has. Playwright manages its own builds and does not use the system Chrome. That duplication is the price of version determinism.
- Given up: Puppeteer's smaller footprint. Playwright ships surface VibeLens never imports (test runner integration, tracing, multiple engines) but that is still installed.
Alternatives considered#
Puppeteer. Rejected, not because it cannot do the job, but because each of
the five requirements above costs more setup: device emulation via
page.emulate() with a descriptor rather than context options, and no equivalent
of the context-level locale/timezone pinning. Neither is prohibitive, but the
weight of the whole list favoured Playwright, and the choice is not worth
revisiting without a concrete failure.
Chrome DevTools Protocol directly, against a browser the user launches. Rejected. It removes the browser download but pushes the burden onto the user (launch Chrome with a debugging port, keep it running) and forces VibeLens to reimplement navigation, load-state and screenshot semantics.
A screenshot service or WebDriver/Selenium. Rejected. A hosted service cannot
reach localhost and contradicts ADR 0001 and ADR 0003. WebDriver adds a driver
process and gives weaker console and network instrumentation than the DevTools
protocol.
Installing all three Playwright engines. Rejected for v1: tripling the
download to serve a use case nobody asked for is the wrong first trade. The
engine choice is one line in launchBrowser() if that changes.