ScreenshotNeo

BlogEngineering

When and How to Retry End-to-End Tests

Use retries to manage intermittent end-to-end test failures without hiding flaky tests. Learn when to retry, how to configure CI, and how to investigate repeat failures.

By the ScreenshotNeo team4 October 20269 min read

Short answer: use a small, explicit whole-test retry allowance in CI only when intermittent failures are disrupting the workflow, and keep every recovered failure visible as a flaky test. For asynchronous page state, first use condition-based, auto-retrying assertions. A test that fails once and passes on retry is not equivalent to a test that passed on its first attempt.

In local development, zero whole-test retries often makes a failure easier to diagnose. Treat retry counts as a team policy to evaluate against your suite, not a universal number. Retries buy another attempt; they do not repair a race, shared state, a broken assertion, or an application defect.

1. Understand which kind of retry you need

“Retry” can mean two different things:

  • Retry a query or assertion: keep checking for a specific condition until it becomes true or a timeout expires. This is usually the right way to handle asynchronously rendered UI.
  • Retry the whole test: rerun the scenario after it fails. This repeats test work and may rerun setup and teardown hooks, so side effects matter.

Cypress documents that its queries and assertions can retry, while an action such as .click() executes once. Playwright provides auto-retrying assertions for asynchronous pages. Neither behavior should be confused with restarting the entire test. See the official [Cypress retry-ability guide](https://docs.cypress.io/app/core-concepts/retry-ability) and [Playwright assertions documentation](https://playwright.dev/docs/test-assertions).

2. Decide when a whole-test retry is appropriate

A whole-test retry is most useful when there is a plausible intermittent cause: a temporary service or network issue, a constrained CI worker, or timing that varies around an animation or asynchronous response. Keep the first failure and the later result in the report so the extra attempt does not erase the diagnostic signal.

Do not make repeated retries the first response to a deterministic assertion failure, stale selector, invalid test data, shared state, or reproducible product bug. Those examples point toward a test or application problem, not a need for more attempts. Isolate the test, verify its data and preconditions, and assert on the user-visible behavior that matters.

Playwright classifies a test that fails initially but passes on a retry as flaky; one that continues to fail is failed. Preserve that distinction in dashboards and release decisions. Playwright also recommends isolated tests so they can be retried independently. [Playwright retries](https://playwright.dev/docs/test-retries)

3. Configure a conservative retry policy

Start with the smallest CI retry allowance that addresses a real workflow problem. A documented Cypress example uses one retry in run mode and zero in open mode; Playwright retries are disabled by default. These are framework-specific examples, not a universal prescription. Check the documentation for the version your project uses.

Playwright: CI retries, no local retries

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  // Preserve fail-fast feedback for local development.
  retries: process.env.CI ? 1 : 0,
  reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list',
  use: {
    // Collect a trace on the first retry to help diagnose the original failure.
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});

Install and run with the project’s usual Playwright setup, for example npm install -D @playwright/test, npx playwright install, then npx playwright test. The configuration uses one retry only in CI and asks Playwright to retain useful failure evidence. Adjust the reporter and artifact options to your team’s reporting setup. See [Playwright configuration](https://playwright.dev/docs/test-configuration) and [Trace Viewer](https://playwright.dev/docs/trace-viewer).

Cypress: one run-mode retry, none in the interactive runner

// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  retries: {
    runMode: 1,
    openMode: 0,
  },
});

