Detox Mobile Testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Detox Mobile Testing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill makes an AI agent write and run Detox gray-box E2E tests for React Native apps: configure .detoxrc.js for iOS simulators and Android emulators, build test binaries, write tests with element(by.id(...)) matchers, control the app lifecycle with device.launchApp, and lean on Detox's automatic synchronization instead of sleeps. Trigger it in React Native repositories containing an e2e/ directory, detox in package.json, or when the user asks for end-to-end tests on iOS/Android simulators.
sleep() in a Detox suite is a bug.testID="login-button" props in the app code as part of writing the test.assembleRelease / -configuration Release binaries.device.launchApp({ newInstance: true }) or device.reloadReactNative() in beforeEach; tests that depend on the previous test's screen are unmaintainable.device.launchApp({ permissions: { notifications: 'YES', location: 'inuse' } }) sets iOS permissions deterministically; tapping system dialogs is flaky and Detox cannot see them anyway.device.disableSynchronization() to the smallest possible window.npm install --save-dev detox jest @types/jest
# iOS dependency for simulator control
brew tap wix/brew
brew install applesimutils
# Scaffold e2e/ folder and config
npx detox init// .detoxrc.js
/** @type {Detox.DetoxConfig} */
module.exports = {
testRunner: {
args: {
config: 'e2e/jest.config.js',
_: ['e2e'],
},
jest: { setupTimeout: 120000 },
},
apps: {
'ios.release': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/ShopApp.app',
build:
'xcodebuild -workspace ios/ShopApp.xcworkspace -scheme ShopApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build',
},
'android.release': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
build:
'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..',
},
},
devices: {
simulator: { type: 'ios.simulator', device: { type: 'iPhone 15' } },
emulator: { type: 'android.emulator', device: { avdName: 'Pixel_7_API_34' } },
},
configurations: {
'ios.sim.release': { device: 'simulator', app: 'ios.release' },
'android.emu.release': { device: 'emulator', app: 'android.release' },
},
};npx detox build --configuration ios.sim.release
npx detox test --configuration ios.sim.release --cleanup
npx detox build --configuration android.emu.release
npx detox test --configuration android.emu.release --headless --record-logs failing// e2e/login.test.js
describe('Login', () => {
beforeAll(async () => {
await device.launchApp({
newInstance: true,
permissions: { notifications: 'YES' },
});
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('logs in with valid credentials', async () => {
await element(by.id('email-input')).typeText('[email protected]');
await element(by.id('password-input')).typeText('Str0ngPass!');
await element(by.id('login-button')).tap();
await expect(element(by.id('home-screen'))).toBeVisible();
await expect(element(by.text('Welcome back'))).toBeVisible();
});
it('shows a validation error for a bad password', async () => {
await element(by.id('email-input')).typeText('[email protected]');
await element(by.id('password-input')).typeText('nope');
await element(by.id('login-button')).tap();
await expect(element(by.id('login-error'))).toHaveText('Invalid email or password');
await expect(element(by.id('home-screen'))).not.toBeVisible();
});
});// e2e/orders.test.js
it('renders orders fetched from the API', async () => {
await element(by.id('tab-orders')).tap();
// Wait for async content beyond the automatic idle sync
await waitFor(element(by.id('orders-list')))
.toBeVisible()
.withTimeout(10000);
// Scroll inside the list until a row appears
await waitFor(element(by.text('Order #1042')))
.toBeVisible()
.whileElement(by.id('orders-list'))
.scroll(250, 'down');
await element(by.text('Order #1042')).tap();
await expect(element(by.id('order-detail-screen'))).toBeVisible();
});// e2e/deeplink.test.js
it('opens a product from a deep link', async () => {
await device.launchApp({
newInstance: true,
url: 'shopapp://products/SKU-1042',
});
await expect(element(by.id('product-screen'))).toBeVisible();
await expect(element(by.id('product-sku'))).toHaveText('SKU-1042');
});
it('survives backgrounding mid-checkout', async () => {
await element(by.id('checkout-button')).tap();
await device.sendToHome();
await device.launchApp({ newInstance: false });
await expect(element(by.id('checkout-screen'))).toBeVisible();
});# .github/workflows/detox-ios.yml
name: detox-ios
on: [pull_request]
jobs:
ios-e2e:
runs-on: macos-14
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: cd ios && pod install && cd ..
- name: Install simulator utils
run: brew tap wix/brew && brew install applesimutils
- name: Build app for Detox
run: npx detox build --configuration ios.sim.release
- name: Run Detox tests
run: npx detox test --configuration ios.sim.release --cleanup --record-videos failing --take-screenshots failing
- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: detox-artifacts
path: artifactstestID props during feature development, not retroactively; treat a missing testID as a review comment.e2e/jest.config.js separate from the unit-test Jest config (maxWorkers: 1, longer timeouts, Detox environment).--record-videos failing --take-screenshots failing in CI so every red test ships with visual evidence.detoxEnableMockServer flag) rather than tapping through logout flows in every test.--headless) and pin the AVD image version; emulator image drift is a top source of "works locally" failures.device.disableSynchronization() plus waitFor(...).withTimeout(...), then device.enableSynchronization() in a finally block.await new Promise(r => setTimeout(r, 5000)) between steps: Detox's synchronization already waits for idle; sleeps only slow the suite and mask real sync bugs.by.text() for anything that will be localized or copy-edited.--cleanup, leaving zombie simulators that exhaust CI runner disk and memory.detox in devDependencies, a .detoxrc.js, or an e2e/ folder with Detox tests.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.