Skip to main content

Retries

Mobile tests can be flaky due to timing, network, or device state. Mobilewright can automatically retry a failing test up to a configured number of times before marking it as failed.

Configuring retries​

In mobilewright.config.ts:

export default defineConfig({
retries: 2,
});

Or on the command line:

npx mobilewright test --retries=2

A test that passes on its first attempt is passed. A test that fails on the first attempt but passes on a later retry is flaky. A test that fails all attempts is failed.

What happens on retry​

When a test fails and has retries remaining, Mobilewright:

  1. Tears down the test (disconnects the device).
  2. Returns the device to the pool — it is not re-allocated. The same physical device is reused for the retry.
  3. Reconnects to the device and resets the app (terminate + relaunch the bundleId).
  4. Runs the test again.

This means retries are fast — device allocation only happens once at the start of the test run, not once per retry attempt.

Detecting retries in test code​

Use testInfo.retry to check whether the current execution is a retry. This is useful for clearing cached state before re-running:

import { test } from '@mobilewright/test';

test('checkout flow', async ({ device, screen }, testInfo) => {
if (testInfo.retry) {
// clear session data that might have been left behind
}
// ... rest of test
});

testInfo.retry is 0 on the first attempt, 1 on the first retry, and so on.

Per-test and per-group retries​

Override the global retry count for a specific test or describe block:

test('flaky upload', { retries: 3 }, async ({ screen }) => {
// ...
});

test.describe('checkout', () => {
test.describe.configure({ retries: 2 });
test('add to cart', async ({ screen }) => { /* ... */ });
test('pay', async ({ screen }) => { /* ... */ });
});

Retries with serial mode​

When a test.describe.configure({ mode: 'serial' }) block fails, the entire group retries together from the beginning. This guarantees that stateful tests (where later tests depend on the outcome of earlier ones) are always re-run as a unit.

test.describe.configure({ mode: 'serial' });

test('sign in', async ({ screen }) => { /* ... */ });
test('add to cart', async ({ screen }) => { /* ... */ });
test('complete purchase', async ({ screen }) => { /* ... */ });

If "add to cart" fails, both "sign in" and "add to cart" (and "complete purchase") retry together from the start.

Retries in CI​

A common pattern is to allow retries in CI but not locally, to keep local feedback fast:

export default defineConfig({
retries: process.env.CI ? 2 : 0,
});