Roku WebDriver
Introduction
This page is a reference for the element model, supported commands, and key mappings of the Roku WebDriver integration.
The Roku WebDriver integration is written by TV Labs for the Roku platform. It is Appium compatible, so all Appium driver commands are available. The integration interfaces with the device over ECP — Roku's External Control Protocol (HTTP and websocket) — and the device's developer web server.
The roku: execute methods are documented on the Execute Methods page.
Sessions
Select a build with the tvlabs:build capability and it is sideloaded onto the device and launched when the session starts. A sideloaded channel always has the app id dev.
A session started without a build is still a valid session — the device is returned to the home screen and no channel is launched. Element commands are unavailable until a developer channel is running; see Finding elements.
Finding elements
The element tree is the running channel's SceneGraph hierarchy, read from ECP's /query/app-ui.
Roku only serves /query/app-ui for the sideloaded developer channel. A store channel answers "Not authorized" and the home screen answers "No active app", and element commands return a no such element error carrying that message. This is a Roku restriction, not a TV Labs one.
getPageSource returns that hierarchy rooted at a synthetic <AppiumAUT> element, and locators are evaluated against the same document you get back from it.
Locator strategies
| Strategy | Matches |
|---|---|
xpath | Evaluated against the page source |
id, name | The node's name attribute |
accessibility id | The node's uiElementId attribute |
class name | The node's tag — its SceneGraph node type, e.g. Label, Poster |
tag name | The node's tag |
Any other strategy returns an invalid selector error, as does an XPath expression the server can't parse.
findElement and findElements honor the session's implicit wait, re-reading the tree from the device until the timeout expires. Set it once and let it cover the channel's own render latency:
await driver.setTimeout({ implicit: 10000 });
const playButton = await driver.$('//Label[@text="Play"]');
Element references
Roku's tree is re-read from the device on every element command, so an element reference is resolved fresh each time rather than pointing at a cached node. An element that has left the tree returns a stale element reference error, and a reference that no longer identifies a single node returns unknown error asking for a more specific selector.
Interacting with elements
Clicking
Roku has no pointer. click moves focus onto the element by pressing direction keys, then presses Select.
This means clicking depends on the channel drawing a focus path to the element:
- Something on screen has to report focus for the walk to have a direction to move from. If nothing does, the click returns
element not interactable. - Neither the element nor any of its ancestors having a focused sibling returns
element not interactableas well — there is nowhere to walk from. - The walk gives up after 20 key presses, also with
element not interactable.
To move focus onto an element without activating it, use roku: selectElement.
Entering text
sendKeys looks for Roku's on-screen keyboard in the element tree, presses Select on the element to summon one if there isn't one already, types the string, and then dismisses the keyboard — pressing its OK button when the dialog has one, and Back otherwise.
const searchField = await driver.$('//TextEditBox');
await searchField.setValue('planet earth');
clear is not supported. Clear a field with the remote instead, for example by pressing Backspace with roku: pressKey once per character.
Element properties
| Command | Behavior on Roku |
|---|---|
getText | The node's text attribute |
getAttribute | The SceneGraph attribute of that name, or null when the node doesn't carry it |
getProperty | The same as getAttribute — SceneGraph nodes have fields, not a separate property model |
getRect | The node's bounds, or a zero rect when it has none |
isSelected | Whether the remote's focus is on the element |
isDisplayed, isEnabled | true for any element that resolves — the tree carries no visibility or disabled state |
getComputedRole | The node's tag |
getComputedLabel | The node's text attribute |
bounds is a node's box in its parent's coordinate space, not the screen's. A label inside a container positioned at {225, 375} reports its own origin as {0, 0}. Compare rects between siblings, not across containers.
Session-level commands
| Command | Behavior on Roku |
|---|---|
getPageSource | The <AppiumAUT>-rooted element hierarchy |
getActiveElement | The innermost focused node |
takeScreenshot | Served from the device's video capture |
getWindowRect | The device's UI render resolution, which is the space bounds is expressed in |
minimizeWindow | Presses Home and returns the window rect |
getContexts | Always the single NATIVE_APP context |
Roku sessions are native-only, so the commands that need a web context are not available: arbitrary JavaScript through executeScript, navigation commands such as back, the cookie endpoints, and element screenshots all return an error. Press the remote's Back button with roku: pressKey instead.
Key map
The following key names are accepted by roku: pressKey and roku: longPress. Both spellings of each key work and matching is case-insensitive, so InstantReplay, instantreplay, and replay are the same key.
| ECP keycode | Alternate name | Description |
|---|---|---|
Home | home | Home |
Back | back | Back |
Up | up | Up |
Down | down | Down |
Left | left | Left |
Right | right | Right |
Select | select, ok | OK / Select |
Rev | rewind | Rewind |
Fwd | forward | Fast forward |
Play | play | Play / Pause |
InstantReplay | replay | Instant replay |
Info | info | Info / * |
Backspace | backspace | Backspace |
Search | search | Search |
Enter | enter | Enter |
VolumeUp | volume_up | Volume up |
VolumeDown | volume_down | Volume down |
VolumeMute | volume_mute, mute | Mute toggle |
ChannelUp | channel_up | Channel up |
ChannelDown | channel_down | Channel down |
InputTuner | input_tuner | Tuner input |
InputHDMI1 | input_hdmi1 | HDMI 1 input |
InputHDMI2 | input_hdmi2 | HDMI 2 input |
InputHDMI3 | input_hdmi3 | HDMI 3 input |
InputHDMI4 | input_hdmi4 | HDMI 4 input |
InputAV1 | input_av1 | AV 1 input |
Power | power | Power toggle |
PowerOn | power_on | Power on |
PowerOff | power_off | Power off |
FindRemote | find_remote | Find remote |
The volume, input, and channel keys only have an effect on Roku TVs. A streaming player has no tuner, inputs, or speakers of its own.
A key name that isn't in the table is sent to ECP verbatim, so any keycode Roku accepts works — including the Lit_ literal-character form:
await driver.execute('roku: pressKey', { key: 'Lit_a' });