← All skills

Detox Skill

Mobile testingJavaScriptTypeScript

Copy and Paste in your Terminal

npx skills add https://github.com/LambdaTest/agent-skills.git --skill detox-skill

Advanced patterns

Advanced topics and patterns for experienced users.

Detox (React Native) — Advanced Patterns & Playbook

Screen Object Pattern

class LoginScreen {
  get emailInput() { return element(by.id('email-input')); }
  get passwordInput() { return element(by.id('password-input')); }
  get submitBtn() { return element(by.id('login-button')); }
  get errorText() { return element(by.id('error-message')); }

  async login(email, password) {
    await this.emailInput.replaceText(email);
    await this.passwordInput.replaceText(password);
    await this.submitBtn.tap();
  }

  async expectError(message) {
    await expect(this.errorText).toBeVisible();
    await expect(this.errorText).toHaveText(message);
  }
}

class HomeScreen {
  get welcomeText() { return element(by.id('welcome-text')); }
  get productList() { return element(by.id('product-list')); }

  async scrollToProduct(name) {
    await waitFor(element(by.text(name)))
      .toBeVisible()
      .whileElement(by.id('product-list'))
      .scroll(200, 'down');
  }
}

Advanced Interactions

describe('Gestures', () => {
  it('supports swipe to delete', async () => {
    await element(by.id('item-1')).swipe('left', 'fast', 0.75);
    await element(by.id('delete-confirm')).tap();
    await expect(element(by.id('item-1'))).not.toExist();
  });

  it('supports pull to refresh', async () => {
    await element(by.id('list')).swipe('down', 'slow', 0.5, 0.5, 0.1);
    await waitFor(element(by.id('refreshed-indicator'))).toBeVisible().withTimeout(5000);
  });

  it('supports long press', async () => {
    await element(by.id('item-1')).longPress();
    await expect(element(by.id('context-menu'))).toBeVisible();
  });
});

Device Capabilities

describe('System interactions', () => {
  it('handles permissions', async () => {
    await device.launchApp({ permissions: { notifications: 'YES', camera: 'YES' } });
  });

  it('handles deep links', async () => {
    await device.openURL({ url: 'myapp://product/123' });
    await expect(element(by.id('product-detail'))).toBeVisible();
  });

  it('handles background/foreground', async () => {
    await device.sendToHome();
    await device.launchApp({ newInstance: false });
    await expect(element(by.id('home-screen'))).toBeVisible();
  });

  it('handles orientation', async () => {
    await device.setOrientation('landscape');
    await expect(element(by.id('landscape-layout'))).toBeVisible();
  });
});

Configuration

// .detoxrc.js
module.exports = {
  testRunner: { args: { $0: 'jest', config: 'e2e/jest.config.js' }, jest: { setupTimeout: 120000 } },
  apps: {
    'ios.debug': { type: 'ios.app', binaryPath: 'ios/build/Debug/MyApp.app', build: 'xcodebuild ...' },
    'android.debug': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk' }
  },
  devices: {
    simulator: { type: 'ios.simulator', device: { type: 'iPhone 15' } },
    emulator: { type: 'android.emulator', device: { avdName: 'Pixel_6_API_34' } }
  },
  configurations: {
    'ios.sim.debug': { device: 'simulator', app: 'ios.debug' },
    'android.emu.debug': { device: 'emulator', app: 'android.debug' }
  }
};

Anti-Patterns

  • await new Promise(r => setTimeout(r, 3000)) — use waitFor().toBeVisible().withTimeout()
  • ❌ Matching by text for dynamic content — use testID props and by.id()
  • ❌ Missing device.reloadReactNative() between unrelated test suites
  • ❌ Not using replaceTexttypeText appends, which causes issues with pre-filled fields