Browser Tasks
The Browser/Run task drives a web portal with a real Chromium browser. Use it for carrier, customs or customer portals that have no API: sign in, fill forms, read statuses and tables, download proofs, upload documents, take screenshots, or let a model work a page toward an instruction with the llm/act verb.
The browser is not part of the API process. It runs on a browser provider (the organization's own Browserbase account, a self-hosted browser host, or any endpoint that speaks the Chrome DevTools Protocol; see Browser providers), and the task connects to it for the duration of the workflow execution.
Browser/Run
task: "Browser/Run@1"
name: portal
inputs:
allowedDomains: ["portal.example.com"]
actions:
- do: goto
url: https://portal.example.com/login
- do: fill
target: label=Username
value: "{{ portalCreds.user }}"
- do: fill
target: label=Password
value: "{{ portalCreds.password }}"
sensitive: true
- do: click
target: role=button[name='Sign in']
- do: waitForNavigation
- do: extractText
target: css=.status
as: status
- do: screenshot
as: proof
outputs:
- name: status
- name: proof
Inputs
| Input | Required | Description |
|---|---|---|
actions | yes | Ordered list of actions (below). |
allowedDomains | when any action is goto, download with url, or llm/act | Hosts the run may navigate to or fetch from. A bare host admits https only; http://host or https://host[:port] admits that scheme. Subdomains are not implied. Templated, so it can come from a fromConfig variable. |
provider | no | Pin a browser provider by name: the organization config tms.browser.<name>, otherwise the server provider <name>. Strict: no fallback to other providers. See Browser providers. |
session | no | reuse (default) shares one browser and page across every Browser/Run step of the execution. new opens a fresh session for this step. |
timeout | no | Default per-action timeout in seconds. Falls back to the server's Browser:DefaultTimeoutSeconds. |
proxy | no | true routes the session through the provider's proxy (Browserbase residential proxy); false (default) connects directly. Only Browserbase providers support it. A proxied and a direct session are never shared under session: reuse. |
Actions
Every action has do and, depending on the verb, some of target, value, url, query, file, attribute, as, all, if, sensitive, timeout, instructions, model, maxTurns.
| Verb | Needs | Captures under as |
|---|---|---|
goto | url (+ optional query object merged as a query string) | |
click, check, uncheck, hover, waitFor | target | |
fill, press, selectOption | target, value (visible label for selectOption; key name such as Enter for press) | |
waitForNavigation | ||
extractText | target, as | string |
extractAttribute | target, as, attribute | string |
extractTable | target, as | { rows: [{ rowIndex, cells: { <header>: text, __inputId, __html } }], count } |
extractUrl | as | current page URL |
screenshot | as | { name, contentType, sizeBytes, url, key } stored in file storage |
uploadFile | target, file: { url } or file: { path } | |
download | as plus target (click form, optional all: true) or url (fetch form) | artifact record, or an array under all |
llm/act | instructions (+ optional model, maxTurns 1 to 20, as) | { success, summary, turns, actions: [{ tool, selector, ok, error }] } |
Common fields:
ifis an NCalc expression over the task scope; a falsy result skips that action only.timeoutoverrides the per-action seconds. Forllm/actit bounds the whole loop.sensitive: truekeeps avalueout of log lines and the action log. Use it for credentials and personal data.
Locators
target must start with one of css=, xpath=, text=, label=, placeholder=, role=. Role locators accept a name: role=button[name='Sign in']. Prefer label= and placeholder= for form fields. Targets are static text; a template inside a target is rejected when the workflow is saved.
- do: fill
target: label=Tracking number
value: "{{ trackingNumber }}"
- do: click
target: role=button[name='Search']
- do: extractTable
target: css=table#results
as: results
Outputs
The task returns every as capture, url (the page URL after the last action), and actions (a list of { index, do, target, ok, error, ms, skipped }). Map them with the step's outputs: like any other task:
outputs:
- name: status
- name: summary
mapping: "agent.summary"
- name: proofUrl
mapping: "proof.url"
Failure
The first failing action stops the step with a message such as Browser/Run action[2] click css=button: strict mode violation. The step's continueOnError decides whether the workflow continues. Captures gathered before the failure are attached to the error.
Sessions
The first Browser/Run step of an execution acquires a browser and a page; later steps in the same execution reuse them, so a login performed in one step is still valid in the next. The browser is released when the execution ends. A foreach over orders with one Browser/Run step per iteration therefore signs in once.
activities:
- name: portal
steps:
- task: "Browser/Run@1"
name: login
inputs:
allowedDomains: ["portal.example.com"]
actions:
- do: goto
url: https://portal.example.com/login
- do: fill
target: label=Username
value: "{{ creds.user }}"
- do: fill
target: label=Password
value: "{{ creds.password }}"
sensitive: true
- do: click
target: role=button[name='Sign in']
- task: foreach
collection: orders
steps:
- task: "Browser/Run@1"
name: lookup
inputs:
allowedDomains: ["portal.example.com"]
actions:
- do: goto
url: "https://portal.example.com/shipments/{{ item.trackingNumber }}"
- do: extractText
target: css=.status
as: status
llm/act
llm/act puts a model in a bounded loop against the live page. The model receives the author's instruction, a page observation (title, URL, visible text capped at 6000 characters, up to 60 interactive elements with suggested selectors), and a closed tool set: readPage, click, fill, selectOption, check, uncheck, press, waitFor, goto, finish. The platform executes every tool call; the model never runs anything itself.
- do: llm/act
instructions: "Open the shipment {{ trackingNumber }} and read its delivery status."
maxTurns: 8
timeout: 120
as: agent
gotoinside the loop obeysallowedDomains.- Page content is treated as data, never as instructions.
- The captured record logs tool names and selectors only, never filled values. Keep credentials in deterministic
fillactions withsensitive: true. - The loop ends on
finish, whenmaxTurnsis exhausted (default 8, maximum 20), or when the actiontimeoutelapses.finishwithsuccess: false, exhaustion and timeout fail the action. - Without an AI backend configured on the server,
llm/actfails withLlm.NotConfigured.
Credentials
Feed a sensitive fill from a $-prefixed input or a fromConfig variable, mark it sensitive: true, and avoid logLevel: Trace on credential workflows, because Trace logs task variables.
variables:
- name: portalCreds
fromConfig:
configName: carrier-portal-credentials
Validation
These rules run when the workflow is saved. Each code is described with causes and fixes under Browser Task Errors.
| Code | Rule |
|---|---|
BROWSER_001 | actions is required and must be a non-empty list. |
BROWSER_002 | Each action must be an object using only known keys. |
BROWSER_003 | do must be a known verb. |
BROWSER_004 | A field the verb needs is missing (for example url for goto, as for extract verbs, exactly one of file.url or file.path). |
BROWSER_005 | A field not allowed on the verb is present (locator fields on llm/act, instructions on other verbs). |
BROWSER_006 | target must be static text with a locator prefix. |
BROWSER_007 | maxTurns must be an integer between 1 and 20. |
BROWSER_008 | allowedDomains is required when any action navigates. |
BROWSER_009 | An allowedDomains entry must be a host or scheme://host[:port] without a path. |
BROWSER_010 | A literal goto URL must be absolute and covered by allowedDomains. |
Browser providers
A Browser/Run step never names a browser URL. It takes its browser from the organization first, then from the server:
provider input | Organization config | Browser used |
|---|---|---|
| omitted | tms.browser.default with an API key | The organization's Browserbase account only. It never falls back to the server's browsers. |
| omitted | none, or no API key | The server's providers, tried by priority. |
X | tms.browser.X with an API key | That organization config only. |
X | none, or no API key | The server provider named X; otherwise the step fails. |
Organization Browserbase account
An administrator connects the organization's own Browserbase account in Organization Configs, entry Browser Automation (installed with the core app). The screen writes the organization config tms.browser.default:
type: browserbase # the only provider type allowed per organization
apiKey: "${secret:org/<organizationId>/tms.browser.default.apiKey}"
projectId: "<project id>" # optional
region: us-east-1 # optional
- The API key is stored as an organization secret; the config keeps only the reference.
- Installing the core app creates an empty
tms.browser.defaultin every organization. Until an API key is saved, steps keep using the server's browsers. - Self-hosted browser hosts and raw Chrome DevTools endpoints can only be configured on the server. An organization-supplied browser URL would let the platform connect to any address, so the organization config has no URL setting.
- Sessions last the server's
Browser:SessionTimeoutMinutes;proxy: trueworks if the Browserbase plan includes proxies.
To keep several accounts or projects, create further configs such as tms.browser.eu and pin them with provider: eu.
Server configuration
Server-wide browser providers and the AI backend are configured in the server settings, not in the workflow:
"Browser": {
"DefaultTimeoutSeconds": 30,
"Providers": [
{ "Name": "chrome", "Type": "browserHost", "Priority": 0, "Endpoints": ["http://browser:8080"] },
{ "Name": "desktop", "Type": "cdp", "Priority": 1, "Url": "http://localhost:9222" }
]
},
"AI": { "Provider": "Anthropic", "Model": "claude-opus-5", "ApiKey": "<secret>" }
browserHostis the self-hosted headed Chromium image shipped with the backend (docker/browser). One browser per endpoint; a busy host answers 409 and the next endpoint or provider is tried.cdpis any Chrome DevTools Protocol endpoint, such as a desktop Chrome started with--remote-debugging-port=9222.AI:ProviderisNone,Anthropic,OpenAIorAzureOpenAI.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
could not acquire a browser: ... no browser provider is configured | The organization has no tms.browser.default with an API key and the server has no providers. Save a Browserbase key in Organization Configs → Browser Automation. |
could not acquire a browser: ... provider 'X' is not configured | The step pins provider: X, but there is no organization config tms.browser.X and no server provider X. |
could not acquire a browser: ... no browser provider could create a session (...) | Every provider failed; the parentheses list each one's reason (host busy or unreachable, Browserbase rejected the key, and so on). |
organization config 'tms.browser.X' has type '...' | Only type: browserbase is allowed in organization config. |
'https://x' is not allowed by allowedDomains (a bare entry admits https only) | Add the host, or http://host for plain http. |
target '#id' must start with a locator prefix | Use css=#id. |
strict mode violation | The locator matched several elements. Narrow it with role=button[name='...'] or a more specific css= selector. |
Llm.NotConfigured | The server has no AI:Provider. |
Click-form download fails on a remote provider | Use the fetch form (url), which reads the file through the page's own session. |