Delivery Factory · Part 5 · Playwright Browser

The Playwright plugin, from URL to observed page

The verification stages need a page's load state, a screenshot at a given width, the computed styles of named elements, and the state of those elements after a click or a key press. This plugin provides them from a real headless Chromium, as data at fixed paths. This page follows it from the URL to the verdict. Repo: agentic-playwright

Checked in README + code

Read in the plugin's README, manifest, skills and scripts. Source: agenticDraft/plugins, folder plugins/agentic-playwright/, version 0.2.2. Paths are relative to that folder unless said otherwise.

From URL to verdict1200 × 627
DELIVERY FACTORY · PART 5 The Playwright plugin, from URL to observed page. EVERY OPERATION STARTS THE SAME WAY target an http(s) URL requires check playwright · chromium launch headless Chromium load waits up to 15 s THEN ONE OF FOUR OPERATIONS render status · title · console errors capture full-page PNG at a width measure settles, then reads styles interact acts, state before and after ONE VERDICT, ALWAYS A RESULT BLOCK PASS the page was loaded and read a 404 or a 500 is a pass · file under .ai/playwright/ FAIL tool missing · cannot launch · no load never settles · action matches nothing · 30 s watchdog Pass means the page was observed. It does not mean the page is healthy.
Every operation checks its tools, launches Chromium and loads the page before it does anything else. Which operation runs decides what is read. The script, not the model, decides pass or fail.

The parts

A plugin of its own

The Playwright pack plays one role in the pipeline: browser. It has its own manifest, skills, scripts and tests, and no MCP server is involved.

The operations

Four operations, one shape

Each takes input lines, runs against the real browser, writes one file under .ai/playwright/ and returns a result block.

Files get the next free name. Each artifact is named <operation>-<target slug>[-<width>]-<n>, so a re-run never overwrites an earlier capture.

Only the script's words. The skill returns the script's stdout unchanged. It adds no commentary and does not summarise it.

$ measure.cjs --width 720 <url> ".logo" ".logo-row"
## Result
verdict: pass
summary: <counts of found and hidden selectors>
artifacts:
  - .ai/playwright/measure-<slug>-720-1.json
next_action: none

The settle

A page that has stopped moving

measure reads only after the page settles. settle.cjs decides when that is.

  1. Wait for the document's fonts to finish loading.
  2. Take a snapshot of every selector. Take another 250 ms later.
  3. If a selector's state still changes, or something blocks the read, discard the snapshot and try again.
  4. Two snapshots in a row are identical: the page has settled, and the second one is the reading.
  5. Everything above has an upper bound of 10 seconds. A page that does not settle fails, with the reason in the summary, and no file is written.

Hidden is a reading, not a wait. A page can hide part of a block at some widths. A selector with no visible match is reported at once with hidden: true. Its values are the first match's, as read, and a consumer never grades them. The other selectors keep their values.

A fixed interval. The snapshot includes hidden, so an element on its way to visible still changes the snapshot and the wait goes on, until the bound.

Inputs are injected. The probe, the fonts wait and the clock are passed in, so the condition runs in tests without a browser. That is why settle has 30 offline assertions.

Only measure settles. In the code, settle.cjs is used by measure.cjs alone. The README's flow shows it for every operation.

Pass and stop

Observed, not healthy

A verdict is about the check. Was the page seen, or could it not be?

$ render.cjs http://127.0.0.1:1/
## Result
verdict: fail
summary: could not launch Chromium:
  browserType.launch: Target page, context
  or browser has been closed
artifacts: []
next_action: none

A browser that could not launch, from the README. The command ran in a sandbox that blocks the browser process. The operation still returned a result block and exit status 0.

Every console error and uncaught page exception is also appended to a log under .ai/playwright/ the moment it happens, so a run that is killed still leaves what it saw.

One declaration

What it needs is data

The manifest states what the pack needs and how to fix its absence. Every reader reports that one text.

# pack.yaml
requires:
  - tool: playwright
    probe: [node, <pack>/scripts/probe-tool.cjs, module]
    remedy: "reinstall or update this plugin so the host
      installs the playwright version its package.json
      pins; loaded by path, run npm ci in this plugin's root"
  - tool: chromium
    probe: [node, <pack>/scripts/probe-tool.cjs, browser]
    remedy: "npx playwright@1.63.0 install chromium"

probe-tool.cjs answers by exit status alone and installs nothing. requires.cjs reads the same block, so an operation that refuses to run reports the same remedy the core's health diagnostic prints.

Two hand-written copies of a remedy agree only until one is edited. This is the one provider pack here that depends on a binary outside the session, which is why the declaration exists at all.

  1. Install from the marketplace: /plugin marketplace add agenticDraft/plugins, then /plugin install agentic-playwright@agenticdraft. The host installs the pinned playwright into the plugin's own directory.
  2. Install the browser: npx playwright@1.63.0 install chromium. Loaded by path instead, run npm ci in the plugin's folder first.
  3. Check the preconditions the way the core does: check-requires.sh on pack.yaml, or /agentic-core:health in a configured session.
  4. Invoke an operation: Skill(agentic-playwright:render) with the input lines as the argument, or /agentic-playwright:render followed by target: <URL>.

What it doesn't cover

The limits, as stated

From the README's own list.