From Unit to E2E: A Guide to Testing React Native TV in 2026

This article is an excerpt from the upcoming Ultimate Guide to TV Development 2026. Sign up for the waitlist below to be the first to get the full guide when it’s released.

Testing code is not only helpful, but crucial for any production-grade software, TV apps included. Thankfully, most of the tools you already know from React Native mobile development are transferable to TV development: from Jest or Vitest as test runners, through React Native Testing Library as a go-to JS-level testing library for UI and interactions, to Appium for end-to-end testing.

Unfortunately, some mobile-specific testing frameworks that you may like, such as Maestro or Detox, will not be very helpful in a TV environment.

JavaScript tests

Whether you name them “unit” or “integration” tests, you’re likely writing these with JavaScript, testing the JS parts of your TV application. In that sense, you’re going to write exactly the same tests using React Native Testing Library.

What will be different on TV though, is that we’ll need a different set of helpers, allowing us to emulate the distinctive UX paradigms that are different from mobile or web. More specifically, you’ll need to provide a mock for remote-controlled navigation.

Here’s an example of a helper:

import { render, screen } from '@testing-library/react-native';

it('navigates and selects the play button', () => {
  const onPressMock = jest.fn();
	render(<MyComponent onPress={onPressMock} />);
	
	const infoButton = screen.getByRole('button', { name: 'Info' });
  const playButton = screen.getByRole('button', { name: 'Play' });

  fireEvent(infoButton, 'onFocus')

  tvRemote.right({ elementToFocus: playButton, elementToBlur: infoButton });
  tvRemote.select({ elementToSelect: playButton });

  expect(onPressMock).toHaveBeenCalled();
});

We’re setting initial focus on the “Info” button with fireEvent(infoButton, 'onFocus'), an API we know from testing React Native apps. Then to navigate to the “Play” button, we’re using a custom tvRemote helper with right and select methods. For this helper, we also need to set elementToFocus, elementToBlur, and elementToSelect as the focus engine is implemented in the native layer, and there’s no way to trigger actual focus movement in JavaScript tests.

This reflects a different kind of input for users to interact with, which can’t be emulated with a click event. However, it’s pretty similar to keyboard events that you’ll want to test for accessibility.

Setup

Since TV platforms don’t provide built-in helpers for these types of tests, we’ll create our own. The functions below form the building blocks for our tvRemote helper, allowing us to emit platform-specific native events and simulate remote-control navigation in our tests.

Let’s start with emitting hardware key events:

function emitHWKeyEvent(hwEventPayload) {
  act(() => {
    DeviceEventEmitter.emit('onHWKeyEvent', hwEventPayload);
  });
}

const PlatformSwitch = {
  select: (platforms) => {
    // where process.env.PLATFORM is "tvos" | "androidtv" | "firetv" 
    return platforms[process.env.PLATFORM] ?? Platform.select(platforms);
  },
};

const createEvent = ({ tag, action, type }) => {
  return PlatformSwitch.select({
    tvos: { eventType: type, body: {} },
    default: { eventType: type, eventKeyAction: action, tag, target: tag },
  });
};

Based on these raw emitHWKeyEvent and createEvent helpers, we can implement another set of helpers that emulate pressing up and down with a D-pad:

function preparePressDownEvent({ keyCode,  tag = 1 }) {
  return function emitPressDown() {
    emitHWKeyEvent(createEvent({ tag, type: keyCode, action: 0 }));
  };
}

function preparePressUpEvent({ keyCode, tag = 1 }) {
  return function emitPressUp() {
    emitHWKeyEvent(createEvent({ tag, type: keyCode, action: 1 }));
  };
}

We can even go a little crazier and start composing some higher-level helpers, such as emitDirectionalSinglePressEvent, which is designed to emulate pressing directional (up, down, left, right) buttons that adhere to native platform specifics:

