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

FactorAppium/WebdriverIOUncle Jesse
Runtime dependencyJava + Appium serverNode.js only
ProtocolWebDriver → Appium → ECPDirect HTTP to ECP
Setup time15-30 min (Java, Appium, driver)npm install (30 seconds)
Element queriesXPath or custom locatorsCSS-like selectors
Focus testingManual key-count scriptingfocusPath with visual replay
Page objectsWebdriverIO patternCompatible BasePage/BaseComponent
Parallel devicesSelenium Grid configBuilt-in DevicePool
Debug outputAppium logsBrightScript 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

WebdriverIOUncle 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

WebdriverIOUncle 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 / ~name to CSS-like syntax
  • Convert page objects to BasePage / BaseComponent
  • Replace driver.pressKeyCode with tv.press()
  • Replace browser.pause() with waitForStable() 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