RPA (Automation)
Build a process once — a sequence of steps, visually, no code — then run it on any profile. It drives the exact same isolated, fingerprinted browser a normal launch would, so it's indistinguishable from you clicking it by hand.
Building a process
Open Processes → New Process, name it, and add steps from the command
palette one at a time. Steps run top to bottom; if and loop can nest other steps
(including more if/loop) inside themselves.
CSS & XPath selectors
Anywhere a command takes a selector, you can use either a normal CSS selector or an XPath expression — XPath is auto-detected from its shape, no prefix needed:
| Pattern | Detected as |
|---|---|
//div[@id='x'] | XPath (absolute-ish) |
/html/body/div | XPath (absolute) |
./span | XPath (relative) |
../td[2] | XPath (parent axis) |
(//a)[1] | XPath (parenthesized, for indexing) |
| anything else | Plain CSS selector |
This means you can paste DevTools' "Copy XPath" output directly into any selector field with no changes.
Command reference
| Command | Parameters | What it does |
|---|---|---|
| goto | url, waitUntil? | Navigate to a URL. waitUntil is load, domcontentloaded (default), networkidle0, or networkidle2. |
| click | selector | Click the matched element. |
| type | selector, text, delayMs? | Type text into the matched element, optionally with a per-keystroke delay for a more human pace. |
| press | key | Press a single keyboard key (e.g. Enter, Tab). |
| keyCombo Keys combination | combo | A modifier chord: select all, copy, paste, or reload. Uses ⌘ on a macOS-fingerprinted profile and Ctrl otherwise — read from the profile's spoofed OS, not the host's. |
| focus Focus | selector | Put the cursor in an element without clicking it — useful when a click would trigger a handler or dismiss an overlay. |
| hover | selector | Move the mouse over the matched element. |
| select | selector, value | Choose an option in a <select> element by value. |
| scroll | selector? or x?/y? | Scroll a specific element into view, or scroll the page to absolute coordinates. |
| wait | ms | Pause for a fixed number of milliseconds. |
| waitForSelector | selector, timeoutMs? | Wait for an element to appear before continuing. |
| waitForRequest Request to finish | urlPattern, timeoutMs? | Wait until the page makes a network call whose URL contains urlPattern and it completes. A barrier only — use the listener commands if you also need the response body. |
| Command | Parameters | What it does |
|---|---|---|
| screenshot | name | Capture the current page, saved under the given name in the run's Debug log. |
| extractText | selector, variable | Read an element's text content into a variable for later steps to use. |
| extractAttribute | selector, attribute, variable | Read one attribute's value (e.g. href) into a variable. |
| evaluate | script, variable? | Run raw JavaScript in the page and, if given, capture its return value into a variable. Times out after 30 seconds so a stuck script can't hang the whole run. |
| Command | Parameters | What it does |
|---|---|---|
| newTab | — | Open a new tab and switch to it. |
| closeTab | — | Close the current tab and fall back to another. Refuses to close the last remaining tab. |
| closeOtherTabs Close Other Tabs | — | Close every tab except the current one. |
| switchTab | index | Switch to the tab at the given index. |
| goBack | — | Navigate back in history. |
| refresh | — | Reload the current page. |
| closeBrowser | — | Close the whole browser, ending the run. |
| Command | Parameters | What it does |
|---|---|---|
| getUrl | variable | Capture the current page URL into a variable. |
| getTitle | variable | Capture the current page title into a variable. |
| Command | Parameters | What it does |
|---|---|---|
| getFocusedElement Focused Element | variable | Whatever currently has focus (document.activeElement) — its value for a form field, otherwise its text content. |
| getElementHtml Element | selector, variable | The matched element's full outerHTML — distinct from Extract text (text only) and Extract attribute (one attribute only). |
| getCookies Get Page Cookie | variable | Every cookie visible to the current page, as a JSON array string. |
| clearCookies Clear Page Cookie | — | Deletes every cookie visible to the current page. |
| saveToFile Save to Txt | variable, filename | Writes a variable's current value to a file, sandboxed to this profile's own RPA-files folder — a filename with ../ in it is stripped down to just the filename, it can never write outside that folder. |
| importFromFile Import Data from Txt | path, variable | Reads a file's whole text content into a variable, from that same sandboxed folder. |
| getClipboard Clipboard Content | variable | Reads the system clipboard. |
| setClipboard | text | Writes to the system clipboard. |
| startListener Listener Request | name, urlPattern | Starts watching network responses whose URL contains urlPattern, capturing the latest match's body — non-blocking, the process keeps running while it watches. |
| awaitListener Listener Request Response | name, variable, timeoutMs? | Waits (30s default) for the named listener to have captured a response, then saves its body into a variable. |
| stopListener Stop Listener Request | name | Stops watching and discards a named listener. |
| downloadFile Download File | selector, saveAs?, variable? | Clicks an element expected to trigger a browser download, waits for the file to finish landing in this profile's downloads folder, optionally renames it, and can save the resulting path into a variable. |
| Command | Parameters | What it does |
|---|---|---|
| if | condition, then, else? | Branch on a condition (see below). else is optional — with no matching branch, execution just continues to the next step. |
| loop For Loop Times | count, body | Repeat a set of steps count times, up to a cap of 10,000 iterations (enforced even if you paste a larger value via Import). A malformed count (zero, negative, fractional) safely floors to 1. |
| forElements For Loop Elements | selector, indexVariable, body | Run once per element currently matching selector (also capped at 10,000). Each iteration sets indexVariable to that element's 0-based index as a string. |
| forData For Loop Data | items, itemVariable, indexVariable?, body | Run once per line in a fixed list typed into the step itself. Each iteration sets itemVariable to that line, and indexVariable (if given) to its 0-based index. |
| whileLoop While Loop | condition, body | Repeat while condition holds, re-checked before every iteration. Also capped at 10,000 iterations — unlike the loops above, a condition has no natural bound of its own, so this is what stops a condition that never flips from spinning forever. |
| exitLoop Exit Loop | — | Immediately stops the nearest enclosing loop (any of the four above) and continues with whatever comes after it — like break in a normal language. Works from inside a nested if too. With no enclosing loop at all, it just stops the remaining steps in its own sequence. |
| Command | Parameters | What it does |
|---|---|---|
| googleSheet Google Sheet | operation, spreadsheetId, worksheetName, scope?, firstRowIsKey?, variable?, values?, append? |
Read, write, or clear spreadsheet data — see the setup section below. |
Using loop variables — ${...}
forElements, forData, and whileLoop are only useful if body steps can
act on the current item — so any selector, text, URL,
or value field elsewhere in the process can reference a captured variable with
${variableName}, substituted fresh on every iteration. This works with any variable — one set by
extractText/extractAttribute/evaluate earlier in the process too, not
just a loop's own index/item variable.
{ "type": "forData", "items": ["alice@example.com", "bob@example.com"],
"itemVariable": "email",
"body": [
{ "type": "type", "selector": "#email", "text": "${email}" },
{ "type": "click", "selector": "#submit" }
]
}
evaluate's script (it's raw JavaScript — read a variable there via the run's
own variable bag instead) and condition values (those compare against literal text).
Conditions (if / whileLoop)
| Condition | Parameters | Matches when… |
|---|---|---|
| elementExists | selector | An element matching the selector is present. |
| elementNotExists | selector | No element matches the selector. |
| variableExists | variable | The variable has a non-empty value. An extraction that found nothing saves "", which counts as not existing — that's the main thing this is for. |
| variableNotExists | variable | The variable is unset or empty. |
| variableEquals / variableNotEquals | variable, value | Exact text match (or its inverse). |
| variableContains / variableNotContains | variable, value | Substring match (or its inverse). |
| variableGreaterThan / variableGreaterOrEqual / variableLessThan / variableLessOrEqual | variable, value | Numeric comparison — both sides parsed as numbers, so "50" correctly ranks above "9". A non-numeric value makes the condition false rather than falling back to a text compare. |
| variableOneOf / variableNotOneOf | variable, value | The variable exactly matches one entry in a comma-separated set (or doesn't). |
Data Processing
Pure variable transforms — no page interaction. Useful for reshaping whatever you extracted before branching on it.
| Command | Parameters | What it does |
|---|---|---|
| extractRegex Extract from Txt | variable, pattern, saveTo | Pulls the first regex match out of a variable. With a capture group, saves group 1; otherwise the whole match. No match saves an empty string rather than failing — branch on it with a variableExists condition. |
| toJson Convert to JSON | variable, saveTo | Validates a variable as JSON and re-saves it normalized. Required before extractField / randomExtract, which both refuse non-JSON input with a message saying so. |
| extractField Extract Field | variable, path, saveTo | Reads a key or array index out of a JSON variable. Supports dotted paths — user.name, items.0.id — so a nested value doesn't need a chain of steps. An object/array result is re-serialized as JSON so you can keep digging. |
| randomExtract Random Extraction | variable, saveTo | Like extractField, but picks a random entry instead of a named one. |
Profile Information
Writes back to the profile the process is running against — handy for recording an outcome that a later run (or you) can see in the Profiles grid.
| Command | Parameters | What it does |
|---|---|---|
| updateProfileRemark Update Profile Remark | text, mode | Writes the profile's notes. append adds below the existing text, replace overwrites. The text goes through ${variable} interpolation. |
| updateProfileGroup Update Profile Tag | group | Moves the profile into a group — this app's equivalent of AdsPower's profile tag. |
Google Sheets
The Google Sheet action reads a sheet into a variable, writes rows back, or clears a range — so a process can pull its input data from a spreadsheet and log its results to one. Combined with For Loop Data, that's the usual "run this once per row" pattern.
One-time setup
Access uses a Google service account — a robot Google account you create and share sheets with.
- In the Google Cloud console, create (or pick) a project and enable the Google Sheets API.
- Go to Credentials → Create credentials → Service account and create one.
- Open the new service account → Keys → Add key → Create new key → JSON. A
.jsonfile downloads. - In Parallel, open ⚙ Settings → Google Sheets, paste the entire contents of that file, and click Connect. Parallel verifies the key immediately.
- Settings then shows the service account's email address. Open any spreadsheet you want to use, click Share, and share it with that address — Viewer is enough to read, Editor is required to write or clear.
Fields
| Field | What it does |
|---|---|
| Operation type | Read, Write, or Clear. |
| Spreadsheet ID | From the sheet's URL — the part between /d/ and /edit. |
| Worksheet name | The tab's name, e.g. Sheet1. Names containing spaces are handled for you. |
| Scope | Optional A1 range within that tab, e.g. A1:B10. Blank means the whole sheet. |
| The first line is set to key Read only | Reshapes the result into objects keyed by the header row instead of a raw grid of rows. |
| Save to Read only | Variable the result is stored in, as a JSON string. |
| Values Write only | One row per line, cells separated by commas. Every cell goes through ${...} interpolation. |
| Append Write only | Adds rows after the last existing row instead of overwriting the range — the right choice for logging results. |
Example: run a process once per row
Read a sheet of accounts, loop over the rows, and append a result line for each — the shape most Sheets-driven automations take.
1. Google Sheet — Read
Spreadsheet ID: 1AbC… Worksheet: Accounts
☑ The first line is set to key Save to: rows
2. Run JavaScript
Script: JSON.parse(vars).map(r => r.email).join('\n')
(turn the JSON into one email per line for the loop below)
3. For Loop Data — items: the emails, itemVariable: email
└─ Go to URL: https://example.com/login
└─ Type: selector #email, text ${email}
└─ Google Sheet — Write
Worksheet: Results ☑ Append
Values: ${email},done
Run JavaScript step first (as above) — For Loop Data takes a plain list, not JSON.
Process settings
| Setting | Options | What it does |
|---|---|---|
| Group | Any group, or none | Shares the same Groups list as profiles. |
| On error | Skip (default) / Stop | Skip: a failed step is recorded and the run continues past it — useful when an occasional optional element (a cookie banner) shouldn't abort everything after it. Stop: halts at the first failure, like an unhandled exception would stop a hand-written script. |
| After task completes | Clear tab / Save tab, and Quit Browser / Keep browser open | Two independent choices for what happens to the browser once the process finishes. |
Running & debugging
Click Debug in the process editor's header — a popup lets you pick which profile to run it on, then launches (or reuses an already-running) profile and executes the process against it.
Every run is recorded in that process's Debug logs — the last 20 runs per process, showing
the full step tree exactly as it executed, including which nested steps inside an if or
loop ran and whether each one succeeded or failed. Screenshots taken mid-run appear inline.
Import / export
Export a process to JSON to share it, back it up, or version it outside the app. Import accepts pasted JSON with a choice of Append (add to your existing processes) or Replace (overwrite). Since a hand-pasted or scripted import bypasses the editor's own UI limits, the same 10,000-loop cap is enforced when the process actually runs, not just when you build it in the editor.
Signed in, processes sync to your account the same way profiles do — build one on one machine, sign in elsewhere, and it's already there.