
Security News
vlt Launches "reproduce": A New Tool Challenging the Limits of Package Provenance
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Detox is an end-to-end testing and automation framework for mobile applications. It is designed to test mobile apps on both iOS and Android platforms, ensuring that the app works as expected from the user's perspective. Detox is known for its ability to handle asynchronous operations and its integration with popular mobile development frameworks like React Native.
End-to-End Testing
This code demonstrates a basic end-to-end test using Detox. It launches the app, checks for the visibility of a welcome screen, taps a button, and verifies that a 'Hello' screen is displayed.
const { device, expect, element, by } = require('detox');
beforeAll(async () => {
await device.launchApp();
});
describe('Example', () => {
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
it('should show hello screen after tap', async () => {
await element(by.id('hello_button')).tap();
await expect(element(by.text('Hello!!!'))).toBeVisible();
});
});
Device Interaction
This code sample shows how to interact with the device using Detox. It includes actions like rotating the device to landscape mode and shaking the device.
const { device } = require('detox');
beforeAll(async () => {
await device.launchApp();
});
describe('Device Interaction', () => {
it('should rotate the device to landscape', async () => {
await device.setOrientation('landscape');
});
it('should shake the device', async () => {
await device.shake();
});
});
Synchronization
This code demonstrates Detox's synchronization capabilities. It waits for a specific element to become visible within a given timeout period.
const { device, element, by } = require('detox');
beforeAll(async () => {
await device.launchApp();
});
describe('Synchronization', () => {
it('should wait for element to be visible', async () => {
await waitFor(element(by.id('uniqueId'))).toBeVisible().withTimeout(2000);
});
});
Appium is an open-source tool for automating native, mobile web, and hybrid applications on iOS and Android platforms. Unlike Detox, which is specifically designed for React Native and mobile apps, Appium supports a broader range of applications and programming languages.
Cypress is a JavaScript end-to-end testing framework primarily designed for web applications. While it does not natively support mobile app testing like Detox, it is known for its ease of use and powerful features for web testing.
WebdriverIO is a popular testing framework that allows you to run tests based on the WebDriver protocol and Appium. It supports both web and mobile applications, making it more versatile compared to Detox, which is focused on mobile apps.
Graybox E2E Tests and Automation Library for Mobile
High velocity native mobile development requires us to adopt continuous integration workflows, which means our reliance on manual QA has to drop significantly. The most difficult part of automated testing on mobile is the tip of the testing pyramid - E2E. The core problem with E2E tests is flakiness - tests are usually not deterministic. We believe the only way to tackle flakiness head on is by moving from blackbox testing to graybox testing and that's where detox comes into play.
Please note that this library is still pre version 1.0.0 and under active development. The NPM version is higher because the name "detox" was transferred to us from a previous inactive package. Package can still break without respecting semver, though we try not to.
This is a step-by-step guide to help you add detox to your project.
You can also go through this short guide
If you used previous detox version, follow the migration guide.
Install the latest version of brew
.
If you haven't already, install Node.js
brew update && brew install node
You will also need fbsimctl
installed:
brew tap facebook/fb
export CODE_SIGNING_REQUIRED=NO && brew install fbsimctl --HEAD
Detox CLI
detox-cli
package should be installed globally, enabling usage of detox command line tools outside of your npm scripts.
npm install -g detox-cli
Install detox:
npm install detox --save-dev
Install mocha:
npm install mocha --save-dev
Add this detox property to your package.json
file:
"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app",
"build": "xcodebuild -project ios/example.xcodeproj -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 7"
}
}
}
In the detox property you just copied, switch example
with your project name.
In "binaryPath"
: example.app
should be <your_project_name>.app
.
In "build"
: example.xcodeproj
should be <your_project_name>.xcodeproj
and
-scheme example
should be
-scheme <your_project_name>
.
To test a release version, make sure to replace 'Debug' with 'Release' in the binaryPath and build properties. For full configuration options see the options section below.
Automatically:
detox init
Manually:
e2e
folder in your project root.mocha.opts
file with this content.init.js
file with this content.myFirstTest.spec.js
with content similar to this.By using the detox
command line tool, you can build and test your project easily.
Build your app:
detox build
Test your app:
detox test
That's it! Your first failing detox test! Next, we'll go over usage and how to make this test pass.
describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
it('should show hello screen after tap', async () => {
await element(by.id('hello_button')).tap();
await expect(element(by.label('Hello!!!'))).toBeVisible();
});
it('should show world screen after tap', async () => {
await element(by.id('world_button')).tap();
await expect(element(by.label('World!!!'))).toBeVisible();
});
})
Detox uses Matchers to find elements in your app, Actions to emulate user interaction with those elements and Assertions to test how your app reacts.
Matchers find elements in your app that match some property.
Whenever possible we recommend to match elements by id:
await element(by.id('random_id123'))
For other cases there is a variety of options:
// find an element by id (add a 'testID' prop to your view for this to work)
await element(by.id('tap_me'))
// find an element by text
await element(by.label('Tap Me'))
// find an element by id and by parent id
await element(by.id('Grandson883').withAncestor(by.id('Son883')))
// find an element by id and by child id
await element(by.id('Son883').withDescendant(by.id('Grandson883')))
// find an element by native view type
await element(by.type('RCTImageView'))
// multiple matchers
await element(by.id('UniqueId345').and(by.label('ID')))
// Choose from multiple elements matching the same matcher using index
await element(by.label('Product')).atIndex(2)
// find an element with an accessibility trait
await element(by.traits(['button'])
To find the view with the id Son883
<View testID='Grandfather883' style={{padding: 8, backgroundColor: 'red', marginBottom: 10}}>
<View testID='Father883' style={{padding: 8, backgroundColor: 'green'}}>
<View testID='Son883' style={{padding: 8, backgroundColor: 'blue'}}>
<View testID='Grandson883' style={{padding: 8, backgroundColor: 'purple'}} />
</View>
</View>
</View>
Use:
// any of the following will work
await element(by.id('Son883'))
await element(by.id('Son883').withAncestor(by.id('Father883')))
await element(by.id('Son883').withDescendant(by.id('Grandson883')))
Tip: To find the back button use:
await element(by.traits(['button']).and(by.label('Back')))
A more detailed explanation on matchers can be found here
Actions are functions that emulate user behavior:
await element(by.label('Tap Me')).tap();
await element(by.label('Tap Me')).longPress();
await element(by.id('UniqueId819')).multiTap(3);
await element(by.id('UniqueId937')).typeText('passcode');
await element(by.id('UniqueId937')).replaceText('passcode again');
await element(by.id('UniqueId005')).clearText();
await element(by.id('ScrollView161')).scroll(100, 'down');
await element(by.id('ScrollView161')).scroll(100, 'up');
await element(by.id('ScrollView161')).scrollTo('bottom');
await element(by.id('ScrollView161')).scrollTo('top');
// directions: 'up'/'down'/'left'/'right', speed: 'fast'/'slow'
await element(by.id('ScrollView799')).swipe('down', 'fast');
Assertions test how your app behaves:
await expect(element(by.id('UniqueId204'))).toBeVisible();
await expect(element(by.id('UniqueId205'))).toBeNotVisible();
await expect(element(by.id('UniqueId205'))).toExist();
await expect(element(by.id('RandomJunk959'))).toNotExist();
await expect(element(by.id('UniqueId204'))).toHaveText('I contain some text');
await expect(element(by.id('UniqueId204'))).toHaveLabel('I contain some text');
await expect(element(by.label('I contain some text'))).toHaveId('UniqueId204');
await expect(element(by.id('UniqueId146'))).toHaveValue('0');
Test async code with waitFor:
await waitFor(element(by.id('UniqueId336'))).toExist().withTimeout(2000);
await waitFor(element(by.label('Text5'))).toBeVisible().whileElement(by.id('ScrollView630')).scroll(50, 'down');
If this is a react native app, reload react native JS bundle
await device.reloadReactNative();
Install the app file defined in the current configuration
await device.installApp();
Uninstall the app defined in the current configuration
await device.uninstallApp();
By default, Xcode uses a randomized hidden path for outputting project build artifacts, called DerivedData. For ease of use (and better support in CI environments), it is recommended to change the project build path to a more convenient path.
File
► Project Settings...
. Click on Advanced...
, select Custom
and from the drop-down menu, select Relative to Derived Data
.DerivedData
folder next to your xcodeproj
project.configurations
holds all the device configurations, if there is only one configuration in configurations
detox build
and detox test
will default to it, to choose a specific configuration use --configuration
param
Configuration Params | Details |
---|---|
binaryPath | relative path to the ipa/app due to be tested (make sure you build the app in a project relative path) |
type | device type, currently only ios.simulator is supported |
name | device name, aligns to the device list avaliable through fbsimctl list for example, this is one line of the output of fbsimctl list : `A3C93900-6D17-4830-8FBE-E102E4BBCBB9 |
build | [optional] build command (either xcodebuild , react-native run-ios , etc...), will be later available through detox CLI tool. |
Detox can either initialize a server using a generated configuration, or can be overriden with a manual configuration:
"detox": {
...
"session": {
"server": "ws://localhost:8099",
"sessionId": "YourProjectSessionId"
}
}
Applies when using detox-cli
by running detox test
command, default is e2e
.
"detox": {
...
"specs": "path/to/tests"
}
In your detox config (in package.json) paste your build command into the configuration's build
field.
The build command will be triggered when running
You can choose to build your project in any of these ways...
If there's only one configuration, you can simply use:
detox build
To choose a specific configuration:
detox build --configuration yourConfiguration
Building with xcodebuild:
xcodebuild -project ios/YourProject.xcodeproj -scheme YourProject -sdk iphonesimulator -derivedDataPath ios/build
Building using React Native, this is the least suggested way of running your build, since it also starts a random simulator and installs the app on it.
react-native run-ios
Note: remember to update the
app
path in yourpackage.json
.
If there's only one configuration, you can simply use:
detox test
For multiple configurations, choose your configuration by passing --configuration
param:
detox test --configuration yourConfiguration
By default the app is removed, reinstalled and launched before each run.
Starting fresh is critical in CI but in dev you might be able to save time between test runs and reuse the app that was previously installed in the simulator. To do so use the reuse
flag and run your tests like this:detox test --reuse
.
This is especially useful with React Native DEV mode when making Javascript code changes that are getting picked up by the packager (and thus no reinstall is needed). This can save up to 7 seconds per run!
You should not use this option if you made native code changes or if your app relies on local ("disk") storage.
Open the React Native demo project and follow the instructions
Not using React Native? you now have a pure native demo project too
Check the API Reference or see detox's own E2E test suite to learn the test API by example.
See the Flakiness handbook
If you're interested in working on detox core and contributing to detox itself, take a look here.
demo-react-native/e2e/example.spec.js
FAQs
E2E tests and automation for mobile
The npm package detox receives a total of 282,526 weekly downloads. As such, detox popularity was classified as popular.
We found that detox demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.