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.
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.
pack.yamldeclares the role and what it needskind: provider,role: browser, four operations mapped to four skills,unsupported: []. It namesskills/render/scripts/render.cjsunderscripts:for a caller that needs a load check without a model step. Arequires:block lists the two tools the pack depends on..claude-plugin/plugin.jsonnames itName
agentic-playwright, version0.2.2, one dependency:agentic-core. The project picks it as its browser withpacks.browser: playwrightin.ai/project-config.yaml.Four skills, one script each
Each
skills/<name>/SKILL.mdrunsskills/<name>/scripts/<name>.cjsand outputs its stdout as the whole response, ending with the## Resultblock. The skill has no branch of its own: the script alone decides pass or fail.Helpers shared by the operations
probe-tool.cjschecks a tool by exit status.requires.cjsreads the manifest'srequires:block.settle.cjsdecides when a page has stopped moving.next-artifact-path.cjspicks a file name nothing has used.operation-guard.cjskeeps a log and a watchdog.launch-options.cjssets headless launch and the time limit.Offline tests for the helpers and
measureFour test files, run with no browser:
declared-dependency(11),next-artifact-path(13),settle(30) andmeasure(15), 69 assertions in total.render,captureandinteracthave no offline test.
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.
render: the load stateInput:
target. Returns the final URL, HTTP status, page title and console errors. It also reports a load time and writes a JSON file.capture: a picture at a widthInput:
target,width. Writes a full-page PNG. The viewport is the given width, 800 pixels tall.measure: numbers for named elementsInput:
target, an optionalwidth, one or moreselector. Per selector it returns the geometry and a fixed set of values:color,background-color,font-family,font-size,font-weight,line-height, the fourpadding-*values,gap,border-radiusandmin-width. It addsholds_text,broken_wordsandhidden. The measurement records the width it was read at. A selector that matches nothing is reportedfound: falseand does not fail the operation.interact: state after actingInput:
target, one or more ofclick,press,type, and one or moreread. Everyreadselector is captured once before the first action and once after the last: geometry, the style values, everyaria-*attribute andclass.pressfocuses the element first, like a keyboard user tabbing to it. An action that matches nothing, or cannot be carried out, fails the whole operation.
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.
- Wait for the document's fonts to finish loading.
- Take a snapshot of every selector. Take another 250 ms later.
- If a selector's state still changes, or something blocks the read, discard the snapshot and try again.
- Two snapshots in a row are identical: the page has settled, and the second one is the reading.
- 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?
Pass
The page was loaded and its state read. An HTTP 404 or 500 is a pass too, because that state was observed. A hidden selector and a selector that matches nothing in
measureare readings, not failures.Fail · tool missing
A declared tool is not there. The result carries the manifest's own remedy text. Nothing is installed.
Fail · browser
Chromium could not launch, or the page did not finish loading within 15 seconds.
Fail · the read
The page did not settle within 10 seconds (
measure), or an action matched nothing or did not take effect (interact). An interaction that did not happen is never reported as one that did.Fail · watchdog
An operation that runs past 30 seconds is ended. The result names the last step, lists the log and exits 0. A page that blocks its own main thread gets the same ending instead of a silent hang.
Usage error
A missing or malformed argument prints a usage line and exits 2. This is the only exit that is not 0.
$ 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.
- Install from the marketplace:
/plugin marketplace add agenticDraft/plugins, then/plugin install agentic-playwright@agenticdraft. The host installs the pinnedplaywrightinto the plugin's own directory. - Install the browser:
npx playwright@1.63.0 install chromium. Loaded by path instead, runnpm ciin the plugin's folder first. - Check the preconditions the way the core does:
check-requires.shonpack.yaml, or/agentic-core:healthin a configured session. - Invoke an operation:
Skill(agentic-playwright:render)with the input lines as the argument, or/agentic-playwright:renderfollowed bytarget: <URL>.
What it doesn't cover
The limits, as stated
From the README's own list.
Engine
Chromium only. No other browser is declared or probed.
Sandbox
A sandbox that blocks the browser process turns every operation into a fail, indistinguishable from a missing binary except by its summary. Each operation skill therefore runs its script with the command sandbox off. A local preview also needs loopback connections.
Late elements
An element that appears after the page settles (an animation, a deferred reveal) is read as hidden. A behaviour that needs it visible is a job for
interact.Fixed values
The style set is fixed: a value outside the
measurelist is not returned.broken_wordsreads one text node at a time, so a word an inline element splits in two is read as two words.Next
The prototype, and the check that measures it in this browser.