Migrating from Appium / WebdriverIO
A step-by-step guide for moving your Roku E2E test suite from Appium to Uncle Jesse. Most patterns have direct equivalents.
Overview
If you're running Roku tests with Appium + WebdriverIO today, you're dealing with:
- Java runtime dependency (Appium server)
- Selenium Grid or local server management
- WebDriver protocol overhead over HTTP
- Appium Roku driver quirks and version pinning
- Flaky element waits through the WebDriver abstraction
Uncle Jesse replaces all of that with direct HTTP calls to ECP on port 8060. Same test patterns, no middleware.
Why Migrate
| Factor | Appium/WebdriverIO | Uncle Jesse |
|---|---|---|
| Runtime dependency | Java + Appium server | Node.js only |
| Protocol | WebDriver → Appium → ECP | Direct HTTP to ECP |
| Setup time | 15-30 min (Java, Appium, driver) | npm install (30 seconds) |
| Element queries | XPath or custom locators | CSS-like selectors |
| Focus testing | Manual key-count scripting | focusPath with visual replay |
| Page objects | WebdriverIO pattern | Compatible BasePage/BaseComponent |
| Parallel devices | Selenium Grid config | Built-in DevicePool |
| Debug output | Appium logs | BrightScript console capture + replay HTML |
Side-by-Side Comparison
Finding an element
Before (WebdriverIO):
const element = await $('~screenTitle');
// or XPath
const el = await $('//Label[@name="screenTitle"]'); After (Uncle Jesse):
const element = await tv.$('Label#screenTitle');
// or just by name
const el = await tv.$('#screenTitle'); Pressing remote keys
Before:
await driver.pressKeyCode('Right');
await driver.pressKeyCode('Right');
await driver.pressKeyCode('Right');
await driver.pressKeyCode('Select'); After:
await tv.press('right', { times: 3 });
await tv.select(); Waiting for an element
Before:
await $('~loadingSpinner').waitForDisplayed({ reverse: true, timeout: 10000 });
await $('~contentGrid').waitForExist({ timeout: 10000 }); After:
const spinner = tv.$('LoadingSpinner');
await spinner.toNotBeDisplayed({ timeout: 10000 });
const grid = tv.$('ContentGrid');
await grid.toExist({ timeout: 10000 }); Selector Migration
| WebdriverIO | Uncle Jesse |
|---|---|
$('~elementName') | tv.$('#elementName') |
$('//Label[@name="title"]') | tv.$('Label#title') |
$('//HomeScreen//RowList') | tv.$('HomeScreen RowList') |
$('//LayoutGroup/Label') | tv.$('LayoutGroup > Label') |
$$('//NavTab') | tv.$$('NavTab') |
Page Object Migration
Uncle Jesse's BasePage and BaseComponent are designed to be a near drop-in replacement for WebdriverIO page objects.
Before:
class HomePage extends Page {
get grid() { return $('~contentGrid'); }
get title() { return $('~screenTitle'); }
async open() {
await driver.pressKeyCode('Home');
await this.grid.waitForExist();
}
} After:
class HomePage extends BasePage {
get root() { return this.$('HomeScreen'); }
get grid() { return this.$('HomeScreen ContentGrid'); }
get title() { return this.$('#screenTitle'); }
async waitForLoaded() {
await this.root.toBeDisplayed();
await this.grid.toExist();
}
} Key differences: selectors scope to the page's subtree, and elements are LiveElement instances with built-in assertions.
Assertion Migration
| WebdriverIO | Uncle Jesse |
|---|---|
expect(el).toBeDisplayed() | await el.toBeDisplayed() |
expect(el).toHaveText('Home') | await el.toHaveText('Home') |
expect(el).toHaveAttr('focused', 'true') | await el.toBeFocused() |
expect(els).toBeElementsArrayOfSize(3) | await els.toHaveLength(3) |
Uncle Jesse assertions poll automatically — no need for explicit waitUntil wrappers.
Setup & Teardown
Before:
// wdio.conf.js — pages of Appium capabilities, server config,
// driver timeouts, implicit waits... After:
import { RokuAdapter } from '@danecodes/uncle-jesse-roku';
let tv: RokuAdapter;
beforeEach(async () => {
tv = new RokuAdapter({ name: 'test', ip: '192.168.1.100' });
await tv.connect();
await tv.home();
await tv.launchApp('dev');
});
afterEach(async () => {
await tv.disconnect();
}); CI Integration
Replace your Appium server setup in CI with a simple npm script:
# Before: start Appium server, configure capabilities, manage driver versions
# After:
npx uncle-jesse test --reporter junit Uncle Jesse's CLI outputs JUnit XML and CTRF JSON for any CI system. Exit code 1 on failure.
Migration Checklist
- Install Uncle Jesse packages
- Convert selectors from XPath /
~nameto CSS-like syntax - Convert page objects to
BasePage/BaseComponent - Replace
driver.pressKeyCodewithtv.press() - Replace
browser.pause()withwaitForStable()or element assertions - Replace navigation test scripts with
focusPath - Update CI pipeline (remove Appium/Java, add
npx uncle-jesse test) - Remove Appium, WebdriverIO, and Java dependencies
- Run tests against a real device to verify