Skip to main content

Roku WebDriver

Introduction​

This page is a reference for the element model, supported commands, execute methods, and key mappings of the Roku WebDriver integration.

The Roku WebDriver integration is written by TV Labs for the Roku platform. It is an Appium compatible WebDriver, so it can be driven from any Appium or WebDriver client, and all Appium Roku 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.

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.

warning

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​

StrategyMatches
xpathEvaluated against the page source
id, nameThe node's name attribute
accessibility idThe node's uiElementId attribute
class nameThe node's tag — its SceneGraph node type, e.g. Label, Poster
tag nameThe 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 interactable as 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​

CommandBehavior on Roku
getTextThe node's text attribute
getAttributeThe SceneGraph attribute of that name, or null when the node doesn't carry it
getPropertyThe same as getAttribute — SceneGraph nodes have fields, not a separate property model
getRectThe node's bounds, or a zero rect when it has none
isSelectedWhether the remote's focus is on the element
isDisplayed, isEnabledtrue for any element that resolves — the tree carries no visibility or disabled state
getComputedRoleThe node's tag
getComputedLabelThe node's text attribute
note

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​

CommandBehavior on Roku
getPageSourceThe <AppiumAUT>-rooted element hierarchy
getActiveElementThe innermost focused node
takeScreenshotServed from the device's video capture
getWindowRectThe device's UI render resolution, which is the space bounds is expressed in
minimizeWindowPresses Home and returns the window rect
getContextsAlways 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.

Execute methods​

The roku: execute methods expose the device's External Control Protocol (ECP). They are available on any Roku session.

note

roku: installApp and roku: removeApp also exist, but sideloading is managed by the platform. Select a build with the tvlabs:build capability instead.

roku: activateApp​

Launches a channel.

Arguments:

  • appId (required, string)
    • The channel to launch
  • contentId (optional, string)
  • mediaType (optional, string)
    • Deep-link media type

Example:

await driver.execute('roku: activateApp', { appId: 'dev' });

Deep-links into content in the channel that is already running. Use roku: activateApp to deep-link into a channel that isn't.

Arguments:

  • contentId (required, string)
    • The content to open
  • mediaType (optional, string)

Example:

await driver.execute('roku: deepLink', { contentId: '12345', mediaType: 'series' });

roku: pressKey​

Presses a key on the remote. See the key map for the accepted names. Any other value is sent to ECP as a raw key value.

Arguments:

  • key (required, string)
    • The key to press

Example:

await driver.execute('roku: pressKey', { key: 'Home' });

roku: longPress​

Holds a key down and then releases it.

Arguments:

  • key (required, string)
    • The key to hold, from the same key map as roku: pressKey
  • durationMs (optional, number)
    • How long to hold it, in milliseconds
    • Default: 1000

Example:

await driver.execute('roku: longPress', { key: 'Select', durationMs: 2000 });

roku: deviceInfo​

Returns the device's /query/device-info fields as an object.

Arguments: None

Example:

const info = await driver.execute('roku: deviceInfo');

console.log(info['software-version']);
// 14.10.5

roku: getApps​

Returns every channel installed on the device, as a list of objects with id, name, type, subtype, and version.

Arguments: None

Example:

const apps = await driver.execute('roku: getApps');

console.log(apps);
// [{ id: '12', name: 'Netflix', type: 'appl', subtype: 'ndka', version: '5.1.10' }, ...]

roku: activeApp​

Returns the channel currently in the foreground, in the same shape as roku: getApps.

Unlike tvlabs: currentApp, this reports the raw ECP answer: the home screen comes back as an entry of its own rather than as an error.

Arguments: None

Example:

const app = await driver.execute('roku: activeApp');

console.log(app);
// { id: 'dev', name: 'TV Labs', type: 'appl', subtype: 'rsga', version: '1.0.0' }

roku: appUI​

Returns the XML app hierarchy from /query/app-ui, as the <topscreen> element. Only available while the sideloaded channel is running.

Example:

const topscreen = await driver.execute('roku: appUI');

roku: playerState​

Returns the state of the media player, from /query/media-player.

Arguments: None

Example:

const { player } = await driver.execute('roku: playerState');

console.log(player.state);
// play

roku: selectElement​

Moves focus onto an element, pressing whichever remote keys get it there. Errors if focus can't be reached.

Arguments:

  • elementId (required, string)
    • The element to focus — the id returned by a previous findElement, or an xpath locator

Example:

await driver.execute('roku: selectElement', { elementId: '//Label[@text="Play"]' });

roku: ecp​

Calls an arbitrary ECP endpoint and returns the response body as text. The escape hatch for anything the methods above don't cover.

Arguments:

  • url (required, string)
    • The ECP path, starting with /
  • method (optional, string)
    • HTTP method
    • Default: "POST"
  • body (optional, string)
    • Request body
    • Default: ""

Example:

const xml = await driver.execute('roku: ecp', {
url: '/query/device-info',
method: 'GET',
});

roku: ecpInput​

Posts to the /input ECP endpoint. The argument object is the parameter set itself — any key/value pairs, URL-encoded for you.

Example:

await driver.execute('roku: ecpInput', {
'acceleration.x': '0.0',
'acceleration.y': '0.0',
'acceleration.z': '9.8',
});

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 keycodeAlternate nameDescription
HomehomeHome
BackbackBack
UpupUp
DowndownDown
LeftleftLeft
RightrightRight
Selectselect, okOK / Select
RevrewindRewind
FwdforwardFast forward
PlayplayPlay / Pause
InstantReplayreplayInstant replay
InfoinfoInfo / *
BackspacebackspaceBackspace
SearchsearchSearch
EnterenterEnter
VolumeUpvolume_upVolume up
VolumeDownvolume_downVolume down
VolumeMutevolume_mute, muteMute toggle
ChannelUpchannel_upChannel up
ChannelDownchannel_downChannel down
InputTunerinput_tunerTuner input
InputHDMI1input_hdmi1HDMI 1 input
InputHDMI2input_hdmi2HDMI 2 input
InputHDMI3input_hdmi3HDMI 3 input
InputHDMI4input_hdmi4HDMI 4 input
InputAV1input_av1AV 1 input
PowerpowerPower toggle
PowerOnpower_onPower on
PowerOffpower_offPower off
FindRemotefind_remoteFind remote
note

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' });