Skip to content

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.

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:

Terminal window
chmod 600 /absolute/path/to/bindings.json
reflow target http://localhost:3000 --group browser-tests --collection <collection-id> --bindings /absolute/path/to/bindings.json

For 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.

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:

Terminal window
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:35291

Repeat --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.

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 form
providers:
site: Browser
---
### Inspect the invitation form
```rfl
site.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:

Terminal window
reflow flows create --name team-invitation --file /tmp/team-invitation.md
reflow plan team-invitation
reflow apply team-invitation
reflow dashboard

The alias site selects the private connection with the same name. The plugin version and descriptor pins above identify its command schema, not its connection.

CommandRequired argumentsOptional argumentsResult
site.openurl stringNoneNavigate to an absolute or target-relative URL.
site.clickselector stringtimeout numberClick one actionable target.
site.fillselector, value stringstimeout numberFill an editable target.
site.expectselector stringtext string, timeout numberCheck visibility, or exact text when supplied.
site.snapshotNonename, selector, mask, mask_optional strings; timeout, settle, threshold numbersCapture a viewport or one element.
site.evaluatesource stringargument string containing JSONRun 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.

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: Browser
needs: [seed]
references:
needs.seed.outputs.itemId: {type: string}
site.open url="/items/${needs.seed.outputs.itemId}"
capture suffix = random.string length=12
site.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.

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=500

timeout 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.

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.

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.

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=0

Both 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.

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.