detox — Cursor rule: what it does, how to install
Enforces E2E testing standards for React Native with Detox: sets up Page Object Model patterns and testID selectors without arbitrary sleep pauses.
.cursor/rules/detox.mdcWhat detox does
This rule guides the AI when writing or refactoring mobile end-to-end tests in React Native. It targets files matching **/*.{js,jsx} and enforces the Page Object Model, separating UI interactions and selectors into dedicated classes away from test execution logic.
Element queries rely on stable by.id lookups, banning fragile selectors like by.type or by.label. Hardcoded sleep() calls are forbidden. Synchronization is handled by waitFor chained with withTimeout. App state is reset before each test block via device.reloadReactNative(), while full clean installs use device.launchApp({ delete: true }) inside beforeAll.
External backend traffic must be mocked with msw or nock to prevent network flakiness. Without explicit testID attributes defined across your components, generated tests will fall back to brittle text queries.
What it helps with
- refactor Detox tests into clean Page Object classes
- replace arbitrary
sleep()pauses with explicitwaitForconditions - reset React Native runtime state cleanly before each test case
- isolate external network requests using
mswornock
Example prompts for the agent
- Write a Page Object and an E2E login flow for Detox in React Native
- Refactor tests to eliminate sleep delays and switch to testID selectors
- Set up msw network mocks for the user profile Detox test suite
How to install detox
Manually: put the files in place
.cursor/rules/detox.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/detox.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/detox.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/detox.mdManually: put the files in place
.clinerules/detox.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about detox
How do I install detox in Cursor?
Create the rule file at .cursor/rules/detox.mdc in your workspace. Cursor automatically attaches it when you work on .js or .jsx files.
What does the detox rule do?
It enforces best practices for React Native Detox testing, focusing on Page Object architecture, by.id queries, async/await usage, and API mocking.
How does the rule eliminate flaky tests?
It bans unconditioned sleep() timers in favor of waitFor, isolates network calls via msw or nock, and resets app state with device.reloadReactNative().
Source text
detox.mdc · CC0-1.0
---
description: This guide provides definitive, opinionated best practices for writing reliable, maintainable, and performant end-to-end tests with Detox in React Native applications.
globs: **/*.{js,jsx}
---
# detox Best Practices
Detox is the definitive choice for E2E testing in React Native. These rules ensure your tests are fast, stable, and easy to maintain, leveraging modern JavaScript and Detox's gray-box capabilities.
## 1. Test Organization with Page Object Model (POM)
Always structure your tests using the Page Object Model. This abstracts UI interactions and selectors, making tests readable and resilient to UI changes.
❌ **BAD: Direct selectors and repeated logic**
```javascript
// login.e2e.js
describe('Login Flow', () => {
it('should log in successfully', async () => {
await element(by.id('emailInput')).typeText('[email protected]');
await element(by.id('passwordInput')).typeText('password123');
await element(by.id('loginButton')).tap();
await expect(element(by.id('homeScreen'))).toBeVisible();
});
});
```
✅ **GOOD: Page Object Model**
```javascript
// pages/LoginPage.js
class LoginPage {
constructor() {
this.emailInput = element(by.id('emailInput'));
this.passwordInput = element(by.id('passwordInput'));
this.loginButton = element(by.id('loginButton'));
}
async login(email, password) {
await this.emailInput.typeText(email);
await this.passwordInput.typeText(password);
await this.loginButton.tap();
}
async isVisible() {
await expect(this.emailInput).toBeVisible();
}
}
export default new LoginPage();
// tests/login.e2e.js
import LoginPage from '../pages/LoginPage';
import HomeScreen from '../pages/HomeScreen';
describe('Login Flow', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should log in successfully', async () => {
await LoginPage.login('[email protected]', 'password123');
await HomeScreen.isVisible();
});
});
```
## 2. Stable Selectors are Paramount
Prioritize `by.id` for all interactive elements. Fallback to `by.text` only when `by.id` is not feasible (e.g., dynamic content). Avoid fragile selectors like `by.type` or `by.label` if a more stable alternative exists.
❌ **BAD: Fragile selector**
```javascript
// Relies on element type, which can change
await element(by.type('RCTTextView').withAncestor(by.id('welcomeMessage'))).toBeVisible();
```
✅ **GOOD: Stable selector with `testID`**
```javascript
// In your React Native component: <Text testID="welcomeMessageText">Welcome!</Text>
await expect(element(by.id('welcomeMessageText'))).toBeVisible();
```
## 3. Explicit Waits and Assertions
Never use arbitrary `sleep()` calls. Detox's gray-box synchronization handles most async operations, but for complex UI states or specific data loads, use `waitFor` with `expect` conditions.
❌ **BAD: Arbitrary `sleep`**
```javascript
await element(by.id('submitButton')).tap();
await sleep(2000); // 🚨 Flaky!
await expect(element(by.id('successMessage'))).toBeVisible();
```
✅ **GOOD: Explicit `waitFor` and `expect`**
```javascript
await element(by.id('submitButton')).tap();
await waitFor(element(by.id('successMessage')))
.toBeVisible()
.withTimeout(5000); // Max wait time
```
## 4. Reset App State Before Each Test
Ensure test isolation by resetting the app state before every `it` block. `device.reloadReactNative()` is the standard for a quick, clean slate. For a full app re-installation, use `device.launchApp({ delete: true })` in `beforeAll`.
```javascript
describe('User Profile', () => {
beforeEach(async () => {
// Resets React Native state, including navigation and Redux store
await device.reloadReactNative();
// Perform login or other common setup here
await LoginPage.login('[email protected]', 'password123');
});
it('should display user details', async () => {
// ... test logic
});
});
```
## 5. Embrace `async/await` Everywhere
All Detox interactions and assertions are asynchronous. Use `async/await` consistently for clear, sequential test logic.
❌ **BAD: Mixing Promises and `async/await` inconsistently**
```javascript
it('should navigate', () => { // Missing async
element(by.id('navButton')).tap().then(() => {
return expect(element(by.id('nextScreen'))).toBeVisible();
});
});
```
✅ **GOOD: Consistent `async/await`**
```javascript
it('should navigate', async () => {
await element(by.id('navButton')).tap();
await expect(element(by.id('nextScreen'))).toBeVisible();
});
```
## 6. Mocking Strategies for External Dependencies
For true E2E tests, mock external APIs or services to control test data and eliminate flakiness from network instability. Use tools like `msw` (Mock Service Worker) for client-side mocking or `nock` for Node.js-based API mocks.
```javascript
// setup.js (run before tests)
import { setupServer } from 'msw/node';
import { rest } from 'msw';
const server = setupServer(
rest.post('https://api.example.com/login', (req, res, ctx) => {
return res(ctx.json({ token: 'mock-token', user: { id: '123' } }));
}),
rest.get('https://api.example.com/profile', (req, res, ctx) => {
return res(ctx.json({ name: 'Test User', email: '[email protected]' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// tests/profile.e2e.js
describe('User Profile', () => {
beforeEach(async () => {
await device.reloadReactNative();
// Login will now use the mocked API response
await LoginPage.login('[email protected]', 'password');
});
it('should display mocked user profile data', async () => {
await expect(element(by.id('userName'))).toHaveText('Test User');
await expect(element(by.id('userEmail'))).toHaveText('[email protected]');
});
});
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc