Skip to main content
rule

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.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/detox.mdc
Open on GitHub

What 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 explicit waitFor conditions
  • reset React Native runtime state cleanly before each test case
  • isolate external network requests using msw or nock

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.mdc

Frontmatter: description, globs, alwaysApply

Step 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

Similar IDE rules

All IDE rules for Mobile →

From the same set240

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor