Skip to main content
Version: Next

Overview

info

If you prefer to read TypeScript files instead of docs, feel free to browse through the typings file provided by Detox.

Running automated tests on your mobile application implies firstly that you have a mobile device and an application. Unlike unit tests, where passing tests don’t require extra checks, the end-to-end tests are better to be executed multiple times – on various devices and app flavors (e.g. debug and release).

This is why Detox is inclined towards static configuration files describing permutations of apps, devices and a lot more, actually. If you come to think of it, even a simple React Native application is likely to have four combinations:

  1. iOS simulator running a debug build;
  2. iOS simulator running a release build;
  3. Android emulator running a debug build;
  4. Android emulator running a release build;

Imagine you want to check your app both on phones and tablets, and now you have eight combinations already! Add multiple OS versions into the test coverage matrix equation, and the configuration grows by a factor of N.

Config structure

In view of the arguments above, our recommendation to keep Detox configs neat and tidy is to keep them inside three key-value dictionaries: devices, apps and configurations. See the schematic code below:

/* @type {Detox.DetoxConfig} */
module.exports = {
devices: {
device1: { /* ... */ },
device2: { /* ... */ },
},
apps: {
app1: { /* ... */ },
app2: { /* ... */ },
},
configurations: {
'device1+app1': {
device: 'device1',
app: 'app1',
},
/* ... */
},
};

Configuration names serve as an entry point, when you want to run Detox tests:

detox test -c 'device1+app1'

When Detox starts, it picks the specified configuration and resolves its aliases to the device and the application. However, the config file is not limited only to devices and applications – there are more sections:

/* @type {Detox.DetoxConfig} */
module.exports = {
artifacts: { /* ... */ },
behavior: { /* ... */ },
logger: { /* ... */ },
session: { /* ... */ },
testRunner: { /* ... */ },
devices: { /* ... */ },
apps: { /* ... */ },
configurations: { /* ... */ },
};

When the config gets finally resolved, it looks more like a flat structure, as shown on the diagram:

Detox config with its global dictionaries for apps, devices and configurations, and also its other config sections, when resolved, it becomes a flat object with all imaginable properties: device, apps, test runner, logger, artifacts, behavior, session, etc.

Aside from mandatory device and app properties, each configuration can have overrides to the global config sections such as testRunner, artifacts, behavior and others, e.g.:

/* @type {Detox.DetoxConfig} */
module.exports = {
logger: {
level: process.env.CI ? 'debug' : 'info',
},
/* ... */
configurations: {
'ios.sim.debug': {
device: 'iphone',
app: 'ios.debug',
testRunner: {
args: {
runInBand: true,
},
},
// ...
logger: {
level: 'trace' // override
},
},
},
};

For more clarity, this relationship might be illustrated with a diagram:

Detox configurations refer to devices and apps dictionaries, and may also contain overrides to the other global config sections: test runner, artifacts, behavior, logger and session.

It should be noted that the aliasing of devices and apps is optional in fact. Instead of using keys, you can inline both device and app configs into your configuration, e.g.:

/* @type {Detox.DetoxConfig} */
module.exports = {
configurations: {
'ios.sim.debug': {
device: {
type: 'ios.simulator',
device: {
type: 'iPhone 13 Pro',
},
},
app: {
type: 'ios.app',
binaryPath: '/path/to/your.app',
},
},
},
};

Besides, there is basic support for tests with multiple applications, if you switch to apps array (aliased or inlined) instead of app, e.g.:

/* @type {Detox.DetoxConfig} */
module.exports = {
apps: { /* ... */ },
devices: { /* ... */ },
configurations: {
'multi.ios.debug': {
device: 'iphone',
apps: ['passenger.ios.debug', 'driver.ios.debug'],
},
},
};

Path conventions

Detox supports standalone configuration files and the respective named section inside package.json. It starts scanning from the current working directory, and runs over the following options, in this order:

  1. .detoxrc.js
  2. .detoxrc.json
  3. .detoxrc (JSON)
  4. detox.config.js
  5. detox.config.json
  6. package.json

If you decide to have detox section in your package.json, it should be defined as a top-level property:

{
"name": "your-project",
"version": "X.Y.Z",
"scripts": {},
"detox": {
"devices": {},
"apps": {},
"configurations": {},
}
}

Extending

All Detox config files are extensible by definition. That helps if you ever need to share certain settings across multiple mobile projects, e.g.:

{
"extends": "@my-org/detox-preset",
"configurations": {
// …
},
}

Please note that extends has to be a valid Node module path. Relative module paths will be resolved relatively to the Detox config file which contains that specific extends property, e.g.:

~/Projects/my-project/e2e/detox.config.js
module.exports = { extends: "../base.detox.config.js" };
// the path resolves to: ~/Projects/my-project/base.detox.config.js

module.exports = { extends: "./ci.detox.config.js" };
// the path resolves to: ~/Projects/my-project/e2e/ci.detox.config.js

The extension chain can have an arbitrary length. All the configs are going to be deep-merged in the logical order: grandparent ← parent ← child.

Default configuration

As you might have noticed, you always have to pass -c <configuration name> argument when running Detox tests:

detox test -c ios.sim.debug

Technically this is not true. You can omit the configuration name if:

  • there is only one configuration in configurations dictionary;

  • you set some configuration as a default choice via selectedConfiguration property:

    /* @type {Detox.DetoxConfig} */
    module.exports = {
    selectedConfiguration: 'device1+app1',
    devices: {
    device1: { /* ... */ },
    device2: { /* ... */ },
    },
    apps: {
    app1: { /* ... */ },
    app2: { /* ... */ },
    },
    configurations: {
    'device1+app1': {
    device: 'device1',
    app: 'app1',
    },
    /* ... */
    },
    };

The next articles will be describing each configuration section in detail.