Skip to content

Latest commit

 

History

History
200 lines (156 loc) · 5.19 KB

File metadata and controls

200 lines (156 loc) · 5.19 KB

Migrating from Appium/WebdriverIO

How to move a Roku E2E test suite from the Appium stack (WebdriverIO + appium-roku-driver + Selenium Grid) to Uncle Jesse. See also: API Reference, Roku Focus Behavior.

What changes

Before After
WebdriverIO client @danecodes/uncle-jesse-core LiveElement
appium-roku-driver @danecodes/uncle-jesse-roku RokuAdapter
Appium server (Java) Nothing. Uncle Jesse talks ECP directly.
Selenium Grid Not yet supported. Single device for now.
browser.$('selector') this.$('selector') (same CSS selectors)
element.click() element.select()
element.waitForDisplayed() element.toBeDisplayed()
driver.sendKeys(Key.Right) device.press('right')
driver.waitUntil(fn) device.waitUntil(fn)
driver.pause(1000) device.pause(1000)

Selectors

Your existing CSS selectors work without changes. Uncle Jesse uses the same selector syntax against the same SceneGraph XML tree:

// These selectors work the same way
this.$('HomePage HeroCarousel')
this.$('Button#infoBtn')
this.$('#infoContainer Label')
this.$('ContentList ContentCard')
this.$('HomePage ContentShelf:has(+ ContentShelf)')
this.$('Label[text="Play"]')

Page objects

Replace BasePage and BaseComponent imports:

// Before
import { BasePage } from '../internal/atf.js';
import { BaseComponent } from '../internal/atf.js';

// After
import { BasePage, BaseComponent } from '@danecodes/uncle-jesse-core';

The class structure stays the same:

export class HeroCarousel extends BaseComponent {
  get currentCard() {
    return new HeroCarouselCard(this.$('HeroCarouselCard'));
  }

  get cards() {
    return this.$$('HeroCarouselCard', HeroCarouselCard);
  }

  get paginator() {
    return this.$('CarouselPaginator#paginator');
  }
}

Element assertions

Replace WebdriverIO assertion patterns with LiveElement methods. Same behavior -- they poll until the condition is met or timeout.

// Before
await element.waitForDisplayed();
await element.waitForExist();
await expect(element).toBeFocused();

// After
await element.toBeDisplayed();
await element.toExist();
await element.toBeFocused();

All assertions accept an optional { timeout } parameter:

await element.toBeFocused({ timeout: 5000 });
await element.toHaveText('Play', { timeout: 3000 });

Device interaction

// Before
await device.sendKeys(Key.Right, { times: 2, delay: 500 });
await device.sendKeys(Key.Select);
await device.sendKeys(Key.Back);
await device.pause(1000);

// After
await device.press('right', { times: 2, delay: 500 });
await device.select();
await device.back();
// No pause needed - assertions poll automatically

Element methods

// Before
const text = await element.getText();
const attr = await element.getAttribute('opacity');
const visible = await element.isDisplayed();
const exists = await element.isExisting();

// After (same API)
const text = await element.getText();
const attr = await element.getAttribute('opacity');
const visible = await element.isDisplayed();
const exists = await element.isExisting();

Test setup

// Before
import { RokuBuilder } from '../../src/RokuBuilder.js';

beforeEach(async (ctx) => {
  const roku = await new RokuBuilder(ctx)
    .withRegistry(skipOnboarding())
    .connect();
  app = roku.app;
  device = roku.device;
});

// After
import { RokuAdapter } from '@danecodes/uncle-jesse-roku';
import { RegistryState } from '@danecodes/uncle-jesse-core';

beforeEach(async () => {
  device = new RokuAdapter({
    name: 'test',
    ip: process.env.ROKU_IP ?? '192.168.1.100',
    devPassword: 'rokudev',
  });
  await device.connect();
  home = new HomePage(device, null);
  await device.home();
  // Define app-specific registry factories in your test data layer
  const registry = new RegistryState().set('MY_APP', 'isFirstLaunch', 'false');
  await device.launchApp('dev', registry.toLaunchParams());
  await home.waitForLoaded();
});

afterEach(async () => {
  await device.disconnect();
});

element.focus() and select()

// Before
await element.focus();
await element.select({ ifNotDisplayedNavigate: Direction.DOWN });

// After
// focus() uses element bounds to determine which direction to navigate.
// It also focuses the parent container first if needed.
await element.focus();
await element.select({ ifNotDisplayedNavigate: 'down' });

Multi-device parallel

// Before: Selenium Grid distributes sessions across runners

// After: DevicePool manages device allocation
import { DevicePool } from '@danecodes/uncle-jesse-core';

const pool = new DevicePool(devices, { acquireTimeout: 30000 });
const device = await pool.acquire();
// ... run tests ...
pool.release(device);

driver.waitUntil() and pause()

// Before
await driver.waitUntil(() => someCondition(), { timeout: 5000 });
await driver.pause(1000);

// After (same API)
await device.waitUntil(() => someCondition(), { timeout: 5000 });
await device.pause(1000);

What's not yet supported

  • Selenium Grid protocol (DevicePool replaces this with a simpler model)