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.
The Ultimate Guide to TV Development 2026 launches soon. Get it when it’s out.


Learn more about
TV
Here's everything we published recently on this topic.
We can help you move
it forward!
At Callstack, we work with companies big and small, pushing React Native everyday.
Streaming App Development
Build low-latency streaming apps that work across all leading platforms with maximum performance.
React Native Development
Hire expert React Native engineers to build, scale, or improve your app, from day one to production.