Run headlessly with npx cypress run; use npx cypress open for interactive local work. Cypress documents retry-specific screenshots and video behavior, so review artifact settings alongside retries. See [Cypress retry configuration](https://docs.cypress.io/app/guides/test-retries) and its [performance guidance](https://docs.cypress.io/app/guides/test-performance).

4. Prefer condition-based waiting for page readiness

If the test fails because a page has not rendered the needed state yet, wait for that state through the framework rather than sleeping for an arbitrary duration. A whole-test retry starts the scenario over; it does not make the first attempt wait more intelligently.

Playwright example

import { test, expect } from '@playwright/test';

test('shows the saved profile', async ({ page }) => {
  await page.goto('/profile');
  await page.getByRole('button', { name: 'Save profile' }).click();
  await expect(page.getByRole('status')).toHaveText('Profile saved');
});

The web-first assertion waits for the expected status text up to its configured timeout. Prefer role, label, and other user-facing locators where practical. Avoid adding fixed sleeps to compensate for a race unless a specific timed behavior is what the test is verifying.

Cypress example

cy.visit('/profile');
cy.findByRole('button', { name: 'Save profile' }).click();
cy.findByRole('status').should('have.text', 'Profile saved');

Cypress retries the query and assertion while checking the expected state. The click itself is not replayed by query retry-ability. See [Cypress retry-ability](https://docs.cypress.io/app/core-concepts/retry-ability).

5. Make whole-test retries safe

Before enabling retries, check that rerunning a test cannot create misleading state or duplicate a harmful side effect.

  1. Isolate data: give each test its own record, account, or namespace where possible. Avoid order-dependent state shared across tests.
  2. Make setup repeatable: setup should tolerate a prior partial attempt. Use stable cleanup or unique test data rather than assuming the first run never began.
  3. Consider external effects: a retry could submit an order, send a message, charge a test payment, or trigger a job again. Use a safe test environment, idempotency support where available, and cleanup that handles partial completion.
  4. Keep the failure evidence: retain the original assertion output and attach a screenshot, video, or trace when useful. Do not report only the final successful attempt.
  5. Track repeat offenders: record retry frequency by test and investigate tests that recover repeatedly.

Playwright’s guidance on [test isolation](https://playwright.dev/docs/browser-contexts) explains why isolated tests can be retried independently. Cypress identifies animations, API calls, server or database availability, dependencies, and network issues among possible causes of unreliable tests. [Cypress retry guidance](https://docs.cypress.io/app/guides/test-retries)

6. Choose the policy that fits the run

Policy Signal Workflow effect Good fit
Zero whole-test retries Every initial failure stays visible Transient faults can make the run red Local debugging, deterministic suites, or strict qualification
Small CI retry allowance Recovered cases remain marked flaky May avoid manual reruns for transient events CI where intermittent infrastructure failures disrupt work
Fail on any initial failure, even if retried successfully Strongest signal that the suite is unstable More red builds and investigation work Release qualification or explicit suite-health gates

Framework support and configuration differ. Cypress documents an experimental strategy for failing when a test passes only after retry; because it is experimental, verify its current status before depending on it. [Cypress test retry strategies](https://docs.cypress.io/app/guides/test-retries)

7. Diagnose recurring retries

When a test retries, use the first failing attempt as evidence. Check the trace, screenshot, video, assertion output, browser console, and relevant server logs around the failure. Then look for a specific cause:

  • Page state arrived late: wait for the actual response or user-visible state with a retryable assertion.
  • Animation or transition: make the test wait for the stable end state, or disable nonessential motion in the test environment when appropriate.
  • Shared or stale data: isolate test records and remove dependencies on test order.
  • External service or CI pressure: inspect service availability, request failures, worker resource limits, and network errors.
  • Repeatable product failure: treat it as a defect. A retry that eventually passes should not conceal a real intermittent application bug.

Cypress describes flaky-test management for inspecting tests with high flake rates as a hosted capability. Availability depends on the product offering. [Cypress Cloud flaky test management](https://docs.cypress.io/cloud/features/flaky-test-management)

8. Troubleshooting common retry problems

Symptom Likely cause Fix
The test passes locally but retries in CI Different timing, resource limits, browser setup, network, or service availability Collect a CI trace and logs; compare environments; wait for the needed condition rather than increasing retries immediately.
The test fails on every attempt Deterministic assertion, bad selector or data, setup failure, or product defect Reproduce the first failure, validate preconditions and selectors, then fix the failing behavior.
The retry creates duplicate records or actions Whole-test setup or side effects are not safe to repeat Use unique test data, idempotent operations where possible, and cleanup that handles partial runs.
A fixed wait still flakes The chosen delay does not represent readiness; timing varies Replace the sleep with an assertion on the exact state needed by the next action.
The CI build is green but the suite feels unreliable Recovered failures are hidden in summaries or never reviewed Keep flaky status visible, trend retries by test, and assign repeat offenders for repair.
Retry configuration appears ignored Wrong config file, wrong run mode, environment variable, or framework version behavior Confirm the command and loaded config, inspect the runner’s retry report, and check documentation for the installed version.

9. Performance, reliability, and cost

Every whole-test retry consumes additional browser and CI time, and may repeat setup, teardown, and external requests. Applying a high retry count broadly can multiply work while making the first-attempt failure less visible. Keep the allowance narrow, track actual retry rates, and compare the saved manual reruns with the extra CI execution your team observes. Vendor examples of retry-duration arithmetic are illustrations, not independent performance benchmarks.

Retries can improve workflow continuity during intermittent faults, but they do not make a test or application more reliable by themselves. Reliability improves when tests are isolated, assertions match user-visible behavior, and recurring failures are repaired. Keep failure artifacts available long enough for developers to inspect them, balanced against your CI storage and retention policies.

10. See what the browser showed on a failure

A screenshot can help establish whether a failed end-to-end step showed a loading state, an unexpected banner, or an error page. Keep it as supporting evidence alongside the test trace, logs, and assertion output; a screenshot alone rarely explains why the attempt failed.

For browser screenshots and page captures, [ScreenshotNeo](https://screenshotneo.com) provides a website screenshot API and MCP server. Its API accepts a URL and returns an image or PDF; see the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/).

Or skip the browser setup

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

With [ScreenshotNeo](https://screenshotneo.com), cookie banners are accepted and removed before the capture, along with known newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status. An MCP server lets AI agents use screenshot, page-info, and PDF capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. [Read the API docs](https://screenshotneo.com/docs/) and [sign up free](https://screenshotneo.com/account/sign-up/).

FAQ

Should a test that passes on retry count as passing?

It may let the CI job complete under your policy, but keep the test marked flaky. The initial failure is useful evidence and should remain visible.

How many retries should I allow?

There is no universal count. Start with a small CI-only allowance if intermittent failures are causing a real workflow problem, and review whether it helps without hiding recurring flakiness.

Should I retry tests on a developer’s machine?

Often zero whole-test retries gives faster, clearer feedback while editing. Keep condition-based assertion retries where the framework uses them to wait for asynchronous UI state.

Do retries fix flaky tests?

No. They provide another attempt and may reduce disruption from a transient event. A test that frequently needs another attempt still needs investigation.

Sources