function emitDirectionalSinglePressEvent(keyCode, params) {
  const emitPressDown = preparePressDownEvent({ keyCode });
  const emitPressUp = preparePressUpEvent({ keyCode });
  
  PlatformSwitch.select({
    tvos: [emitFocusEvent, emitBlurEvent, emitPressUp],
    default: [emitPressDown, emitBlurEvent, emitFocusEvent, emitPressUp],
  }).forEach((emitEvent) => act(() => emitEvent(params)));
}

Notice how tvos, which stands for Apple TV, emits different array of events based on the same kind of input, compared to the default TV platforms (such as Android TV in this case).

Now that we have these event helpers, we can implement some of that tvRemote function with right and select methods:

const tvRemote: TvRemoteAPI = {
  right: ({ elementToFocus, elementToBlur, tag } = {}) => {
    emitDirectionalSinglePressEvent('right', {
      elementToFocus,
      elementToBlur,
      tag,
    });
  },
  select: ({ elementToSelect } = {}) => {
    const emitPressDown = preparePressDownEvent({ keyCode: 'select' });
    const emitPressUp = preparePressUpEvent({ keyCode: 'select' });
    
    if (elementToSelect && Platform.isTvOS) {
      fireEvent(elementToSelect, 'pressIn');
      fireEvent.press(elementToSelect);
    }
    
    PlatformSwitch.select({
      tvos: [emitPressUp],
      default: [emitPressDown, emitPressUp],
    }).forEach((emitEvent) => act(() => emitEvent()));
    
    if (elementToSelect && !Platform.isTVOS) {
      fireEvent(elementToSelect, 'pressIn');
      fireEvent.press(elementToSelect);
    }
  },
};

There is currently no reusable library that would offer this set of helpers, so for now you’ll need to work it out for your use case.

The maintainers of React Native Testing Library are working on creating a dedicated set of helpers for TV platforms, which may already be available by the time you’re reading this book. Keep an eye on the project and follow Maciej Jastrzębski on X for important updates to the library.

End-to-end tests

When you want to test the full behavior of your app, end-to-end (E2E) tests are the way to go. For React Native TV apps, Appium is our best bet, as it supports Android TV and Apple TV automation via UIAutomator and XCUITest respectively. As many of the TV platforms out there are web-based and essentially display HTML with JS and CSS, we’ll also cover how to run similar tests there.

Native TV platforms

First, we’ll need some setup to run Appium on native Android TV and Apple TV devices.

Setup

You'll need to set up new drivers for new TV platforms you want to test with Appium. For example, when using WebdriverIO, one of the most popular test runners for Appium, you can add AndroidTV-specific capabilities in wdio.conf.ts:

capabilities: [{
  platformName: 'Android',
  automationName: 'UiAutomator2',
  deviceName: 'Android TV Emulator',
  appPackage: 'com.mycompany.tvapp',
  appActivity: 'com.mycompany.tvapp.MainActivity',
  newCommandTimeout: 300
}]

Similarly, we can add a second capability block for Apple TV:

capabilities:[
	// android capabilities...
	{
	  platformName: 'iOS',
	  platformVersion: '17.0', // match simulator version
	  deviceName: 'Apple TV',
	  automationName: 'XCUITest',
	  udid: 'auto', // or specific UDID of simulator/device
	  app: '/path/to/your/TVApp.app', // or path to IPA for device
	  newCommandTimeout: 300
	}
]

Example test

With this setup done, you can write your tests. For example, in our app, we can have a following button that will get us back to the home screen:

<Pressable
  accessibilityLabel="home-button"
  onPress={goHome}
>
  <Text>Home</Text>
</Pressable>

Because it’s described in an accessible way, we can use the accessibility selector (~) from WebdriverIO to access it. On top of that, by using driver.pressKeyCode we can control navigation similarly to how we do it with a D-pad, to access another screen:

describe('TV App Navigation', () => {
  it('navigates to Home and selects an item', async () => {
    const homeButton = await $('~home-button');
    await homeButton.click();

    await driver.pressKeyCode(20); // DPAD_DOWN
    await driver.pressKeyCode(23); // DPAD_CENTER

    const detailsSection = await $('~details-section');
    await expect(detailsSection).toBeDisplayed();
  });
});

Run tests with wdio in Terminal:

npx wdio run wdio.conf.ts

Web-based TV platforms

Many of the available TV platforms display web apps rendered by React. In a case like that, we can also leverage WebdriverIO, but with a different set of capabilities and services that allow us to test in a browser, e.g., Chrome:

capabilities: [{
  browserName: 'chrome',
  'goog:chromeOptions': {
    args: ['--window-size=1920,1080'],
    // Optional: emulate TV-like viewport
  }
}],

services: ['chromedriver'],

Example test

The same scenario, as we run on native Android and Apple TV devices, could be described in the following way, leveraging browser.keys instead of driver.pressKeyCode, and different selectors (such as aria/* or [data-testid=*]) that match the web platform:

describe('Web TV App (webOS)', () => {
  it('navigates with keyboard and selects an item', async () => {
    await browser.url('http://localhost:1234/index.html'); // your local dev server or built app

    const homeButton = await $('aria/Home');
    await homeButton.click();

    await browser.keys(['ArrowDown', 'Enter']); // simulates remote navigation

    const details = await $('[data-testid="details-section"]');
    await expect(details).toBeDisplayed();
  });
});

Run tests with wdio in Terminal:

npx wdio run wdio.web.conf.ts

Device farms

The examples above covered how to run end-to-end tests using emulators, simulators, and local browsers. While that's great for development and CI, real-world projects typically require testing on real devices to ensure accurate behavior, performance, and remote control input handling. This is where device farms become essential.

Popular device farm solutions include:

  • AWS Device Farm: Amazon’s managed testing service that supports Android, iOS, and custom test environments.
  • BrowserStack App Automate: A cloud platform that provides real device access for Android, iOS, and web browsers, with Appium integration out of the box.
  • Sauce Labs: A testing platform with robust support for mobile and web applications, including real devices and simulators.

These services allow you to run automated tests on a wide range of real smart TVs, set-top boxes, and mobile devices, all without maintaining physical hardware in-house.

Running tests on device farms

Depending on the service you use, you’ll need to send your app’s APK binary for Android TV and IPA binary for Apple TV to the service provider, usually using an API. Once uploaded, the services will provide you with an App ID (Browserstack) or ARN (AWS Device Farm), which you can later reference in your app capability:

  • On AWS, it will look like this: arn:aws:...
  • On BrowserStack: bs://abc123
  • On Sauce Labs: storage:filename

Note that you’ll need to set a different deviceName that reflects the device you’re going to run your tests on.

For Android TV:

capabilities: [{
  platformName: 'Android',
  deviceName: 'Samsung SmartTV',
  app: '<link-to-your-android-app-id>',
  automationName: 'UiAutomator2'
}]
services: ['browserstack'] // or other

And for Apple TV:

capabilities: [{
  platformName: 'iOS',
  deviceName: 'Apple TV 4K',
  app: '<link-to-your-apple-app-id>',
  automationName: 'XCUITest'
}]
services: ['browserstack'] // or other

The story is a little different for web-based TVs. Since they’re based on the web platform, teams will often test on web browsers, such as Chrome, set for specific screen sizes and dimensions, due to limited support for running automated tests on these platforms.

You can configure your Appium driver in a way that closely resembles the environment, including browser engine, user-agent, and screen resolution. Unfortunately it’s never going to be an exact match, due to hardware vendors running custom forks of popular browser engines. To test for specific platform quirks, you’ll need to run your app on a real device.

BrowserStack and Sauce Labs

Both BrowserStack and Sauce Labs support WebdriverIO, so you can run your tests directly through wdio CLI. Once you’re done with the setup, you can run your tests locally or on the CI server with this command:

npx wdio wdio.browserstack.conf.ts

Assuming wdio.browserstack.conf.ts is your configuration file for BrowserStack, you can run it in a similar fashion with Sauce Labs.

AWS Device Farm

Since WebdriverIO doesn’t provide a dedicated service for AWS Device Farm, you’ll need to zip your test suite, upload it through AWS CLI or Console, and run with the aws devicefarm command to execute your end-to-end tests:

aws devicefarm schedule-run \
  --project-arn arn:... \
  --app-arn arn:... \
  --device-pool-arn arn:... \
  --name "MyApp TV Run" \
  --test type=APPIUM_NODE,testPackageArn=arn:...

You’ll need to look up ARNs (Amazon Resource Names) for each configuration option.

With all of that, you’re ready to gradually automate your end-to-end test suite on Android TV, Apple TV, and web-based platforms such as webOS or Samsung Tizen. Keep in mind that you will need real devices and manual setup to properly test web platforms.

Callstack & Amazon ebook

The Ultimate Guide to React Native TV Development 2026 is out

Book consultation

We’ll connect you with the right expert within 24 hours.

Insights

Learn more about Testing

How AI Agents Can Cut Manual QA by 30% — cover

Oct 30Article

How AI Agents Can Cut Manual QA by 30%

Explore an agentic QA workflow targeting 30% less manual effort across mobile, web, and TV, with test selection, failure triage, and validation.

Give QA Agents the Controls They Need With Appduct — cover

Oct 20Article

Give QA Agents the Controls They Need With Appduct

Use Appduct to give AI QA agents access to app functions for setting test conditions, triggering events, and checking results with UI automation.

cover

Oct 13Article

Agentic Mobile Testing With e2e and agent-device

See how e2e uses agent-device for AI-driven iOS and Android tests, combines exact assertions with agent actions, and replays cached steps without model calls.

Who Tests AI-Written Code? Inside TesterArmy With Szymon Rybczak — cover

Oct 12Podcast

Who Tests AI-Written Code? Inside TesterArmy With Szymon Rybczak

Szymon Rybczak explains how TesterArmy tests web and mobile apps with AI agents, creates test plans for pull requests, and fits into QA workflows.

cover

Aug 4Article

Automating Mobile QA With Cloud Agents

Code review and green CI checks do not prove a mobile app still works. Learn how cloud agents, agent-device, and runtime evidence can verify app behavior before merge.

cover

Jul 21Webinar

Behavior Review for AI-Native Delivery

AI can increase code output faster than teams can verify the real app. See why behavior review matters before merge.

cover

Jul 6Article

Introducing Sniffler: Run only the E2E tests your changes can affect

Sniffler maps changed files to affected E2E tests, so teams can avoid running the full suite on every pull request.

cover

Jun 17Talk

Automating Mobile QA with Cloud Agents

Michał Pierzchała will present Automating Mobile QA with Cloud Agents at Ship London on 17 June 2026.

cover

May 13Podcast

How AI Is Changing React Native Development and Testing

Zack Ebenfeld joins React Universe On Air to talk about how AI is changing React Native workflows, testing, QA, and day-to-day engineering work.

cover

Apr 14Article

Agent React DevTools: Give AI Agents Access to React Internals

Agent React DevTools gives AI agents access to the React component tree, state, and performance data, enabling deeper debugging and more reliable QA for React and React Native apps.

cover

Mar 19Live Stream

Agent Device, AI workflows, and mobile testing in practice

A discussion on Agent Device, AI-driven workflows, and testing mobile apps using simulators and accessibility data.

cover

Aug 23Podcast

Introducing E2E Tests to React Native Core

This episode of the React Native Show dives into the recent introduction of End-to-End (E2E) testing in RNTester. Join us as we unpack the thought process, technical considerations, and practical implementation of this new approach to testing in React Native.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.