Browser plugin
Use Browser for user journeys: open a page, fill a form, check the result and capture the screen. Combine its steps with other plugins in the same flow or use dependencies to pass values between flows.
Connect
Section titled “Connect”Select your application collection during onboarding. Use its ID in the target command below.
Install the bundled plugin with reflow init --collection <collection-id>. A supported browser runtime
must be installed on the execution machine; init checks available prerequisites.
Device plan/apply execution uses Chromium so every document redirect can be checked against the selected origins. The target
application must already be running and reachable.
If a paused or sleeping environment can answer with a placeholder page, configure
target readiness HTTP probes so new work stops with
target_not_ready instead of failing browser assertions.
Save a private bindings.json:
{ "site": { "provider": "Browser", "configuration": { "targetUrl": "http://localhost:3000", "browser": "chromium", "width": 1280, "height": 800 } }}Select the application URL and the bindings file:
chmod 600 /absolute/path/to/bindings.jsonreflow target http://localhost:3000 --group browser-tests --collection <collection-id> --bindings /absolute/path/to/bindings.jsonFor direct calls such as site.open, the binding’s targetUrl supplies the base
for relative routes. Keep it aligned with the URL selected by reflow target.
Absolute site.open URLs do not need a base. Optional device selects an exact Playwright device
preset; executablePath selects an installed browser executable. extraHTTPHeaders
is a JSON-encoded string of private request headers. Keep credentials out of flows
and versioned connection files.
Auth redirects and navigation origins
Section titled “Auth redirects and navigation origins”Local CLI plan/apply runs (including MCP manage_execution) and local interactive
sessions allow document navigation to the selected target origin. Grant each additional login or IdP origin explicitly:
reflow target https://localhost:5173/ --group gram --collection <collection-id> --bindings /absolute/path/to/bindings.json --allow-origin https://localhost:8080 --allow-origin http://localhost:35291Repeat --allow-origin for another origin. In local MCP, use manage_execution
with action: "target" and allowed_origins: ["https://localhost:8080", "http://localhost:35291"] alongside
the same target, group, collection and bindings. Scheme, host and port must match
exactly. Paths, credentials and wildcards are refused. For the same connection,
checkout, collection, URL, group and scope, omitting origins preserves the existing
grants. An explicit list replaces all grants. Use --clear-allowed-origins or MCP
allowed_origins: [] to revoke them. Omitted scope and private binding/input paths
are also preserved for the same selection; changing the selection does not carry
origins or private files into it. End any
active runs or sessions before selecting a new target, then re-plan.
Every redirect hop must be granted. An ungranted navigation fails the step and
retains a diagnostic screenshot; further actions in that browser session stop.
A clicked redirect or email link outside the grant also ends an interactive session; explicitly
requested navigation is checked before dispatch. Popups remain unsupported.
These are document-navigation grants, not a network sandbox for scripts or subresources.
Separate Zero device jobs and managed static targets do not read CLI target settings
and remain target-origin-only. Use local plan/apply for multi-origin authentication.
Additional origins cannot be combined with context-wide team or binding request
headers; remove those headers first to prevent forwarding them to the IdP.
navigationPolicy in a binding is reserved for trusted runtime configuration.
Ordinary RFL and direct Browser commands share one page, cookies and downloads. All Browser aliases in one flow must have identical private configurations. A conflicting target, browser, device or headers fails before launching the page.
Write a flow
Section titled “Write a flow”Copy this into a private draft outside your checkout, such as
/tmp/team-invitation.md. Adapt the route and selectors to your application;
this example expects an invitation form:
---name: Inspect the invitation formproviders: site: Browser---
### Inspect the invitation form
```rflsite.open url="/settings/team"site.click selector="[data-testid=invite-member]"site.expect selector="[data-testid=invitation-form]"site.snapshot name="team-invitation" selector="[data-testid=invitation-form]" timeout=5000```Create the test in the team’s chosen location, then run it:
reflow flows create --name team-invitation --file /tmp/team-invitation.mdreflow plan team-invitationreflow apply team-invitationreflow dashboardThe alias site selects the private connection with the same name. The plugin
version and descriptor pins above identify its command schema, not its connection.
Commands
Section titled “Commands”| Command | Required arguments | Optional arguments | Result |
|---|---|---|---|
site.open | url string | None | Navigate to an absolute or target-relative URL. |
site.click | selector string | timeout number | Click one actionable target. |
site.fill | selector, value strings | timeout number | Fill an editable target. |
site.expect | selector string | text string, timeout number | Check visibility, or exact text when supplied. |
site.snapshot | None | name, selector, mask, mask_optional strings; timeout, settle, threshold numbers | Capture a viewport or one element. |
site.evaluate | source string | argument string containing JSON | Run an explicit page function. |
Timeouts in these plugin commands use numbers in milliseconds. Assert readiness
with expect before taking a snapshot. Direct command selectors are CSS; for RFL’s
role, label, text and fallback selectors, see the language reference.
The two syntaxes use the same Browser provider but have different argument forms.
URLs from captures and upstream outputs
Section titled “URLs from captures and upstream outputs”In admitted reflow plan / reflow apply execution, named Browser arguments
support the same scalar interpolation as other plugins. Use an explicit Browser
alias for an upstream URL, declare the dependency and its output type, then pass
the value to site.open:
providers: site: Browserneeds: [seed]references: needs.seed.outputs.itemId: {type: string}site.open url="/items/${needs.seed.outputs.itemId}"capture suffix = random.string length=12site.open url="/search?q=${suffix}"The seed flow must actually export itemId. Random values require admitted
plan/apply execution; they are not sampled by an interactive browser command.
Interpolation does not URL-encode values: use values already safe for the intended
URL component. Arbitrary text can change the path or query.
The resolved URL still has to satisfy the target’s origin grants. The explicit
site.open url=... form above makes the provider argument and its interpolation
visible in the flow.
The installed descriptor also exposes execute (host-parsed statements), script
(legacy TypeScript steps) and session (recorder, mirror and debugger operations).
These support host integrations; ordinary flows should use the commands above or
RFL browser statements. Use list_plugins for their installed schemas and the
MCP reference for interactive browser tools.
Snapshots
Section titled “Snapshots”The name identifies the checkpoint across runs. selector limits the screenshot
to one element and scrolls it into view when needed. Omit it to capture the
viewport. The selected element must be visible and have stable bounds. A missing,
hidden, invalid or ambiguous selector fails the command;
it never falls back to a whole-screen capture. For a scrollable element, the
screenshot includes the content visible inside its box.
Snapshot arguments are ordinary named plugin arguments:
site.snapshot name="team-invitation" selector="[data-testid=invitation-form]" timeout=5000 settle=500timeout bounds finding and capturing the element: five seconds by default,
up to 300000 milliseconds, and greater than zero. settle separately waits for
finite page animations: two seconds by default, or 0 to skip that wait.
settle=0 still requires stable bounds for an element capture. A continuously
moving element times out without a screenshot; no animation is frozen or changed.
These plugin arguments use numbers in milliseconds. Assert application readiness
first; animation settling does not wait for requests or application data.
For volatile text or images, explicitly mask their CSS selectors:
site.snapshot name="org-home" selector="body" settle=0 mask="[data-testid=timestamp], [data-testid=branch]"mask is one CSS selector-list string. Commas inside quoted attributes or CSS
functions stay intact. Blank or invalid CSS fails; a list matching no elements
after settling fails. Multiple matches are allowed, and individual branches of
a list may match nothing. Each matched bounding box is painted pink (#FF00FF),
including invisible matches that have a box. Both viewport and scoped captures
support masks; a scoped image includes only the portion inside its captured box.
Masks hide those pixels, not movement or layout. Changing box geometry or content
outside it can still produce a difference. The authored mask remains in provider
state, including failures. Masks do not freeze animations, skip settling, apply
to other captures or approve changes. Direct site.snapshot captures an artifact
for an explicit comparison or agent review; it is not an automatic baseline gate.
The built-in RFL snapshot statement does not accept this plugin-only mask argument.
Mask selectors must still match after capture; otherwise the command fails and discards the image. Establish application readiness and stable layout first. Masks aid visual comparison; they do not guarantee redaction across arbitrary DOM changes or in other captures.
To use these arguments, initialize the application’s plugin selection with
reflow init --collection <collection-id>. If its selected Browser release lacks
an argument, update the CLI and explicitly run init with --upgrade. The generated
application lock records the release; flow files contain no plugin version pins.
Snapshot selectors use native CSS syntax; Playwright engine chains and
pseudo-classes such as :visible are not accepted. The timeout default also
applies to viewport captures. name identifies the checkpoint. Optional
threshold is retained metadata for this plugin invocation; it does not implicitly
compare an approved baseline or approve a difference.
Use snapshots and review for before/after evidence and the separate explicit visual assertion contract. A new screenshot is not permission to update an expectation.
Failures and reruns
Section titled “Failures and reruns”A missing or ambiguous target, failed assertion or capture timeout fails the step. Inspect the retained outcome before changing the flow. Reflow reconstructs browser state when earlier steps need rerunning; named captures are evidence, not a saved browser login. Browser isolation does not clear your application’s database. See repair and rerun.
Flows using this plugin can be saved in Reflow or in your repository. Read test storage for the team preference, private bindings and the shared plan/apply workflow.
Interrupted browser execution
Section titled “Interrupted browser execution”Device execution sizes memory at each launch from the host runtime’s available memory: 20% stays reserved for the host, and the remaining allowance is divided across configured execution slots. The worker heap receives a quarter of that per-execution budget and the Browser helper half; the rest allows for native memory and browser processes. This is a launch-time budget, not live heap resizing or a guarantee that every application fits. Cloud runners retain fixed ceilings. A helper heap exhaustion reports the heap-exhaustion message from stderr while preserving unknown completion: an interrupted action might already have changed the application. Stderr is diagnostic, not authoritative; a script can print the same message. Inspect the interruption before tainting or retrying. Helper stderr is held in a bounded private diagnostic buffer, not a separate log file; raw stderr is not included in run errors.
Optional masks and shadow roots
Section titled “Optional masks and shadow roots”Use mask_optional for conditional volatile UI that may be absent:
site.snapshot name="home" selector="body" mask=".timestamps" mask_optional="[data-react-grab-toolbar]" settle=0Both arguments take one intact CSS selector list. mask requires the list to
match at least one element; individual comma-separated branches may be absent.
mask_optional permits zero matches. Blank or invalid CSS still fails. Both
resolve through Playwright CSS locators, including open shadow roots; closed
shadow roots are inaccessible. Matched bounding boxes are pink.
Provider evidence retains mask_matches, with the authored selector list,
optional, beforeCapture and afterCapture counts. These are observations on
either side of capture, not an atomic guarantee about a changing DOM. Counts are
per whole authored list, not per comma-separated branch. Optional regions may
change the page layout when present, so masking does not make different layouts
equal or approve a baseline.
Inspect these observations with get_run_evidence using run_id and
include_mask_matches: true. Provider images return mask_matches.entries when
status is recorded; missing phases remain absent, not zero. Selectors may be
sanitized or truncated (selector_modified: true). An unavailable, not-recorded
or unsupported status does not establish that no elements were masked.
Screenshot sizing after a Browser upgrade
Section titled “Screenshot sizing after a Browser upgrade”After the 0.1.0-5056b76fe13ab70031c6 release, application testing reported an element capture
width changing from 1024 to 1009 pixels, and an org-home body capture at 1265
pixels. These are application-specific observations, not fixed output sizes.
Review dimensions and content when comparing captures across Browser pins;
masking does not normalize layout or image dimensions. This inspection change
itself does not change screenshot capture.