Creating Browser Automations
This skill is for the manual-demo-to-automation pipeline.
For simple, reusable, low-cost automation on top of browser-pilot, use the companion Flightplan package for TOML workflows with effects, assertions and proof artifacts.
For local Chrome on Chrome 144+, try plain bp connect first after enabling remote debugging in chrome://inspect/#remote-debugging. Only add --channel or --user-data-dir if auto-discovery is ambiguous.
Keep the named session for capture and replay. Local CLI sessions use one
browser-scoped daemon by default; CI must set BROWSER_PILOT_NO_DAEMON=1.
Shared controller ownership
Named daemon sessions admit one controller across all aliases. On session_busy, wait for the current controller to hand back; do not reconnect, open a fallback tab or force-stop the owner. After Flightplan returns control, take a fresh snapshot before using element refs.
An uncertain action requires reconciliation before another controller can act. Strict owner profiles require the typed host API and refuse raw CLI/CDP actions. See shared owner when embedding or configuring a guarded host.
Core rule
Do not start by reading raw recording.json.
Start with:
bp record summarybp record inspectbp trace summary --view ...
Pipeline
- Capture the workflow with
bp record - Summarize the artifact
- Inspect runtime behavior through
trace summary - Derive replayable steps
- Run the derived workflow
- Harden with assertions or replay proof
Capture
bp connect --name demo
bp record -s demo --profile automation -f ./artifacts/demo.recording.json
# perform the flow manually, then stop with Ctrl+C
Use a profile that matches the job:
automationrealtimevoiceauth
Prefer a saved cookie snapshot when one exists for the target site — restore it
(bp connect --auth <name-or-path>) instead of logging in again before recording; see
Cookie snapshot auth.
If the target sits behind Cloudflare Access, authenticate before capturing — otherwise the
recording just captures the Access login redirect. Run bp connect --cf-access (or
bp env auth set-headers/set-cookie for persisted auth) before bp record, not as part
of the demoed flow. See docs/cli.md#env and, for reusing a saved login instead of
Cloudflare Access, docs/guides/auth-cookies.md.
Understand the artifact before deriving
bp record summary ./artifacts/demo.recording.json
bp record inspect ./artifacts/demo.recording.json
bp trace summary ./artifacts/demo.recording.json --view session
bp trace summary ./artifacts/demo.recording.json --view ws
bp trace summary ./artifacts/demo.recording.json --view console
Use trace summary to inspect network/runtime behavior inside the same artifact instead of inventing a separate debugging path.
Derive and run
bp record derive ./artifacts/demo.recording.json -o ./artifacts/demo.workflow.json
jq . ./artifacts/demo.workflow.json
bp run ./artifacts/demo.workflow.json -s demo --json
bp record derive writes browser-pilot workflow JSON for bp run. Use Flightplan for simple reusable workflows.
Harden the automation
Add trace-backed assertions where the app is stateful:
bp exec -s demo '[
{"action":"waitForWsMessage","match":"*realtime*","where":{"type":"session.ready"}},
{"action":"assertTextChanged","selector":"#status","from":"Connecting","to":"Live"},
{"action":"assertNoConsoleErrors","windowMs":500}
]'
Add outcome conditions to verify actions actually worked:
bp exec -s demo '[
{"action":"submit","selector":"form",
"expectAny":[
{"kind":"urlMatches","pattern":"*/success*"},
{"kind":"textAppears","text":"Submitted"}
],
"failIf":[{"kind":"textAppears","text":"Error"}],
"dangerous":true}
]'
Conditions include urlMatches, elementVisible, elementHidden, textAppears, textChanges,
networkResponse, stateSignatureChanges, selectedTab, fieldValue, checkbox, switch,
elementEnabled, targetCount, newTarget, urlChanged, and fieldChanged. Text and URL
conditions can use explicit match modes and scoped selectors or landmarks.
For asynchronous pages, a waitForReady action can combine any/all, loadingHidden,
predicates, and DOM-stability requirements. waitFor: "networkIdle" only means transport quiet.
If a derived workflow uses retries, remember that retries stop at the dispatch boundary: a
pre-dispatch failure may retry the action, while a dispatched or uncertain effect is observed and
its conditions are re-evaluated instead of blindly repeating input. Use effect and dangerous
when the action's retry policy matters.
Review page state after a workflow completes:
bp review -s demo --json
Or capture replay proof:
bp connect --name validation --record
bp exec -s validation -f workflow.json
When to reach for other surfaces
- Need fresh targets on a page:
bp snapshot -i - Need structured business state:
bp review - Need one-off direct control:
bp exec - Need to verify an action's effect: outcome conditions in
bp exec - Need long-running analysis or evidence:
bp trace - Need voice/media control:
bp audio - Need permissions/network/visibility changes:
bp env
Hosted providers and borrowed workflows
Use bp connect --provider cloudflare:chromium --name <name> with environment
credential references; cloudflare:kitesurf is an explicit connection-bound
engine. Provider authentication differs from page Cloudflare Access auth.
Node header handshakes use the optional ws peer; Bun native transport has an
unresolved compressed-socket diagnostic failure. See the repository's
docs/cloudflare-validation.md before claiming engine support.
Reusable workflows belong in Flightplan. [config.connect] mode = "hosted"
acquires an owned Cloudflare browser; mode = "session" needs a host acquirer,
session_ref and explicit target policy. Borrowed teardown preserves the owner;
provider release can report cleanup_pending and requires exact-allocation
reconciliation. Never replay an ambiguous mutation or allocate a replacement
as a cleanup retry.
Page.close() disposes the wrapper. Close an owned fixture target explicitly
with Browser.closePage(cachedPageName) and verify target removal using
Browser.listTargets(). This method takes a cached page name, not a target ID. Daemon stop requires
-s <session> or --daemon-id <id>; a positional owner name is rejected.
The Flightplan SauceDemo example is a public demo checkout, not an Adyen/card or
live Cloudflare conformance fixture. Read its examples README for the tested isolated-profile setup and
verify the order-complete state rather than accepting click receipts.