Capabilities Guide
How to use RobotActions' ra:* desired capabilities — at session create, at runtime, and via the REST/WebSocket APIs. For the full per-capability table see the Capabilities Reference.
1. What are capabilities?
Capabilities are a JSON object you send to the grid at the POST /session call. They tell the grid what platform you want (Android, iOS, browser X), which device/version, and what extra behaviour you want from the session — recording, profiling, test naming, etc.
The W3C WebDriver spec defines a core set of capabilities (platformName, browserName, browserVersion, …). Drivers add vendor-specific caps under their own namespace — Appium uses appium:*, Selenium uses se:*, and RobotActions uses ra:*.
// Capabilities are a JSON payload you send when creating a WebDriver session.
// The grid extracts ra:* values, strips them before forwarding to Appium /
// Selenium, and uses them to opt into recording, profiling, suite grouping,
// and so on.
{
"platformName": "Android",
"appium:app": "path/to/app.apk",
"appium:deviceName": "Google Pixel 8",
// RobotActions namespace
"ra:testName": "Login — happy path",
"ra:videoRecording": true
}2. The ra:* namespace
Every RobotActions capability is prefixed with ra:. Three reasons:
- W3C compliance — vendor-prefixed extension caps are the supported way to add proprietary behaviour without breaking spec validators.
- Conflict-free — won't collide with future Appium or Selenium caps, or with caps from other cloud vendors.
- Predictable lifecycle — the proxy strips
ra:*before forwarding to the Selenium Hub / Appium server, so the underlying drivers never see them. No risk of an unrecognised-cap rejection.
capabilities.alwaysMatch. Legacy JSON Wire clients use desiredCapabilities. Both work; the grid reads from either bucket and from every firstMatch[*] entry as well.3. Setting caps at session create
Pass ra:* caps the same way you pass any other vendor cap — through your client's options/capabilities builder. Three examples in three popular clients:
Selenium (Python)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
opts = Options()
opts.set_capability("ra:testName", "Checkout — happy path")
opts.set_capability("ra:testsuite", "regression-2026-05-15")
opts.set_capability("ra:videoRecording", True)
driver = webdriver.Remote(
command_executor="https://<subdomain>.robotactions.com/t/<token>",
options=opts,
)Appium (Java)
UiAutomator2Options opts = new UiAutomator2Options()
.setUdid("9B051FFBA007KA")
.setAppPackage("com.myapp")
.setAppActivity("com.myapp.MainActivity")
.amend("ra:testName", "Login flow")
.amend("ra:testsuite", "smoke-2026-05-16")
.amend("ra:videoRecording", true)
.amend("ra:appProfiling", true); // CPU/memory/network samples — Android only
AndroidDriver driver = new AndroidDriver(
new URL("https://<subdomain>.robotactions.com/t/<token>"),
opts);WebdriverIO (JavaScript)
const browser = await remote({
protocol: "https",
hostname: "<subdomain>.robotactions.com",
path: "/t/<token>",
port: 443,
capabilities: {
platformName: "Android",
"appium:automationName": "uiautomator2",
"appium:udid": "9B051FFBA007KA",
"ra:testName": "Checkout regression",
"ra:testsuite": "nightly-2026-05-16",
"ra:videoRecording": true,
},
});4. Setting them at runtime
Some caps you don't know at session-create time — the test name might be derived from the test framework's runtime, the result is by definition known only after the test runs, the suite name might depend on a CI build ID injected mid-run.
For these, RobotActions intercepts magic strings passed to executeScript — before the string reaches the underlying browser or device — and acts on them server-side. Works in every WebDriver-protocol client.
// Inside the test body — set pass/fail + naming from the test itself.
// Works in any WebDriver-protocol client (Selenium, Appium, WebdriverIO).
// The grid intercepts the magic string BEFORE it reaches the underlying
// browser/device, so the test never sees an error if the verb is unknown.
driver.execute_script("ra:job-name=Checkout — happy path")
driver.execute_script("ra:testsuite=regression-2026-05-15")
try:
run_checkout_flow()
driver.execute_script("ra:job-result=passed")
except AssertionError as e:
driver.execute_script(f"ra:job-result=failed:{e}")Verbs available: ra:job-result=passed / ra:job-result=failed:<reason>, ra:job-name=<name>, ra:testsuite=<suite>, ra:fail-reason=<msg> (message only, doesn't change result), ra:profile-start / ra:profile-stop (Android profiling, runtime control).
5. REST & WebSocket result APIs
The executeScript magic works for WebDriver-protocol clients. For Playwright — which doesn't expose executeScript in the same shape — or for CI scripts that need to write the result after the test process has exited, the grid exposes a REST endpoint:
# Set result + test name from outside the test (CI script, post-step hook, etc).
# Works for any client — including Playwright where executeScript magic isn't available.
curl -X POST "https://<subdomain>.robotactions.com:3001/api/sessions/$SESSION_ID/result" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"result": "failed",
"message": "Login API returned 503",
"test_name": "Auth — invalid creds",
"test_suite": "smoke-2026-05-16"
}'The same write is also available over WebSocket if your client already has a connection to the grid's WS endpoint — send { "type": "setSessionResult", "sessionId": "...", "result": "passed" | "failed", "message": "...", "testName": "..." } and the grid replies with { "type": "sessionResultUpdated", "success": true }.
sessions.result + sessions.result_message + sessions.test_name columns. Use whichever fits your client; the dashboard, Reports tab, and suite rollups treat them identically.6. Common patterns
Headless CI runs
When the test runs unattended (nightly regression, PR check), nobody's watching the dashboard live-view and Inspector isn't connected. Turn off the live streams to save CPU + bandwidth, but keep MP4 recording so failures can be reviewed post-mortem.
// CI runs where the screen-mirror isn't needed — save CPU + bandwidth.
{
"platformName": "Android",
"appium:app": "build/app-debug.apk",
"appium:deviceName": "Pixel 8",
"ra:liveView": false, // disable the dashboard live-view button
"ra:liveVideo": false, // strip mjpeg* — no Inspector screen-mirror
"ra:videoRecording": true, // keep MP4 recording for post-run debugging
"ra:autoFailDetect": true, // default — let the grid mark failed if the last cmd 4xx'd
"ra:testName": "${env.TEST_NAME}",
"ra:testsuite": "ci-${env.BUILD_ID}"
}Test grouping for Reports
Pair ra:testsuite with ra:testName so the Reports tab can show per-suite rollup cards (total runs, passed, failed, unmarked).
// Surface a coherent build/suite view in the Reports tab.
// ra:testsuite groups runs; ra:testName labels each leaf test.
{
"platformName": "Android",
"appium:app": "build/app-debug.apk",
"ra:testsuite": "regression-2026-05-15", // shared across every test in the suite
"ra:testName": "Settings — toggle dark mode"
}Android app profiling
Add "ra:appProfiling": true at session create. The Appium plugin samples mobile:getPerformanceData every ~3s and writes one JSONL line per tick (CPU user/kernel, totalPss memory, network rx/tx bytes, battery power). Available on the session's Performance tab as four stacked SVG line charts with hover correlation. Requires a resolvable appium:appPackage (or appium:appActivity prefix).
Runtime control: driver.execute_script("ra:profile-start") / "ra:profile-stop". Idempotent.
7. Reference table
For the full per-capability table with type, default, scope, and detail card for each ra:* cap, see the dedicated reference page.