
detox-mobile-test
Эксперт Detox тестирования. Используй для React Native E2E tests и mobile automation.
- Installs
- 86
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/dengineproblem/agents-monorepo --skill detox-mobile-test- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads detox-mobile-test automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Detox Mobile Testing Expert
Эксперт по E2E тестированию React Native приложений с Detox.
Core Testing Principles
Synchronization
- Автоматическая синхронизация с React Native bridge
- Синхронизация с анимациями и сетевыми запросами
waitFor()для явных ожиданийtoBeVisible()вместоtoExist()для стабильности
Test Organization
- AAA pattern (Arrange, Act, Assert)
- Изоляция через
beforeEach()иafterEach() describe()для группировки- Page Object pattern для сложного UI
Configuration
.detoxrc.json
{
"testRunner": {
"args": {
"$0": "jest",
"config": "e2e/jest.config.js"
},
"jest": {
"setupTimeout": 120000
}
},
"apps": {
"ios.debug": {
"type": "ios.app",
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/MyApp.app",
"build": "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build"
},
"ios.release": {
"type": "ios.app",
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/MyApp.app",
"build": "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build"
},
"android.debug": {
"type": "android.apk",
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug"
},
"android.release": {
"type": "android.apk",
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release"
}
},
"devices": {
"simulator": {
"type": "ios.simulator",
"device": { "type": "iPhone 14" }
},
"emulator": {
"type": "android.emulator",
"device": { "avdName": "Pixel_4_API_30" }
}
},
"configurations": {
"ios.sim.debug": {
"device": "simulator",
"app": "ios.debug"
},
"ios.sim.release": {
"device": "simulator",
"app": "ios.release"
},
"android.emu.debug": {
"device": "emulator",
"app": "android.debug"
},
"android.emu.release": {
"device": "emulator",
"app": "android.release"
}
}
}
Jest Config
// e2e/jest.config.js
module.exports = {
rootDir: '..',
testMatch: ['<rootDir>/e2e/**/*.test.js'],
testTimeout: 120000,
maxWorkers: 1,
globalSetup: 'detox/runners/jest/globalSetup',
globalTeardown: 'detox/runners/jest/globalTeardown',
reporters: ['detox/runners/jest/reporter'],
testEnvironment: 'detox/runners/jest/testEnvironment',
verbose: true
};
Basic Test Structure
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
afterAll(async () => {
await device.terminateApp();
});
it('should login with valid credentials', async () => {
// Arrange
const email = 'test@example.com';
const password = 'password123';
// Act
await element(by.id('email-input')).typeText(email);
await element(by.id('password-input')).typeText(password);
await element(by.id('login-button')).tap();
// Assert
await expect(element(by.id('home-screen'))).toBeVisible();
});
it('should show error for invalid credentials', async () => {
// Arrange
const email = 'wrong@example.com';
const password = 'wrongpassword';
// Act
await element(by.id('email-input')).typeText(email);
await element(by.id('password-input')).typeText(password);
await element(by.id('login-button')).tap();
// Assert
await expect(element(by.id('error-message'))).toBeVisible();
await expect(element(by.text('Invalid credentials'))).toBeVisible();
});
});
Element Matchers
// By testID
element(by.id('submit-button'))
// By text
element(by.text('Submit'))
// By label (accessibility)
element(by.label('Submit form'))
// By type
element(by.type('RCTTextInput'))
// By traits (iOS)
element(by.traits(['button']))
// Combining matchers
element(by.id('item').withAncestor(by.id('list')))
element(by.id('item').withDescendant(by.text('Title')))
// Index for multiple matches
element(by.id('list-item')).atIndex(0)
Actions
// Tap
await element(by.id('button')).tap();
await element(by.id('button')).multiTap(2);
await element(by.id('button')).longPress();
await element(by.id('button')).longPress(2000); // 2 seconds
// Text input
await element(by.id('input')).typeText('Hello');
await element(by.id('input')).replaceText('New text');
await element(by.id('input')).clearText();
// Scroll
await element(by.id('scrollView')).scroll(200, 'down');
await element(by.id('scrollView')).scroll(200, 'up');
await element(by.id('scrollView')).scrollTo('bottom');
await element(by.id('scrollView')).scrollTo('top');
// Scroll until visible
await waitFor(element(by.id('item')))
.toBeVisible()
.whileElement(by.id('scrollView'))
.scroll(200, 'down');
// Swipe
await element(by.id('card')).swipe('left');
await element(by.id('card')).swipe('right', 'fast', 0.9);
// Pinch
await element(by.id('map')).pinch(1.5); // zoom in
await element(by.id('map')).pinch(0.5); // zoom out
Expectations
// Visibility
await expect(element(by.id('view'))).toBeVisible();
await expect(element(by.id('view'))).not.toBeVisible();
await expect(element(by.id('view'))).toExist();
await expect(element(by.id('view'))).not.toExist();
// Focus
await expect(element(by.id('input'))).toBeFocused();
// Text
await expect(element(by.id('label'))).toHaveText('Hello');
await expect(element(by.id('input'))).toHaveValue('input value');
// Toggle state
await expect(element(by.id('switch'))).toHaveToggleValue(true);
// Slider
await expect(element(by.id('slider'))).toHaveSliderPosition(0.5);
// ID
await expect(element(by.id('view'))).toHaveId('view');
// Label
await expect(element(by.id('button'))).toHaveLabel('Submit');
waitFor API
// Wait for element to be visible
await waitFor(element(by.id('loading')))
.not.toBeVisible()
.withTimeout(10000);
// Wait for element to exist
await waitFor(element(by.id('data')))
.toExist()
.withTimeout(5000);
// Wait while scrolling
await waitFor(element(by.id('item-50')))
.toBeVisible()
.whileElement(by.id('list'))
.scroll(100, 'down');
// Custom polling
await waitFor(element(by.id('result')))
.toHaveText('Success')
.withTimeout(30000);
Page Object Pattern
// e2e/pages/LoginPage.js
class LoginPage {
get emailInput() {
return element(by.id('email-input'));
}
get passwordInput() {
return element(by.id('password-input'));
}
get loginButton() {
return element(by.id('login-button'));
}
get errorMessage() {
return element(by.id('error-message'));
}
async login(email, password) {
await this.emailInput.typeText(email);
await this.passwordInput.typeText(password);
await this.loginButton.tap();
}
async assertErrorVisible(message) {
await expect(this.errorMessage).toBeVisible();
if (message) {
await expect(element(by.text(message))).toBeVisible();
}
}
}
module.exports = new LoginPage();
// e2e/tests/login.test.js
const LoginPage = require('../pages/LoginPage');
const HomePage = require('../pages/HomePage');
describe('Login', () => {
it('should login successfully', async () => {
await LoginPage.login('user@test.com', 'password123');
await expect(HomePage.welcomeMessage).toBeVisible();
});
});
Debugging
Verbose Logging
// In test
await device.launchApp({
launchArgs: {
detoxPrintBusyIdleResources: 'YES'
}
});
Screenshots
// Take screenshot
await device.takeScreenshot('login-screen');
// On failure (in jest setup)
afterEach(async () => {
if (jasmine.currentTest.failedExpectations.length > 0) {
await device.takeScreenshot(`failed-${jasmine.currentTest.fullName}`);
}
});
Element Debugging
// Get element attributes
const attributes = await element(by.id('button')).getAttributes();
console.log(attributes);
// { text: 'Submit', visible: true, enabled: true, ... }
Handling Common Issues
Disable Synchronization
// For non-React Native screens (WebViews, etc.)
await device.disableSynchronization();
await element(by.id('webview-button')).tap();
await device.enableSynchronization();
Permission Dialogs
// iOS
await device.launchApp({
permissions: {
notifications: 'YES',
camera: 'YES',
photos: 'YES',
location: 'always'
}
});
// Android - handle at runtime
await element(by.text('Allow')).tap();
Keyboard Issues
// Dismiss keyboard
await element(by.id('input')).typeText('text\n');
// or
await device.pressBack(); // Android
// Avoid keyboard overlap
await element(by.id('input')).tap();
await element(by.id('input')).typeText('text');
await element(by.id('submit')).tap();
CI/CD Integration
GitHub Actions
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ios-e2e:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install pods
run: cd ios && pod install
- name: Build app
run: npx detox build --configuration ios.sim.release
- name: Run tests
run: npx detox test --configuration ios.sim.release --cleanup
- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: detox-artifacts
path: artifacts/
android-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '11'
- name: Install dependencies
run: npm ci
- name: Build app
run: npx detox build --configuration android.emu.release
- name: Start emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
target: google_apis
script: npx detox test --configuration android.emu.release --cleanup
Performance Tips
// Use reloadReactNative instead of launchApp
beforeEach(async () => {
await device.reloadReactNative(); // Fast
// await device.launchApp({ newInstance: true }); // Slow
});
// Record videos only on failure
// In detoxrc.json
{
"artifacts": {
"plugins": {
"video": {
"enabled": true,
"keepOnlyFailedTestsArtifacts": true
}
}
}
}
// Test sharding for parallel execution
// jest.config.js
module.exports = {
maxWorkers: process.env.CI ? 2 : 1,
// ...
};
Лучшие практики
- Stable selectors — используйте testID, не text
- Proper waits — waitFor вместо sleep
- Page Objects — переиспользуемые абстракции
- Isolated tests — каждый тест независим
- CI/CD first — тесты должны работать в CI
- Record on failure — видео/скриншоты при падении
FAQ
What does the detox-mobile-test skill do?
Эксперт Detox тестирования. Используй для React Native E2E tests и mobile automation.
How do I install detox-mobile-test?
Run npx skills add https://github.com/dengineproblem/agents-monorepo --skill detox-mobile-test in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/detox-mobile-test/ (personal) or .claude/skills/detox-mobile-test/ (project).
Which AI agents can use detox-mobile-test?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is detox-mobile-test safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains detox-mobile-test?
It is published by dengineproblem in dengineproblem/agents-monorepo. It has 86 recorded installs.
More from dengineproblem/agents-monorepo
social-media-marketing
dengineproblem/agents-monorepo
Эксперт по SMM. Используй для стратегий в Instagram, Facebook, TikTok, LinkedIn, контент-планов и community management.
frontend-design
dengineproblem/agents-monorepo
Open the skill to read its SKILL.md instructions.
video-marketing
dengineproblem/agents-monorepo
Open the skill to read its SKILL.md instructions.
k6-load-test
dengineproblem/agents-monorepo
Эксперт k6 нагрузочного тестирования. Используй для performance testing, load scenarios и stress tests.
b2b-saas-marketing
dengineproblem/agents-monorepo
Open the skill to read its SKILL.md instructions.
seo-expert
dengineproblem/agents-monorepo
Open the skill to read its SKILL.md instructions.
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work