Configuration

React Native Harness can be configured through a configuration object that defines various aspects of your testing setup.

The most basic configuration would, assuming you support both iOS and Android platforms, look like this:

rn-harness.config.mjs

import {
  androidPlatform,
  androidEmulator,
} from '@react-native-harness/platform-android';
import {
  applePlatform,
  appleSimulator,
} from '@react-native-harness/platform-apple';

const config = {
  entryPoint: './index.js',
  appRegistryComponentName: 'App',

  runners: [
    androidPlatform({
      name: 'android',
      device: androidEmulator('Pixel_8_API_35'),
      bundleId: 'com.yourapp',
      appLaunchOptions: {
        extras: {
          launch_mode: 'test',
        },
      },
    }),
    applePlatform({
      name: 'ios',
      device: appleSimulator('iPhone 16 Pro', '18.0'),
      bundleId: 'com.yourapp',
      appLaunchOptions: {
        arguments: ['--mode=test'],
      },
    }),
  ],
};

export default config;

Entry Point and App Component

React Native Harness needs to know how to locate and integrate with your React Native app.

entryPoint

The path to your React Native app's entry point file.

{
  entryPoint: './src/main.tsx',
}

appRegistryComponentName

The name of the component registered with React Native's AppRegistry.

// In your app's entry point
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('MyApp', () => App);
// In your rn-harness.config.mjs
{
  appRegistryComponentName: 'MyApp',
}
Expo

For Expo projects, the entryPoint should be set to the path specified in the main property of package.json. The appRegistryComponentName is typically set to main for Expo apps.

All Configuration Options

OptionDescription
entryPointRequired. Path to your React Native app's entry point file.
appRegistryComponentNameRequired. Name of the component registered with AppRegistry.
runnersRequired. Array of test runners (at least one required).
pluginsArray of Harness plugins to run during the test lifecycle.
defaultRunnerDefault runner to use when none specified.
hostHostname or IP address to bind the Metro server to (default: Metro default).
metroPortPreferred port used by Metro and Harness bridge traffic (default: 8081). Harness falls back to the next available port when this one is already in use.
webSocketPortDeprecated. Bridge traffic now uses metroPort; this option is ignored.
platformReadyTimeoutPlatform-ready timeout in milliseconds (default: 300000).
bridgeTimeoutBridge timeout in milliseconds (default: 60000).
bundleStartTimeoutBundle start timeout in milliseconds (default: 60000).
maxAppRestartsMaximum number of automatic app relaunch attempts while Harness is waiting for startup (default: 2).
resetEnvironmentBetweenTestFilesReset environment between test files (default: true).
detectNativeCrashesDetect native app crashes during startup and test execution (default: true).
crashDetectionIntervalInterval in milliseconds to check for native crashes (default: 500).
disableViewFlatteningDisable view flattening in React Native (default: false).
coverageCoverage configuration object.
coverage.rootRoot directory for coverage instrumentation (default: process.cwd()).
forwardClientLogsForward console logs from the app to the terminal (default: false).
unstable__enableMetroCacheEnable Metro transformation cache under .harness/metro-cache and log when reusing it (default: false).

Test Runners

Metro Port Fallback

Harness treats metroPort as the preferred starting port. If that port is already in use, Harness tries the next available ports automatically and logs the port it selected for the run.

Physical iOS devices are the exception: they always use the default Metro port and do not support custom or fallback ports.

When multiple Harness runs target the same device, simulator, emulator, or browser configuration, Harness waits for the existing run to finish before starting the next one.

When a run finishes, Harness clears platform-specific dev settings it applied for the session (for example the Android debug HTTP host override and the iOS simulator JS location override) so your next normal Metro or dev-client launch is not stuck pointing at Harness's port.

A test runner defines how tests are executed on a specific platform. React Native Harness uses platform-specific packages to create runners with type-safe configurations.

Runner-specific launch options belong inside each runner config via appLaunchOptions. They are passed whenever Harness launches or relaunches the app for that runner.

For detailed installation and configuration instructions, please refer to the platform-specific guides:

Default Runner

When you have multiple runners configured, you can specify which one to use by default when no runner is explicitly specified in the CLI command.

import {
  androidPlatform,
  androidEmulator,
} from '@react-native-harness/platform-android';
import {
  applePlatform,
  appleSimulator,
} from '@react-native-harness/platform-apple';

const config = {
  runners: [
    androidPlatform({
      name: 'android',
      device: androidEmulator('Pixel_8_API_35'),
      bundleId: 'com.myapp',
    }),
    applePlatform({
      name: 'ios',
      device: appleSimulator('iPhone 16 Pro', '18.0'),
      bundleId: 'com.myapp',
    }),
  ],
  defaultRunner: 'android', // Will use Android runner by default
};

If no defaultRunner is specified, you must explicitly provide the --harnessRunner flag when running tests:

# With default runner set (uses the runner specified in defaultRunner)
react-native-harness

# Without default runner (explicit runner required)
react-native-harness --harnessRunner android
react-native-harness --harnessRunner ios

Platform Ready Timeout

The platform ready timeout controls how long React Native Harness waits for the selected device, simulator, or emulator to become usable. This includes device discovery, simulator or emulator boot, and platform runtime setup before the app is launched.

{
  platformReadyTimeout: 300000,  // 5 minutes in milliseconds
}

Default: 300000 (5 minutes) Minimum: 1000 (1 second)

Increase this value if you experience startup failures while:

  • Booting cold simulators or emulators
  • Starting devices in slower CI environments

Bridge Timeout

The bridge timeout controls how long React Native Harness waits for the app to report runtime readiness after it has been launched. It does not include simulator or emulator boot time.

{
  bridgeTimeout: 120000,  // 2 minutes in milliseconds
}

Default: 60000 (60 seconds) Minimum: 1000 (1 second)

Increase this value if you experience timeout errors, especially on:

  • Complex test suites with heavy setup
  • Slower app startup after launch

Bundle Start Timeout

The bundle start timeout controls how long React Native Harness waits for the launched app to request its Metro bundle.

{
  bundleStartTimeout: 120000,
}

Default: 60000 (60 seconds) Minimum: 1000 (1 second)

Startup Recovery

Harness can retry app startup automatically when the app fails to come up cleanly during launch.

{
  maxAppRestarts: 2,
}

Default: 2 Minimum: 0

Increase this value if your app occasionally needs another launch attempt before it reaches a ready state, especially in slower CI environments.

Set it to 0 if you want startup failures to fail immediately without any automatic retry.

Plugins

You can register Harness plugins with the plugins option.

import { definePlugin } from '@react-native-harness/plugins';

const loggingPlugin = definePlugin({
  name: 'logging-plugin',
  hooks: {
    run: {
      started: async (ctx) => {
        ctx.logger.info('Starting run', ctx.runId);
      },
    },
  },
});

export default {
  // ...
  plugins: [loggingPlugin],
};

Use plugins for project-specific logging, artifact collection, reporting, or workflow automation. See the Plugins guide for more.

Environment-Specific Configurations

You can create different configurations for different environments:

rn-harness.config.mjs

import {
  androidPlatform,
  androidEmulator,
} from '@react-native-harness/platform-android';

const isDevelopment = process.env.NODE_ENV === 'development';
const isCI = process.env.CI === 'true';

const config = {
  runners: isDevelopment
    ? [
        // Development runners with local simulators
        androidPlatform({
          name: 'android',
          device: androidEmulator('Pixel_8_API_35'),
          bundleId: 'com.myapp.debug',
        }),
      ]
    : [
        // CI runners with headless or cloud devices
        androidPlatform({
          name: 'android',
          device: androidEmulator('emulator-5554'),
          bundleId: 'com.myapp.debug',
        }),
      ],

  platformReadyTimeout: isCI ? 420000 : 300000,
  bridgeTimeout: isCI ? 180000 : 60000,
};

export default config;

Coverage Root

The coverage root option specifies the root directory for coverage instrumentation in monorepository setups. This is particularly important when your tests run from a different directory than where your source files are located.

{
  coverage: {
    root: '../',
  },
}

Default: process.cwd() (current working directory)

This option is passed to babel-plugin-istanbul's cwd option and ensures that coverage data is collected correctly in monorepo scenarios where:

  • Tests run from an example/ directory but source files are in ../src/
  • Libraries are structured with separate test and source directories
  • Projects have nested directory structures that don't align with the current working directory

Without specifying coverage.root, babel-plugin-istanbul may skip instrumenting files outside the current working directory, resulting in incomplete coverage reports.

When to use coverage.root

Set coverage.root when you notice 0% coverage in your reports or when source files are not being instrumented for coverage. This commonly occurs in create-react-native-library projects and other monorepo setups.

Need React or React Native expertise you can count on?