Gray-box end-to-end testing for React Native apps with Detox. Covers .detoxrc.js configuration, build and test commands, matchers, device.launchApp control, automatic synchronization, and macOS CI pipelines.
npx skills add https://github.com/PramodDutta/qaskills --skill Detox Mobile Testing
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, never by text or traversal. Text changes with copy edits and localization; view hierarchy changes with refactors. Add 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: artifacts
testID 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.Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when implementing any feature or bugfix, before writing implementation code
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Expert guidance for systematic backtesting of trading strategies. Use when developing, testing, stress-testing, or validating quantitative trading strategies. Covers "beating ideas to death" methodology, parameter robustness testing, slippage modeling, bias prevention, and interpreting backtest results. Applicable when user asks about backtesting, strategy validation, robustness testing, avoiding overfitting, or systematic trading development.
Cloud laboratory platform for automated protein testing and validation. Use when designing proteins and needing experimental validation including binding assays, expression testing, thermostability measurements, enzyme activity assays, or protein sequence optimization. Also use for submitting experiments via API, tracking experiment status, downloading results, optimizing protein sequences for better expression using computational tools (NetSolP, SoluProt, SolubleMPNN, ESM), or managing protein design workflows with wet-lab validation.
This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
Take pramoddutta/detox mobile testing from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, npx, brew.
Without those the skill loads but fails at the first command.