Resource library

QA How-To

How to Use Playwright trace on retry (2026)

Learn how Playwright trace on retry captures CI evidence, how retry indexes work, which trace mode to choose, and how to debug failures safely in 2026.

21 min read | 2,774 words

TL;DR

Configure Playwright trace on retry with `retries: process.env.CI ? 2 : 0` at the config root and `use: { trace: 'on-first-retry' }`. Playwright then records retry index 1 only, stores `trace.zip` with that test result, and exposes it through the HTML report. Use `retain-on-first-failure` if the initial failing run is the evidence you need.

Key Takeaways

  • Set retries at the top level and `trace: 'on-first-retry'` inside `use` to capture only retry index 1.
  • `on-first-retry` records the first retry, not the initial failing attempt, so choose another mode when the original run is required.
  • Use Trace Viewer to inspect actions, DOM snapshots, requests, console output, attachments, timing, and source context together.
  • Keep retries low and treat a retry pass as flaky evidence, not as proof that the test is healthy.
  • Upload the HTML report and test artifacts from CI, and apply normal retention and privacy controls.
  • Prefer Playwright Test trace configuration over manual tracing when you need assertion and test-step integration.

Playwright trace on retry gives you a time-travel record of a failed test's next attempt without paying the cost of tracing every successful run. Configure retries at the test-runner level and set trace: 'on-first-retry' under use. When a test fails initially, Playwright starts a trace for retry index 1 and attaches the resulting trace.zip to that retry's output.

The most important detail is easy to miss: on-first-retry does not record the original attempt that triggered the retry. It records the first retry itself. That tradeoff keeps routine CI lean and often captures the flaky state, but it influences which mode you should choose and how you interpret evidence. This guide explains the full workflow.

TL;DR

import { defineConfig } from '@playwright/test';

export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  reporter: [['html', { open: 'never' }]],
  use: {
    trace: 'on-first-retry',
  },
});
Outcome with two retries Trace kept by on-first-retry Meaning
Passes on initial run None No retry occurred
Fails, then passes First retry Flaky result with retry evidence
Fails three times First retry only Final failure, later retry not traced

Run npx playwright show-report to open the report, or npx playwright show-trace path/to/trace.zip for a downloaded archive. Inspect the failing or flaky attempt's action timeline, DOM snapshots, network, console, attachments, and source before rerunning or changing the test.

1. What Playwright trace on Retry Records

A Playwright trace is a structured execution artifact, not just a screen recording. Trace Viewer presents test steps and Playwright calls on a timeline. For each selected action, it can show before and after DOM snapshots, action logs, locator details, timing, network activity, console messages, errors, attachments, and source context. Screenshots drive a filmstrip, while snapshots let you inspect the captured page state.

With Playwright Test, trace recording is tied to individual test results. The configured mode decides which initial runs or retries are recorded and which completed archives are retained. The default mode is off. on-first-retry begins recording only when the runner starts retry index 1, then keeps that archive whether the retry passes or fails.

This evidence is richer than a video. A video shows what was visible, but it usually cannot tell you that a request returned 401, a locator resolved twice, or a button waited because an overlay intercepted events. A screenshot shows one frame. A trace connects UI state with the actions and technical events that produced it.

Tracing is still evidence, not root-cause analysis. DOM snapshots may not perfectly reproduce every dynamic canvas or media surface, secrets may appear in requests, and external service behavior can require server logs. Use the trace to form and test a specific explanation.

2. Configure Retries and on-first-retry Correctly

retries is a top-level test-runner option. trace belongs inside use because it configures the browser context created for each test. A robust starting configuration enables retries only in CI:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 2 : undefined,
  outputDir: 'test-results',
  reporter: [
    ['line'],
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
  ],
  use: {
    baseURL: 'http://127.0.0.1:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  ],
});

Locally, no retries means no first retry, so this mode produces no trace. That is intentional. Use UI Mode during development or temporarily pass --trace=on to a focused local command when you need a trace immediately. In CI, a failed first run is retried, and the first retry is traced.

Do not place retries inside use, and do not assume trace automatically turns retries on. They are independent policies. If retries is zero, on-first-retry has nothing to record. The Playwright test configuration guide covers the boundary between runner and context options.

3. Understand Retry Indexes and Worker Behavior

The original execution has testInfo.retry === 0. The first retry has index 1, the second retry index 2, and so on. The configured number is the maximum retry attempts after the original. Therefore, retries: 2 allows as many as three total executions for a failing test.

When a test fails, Playwright discards the worker process and starts a new worker before retrying. beforeAll hooks and worker-scoped fixtures run again in that replacement worker. This isolation reduces the chance that corrupted in-memory state leaks directly into the retry, but external state remains. A database record, user account, queue message, file, or remote session created by the first attempt can still affect the next attempt.

That detail shapes trace interpretation. If the initial attempt partially created an order and the retry fails on a duplicate, the retry trace shows the secondary failure. It may not reveal the exact action that created the first order because the initial run was not traced. Test logs, unique data identifiers, API evidence, or a different trace mode may be needed.

You can include retry metadata in a diagnostic attachment or log, but do not branch product behavior to make retries pass. Tests should be independently repeatable. Use a retry-aware name only for artifacts:

test('places an isolated order', async ({ page }, testInfo) => {
  const runId = `${testInfo.project.name}-${testInfo.workerIndex}-${testInfo.retry}`;
  await testInfo.attach('run-id', {
    body: Buffer.from(runId),
    contentType: 'text/plain',
  });
  await page.goto('/checkout');
  // The test should still create unique business data through a fixture.
});

4. Compare Current Playwright Trace Modes

The right mode depends on which attempt you need and what storage or runtime cost you accept. Current Playwright Test supports these trace modes:

Mode Records Keeps Best fit
off Nothing Nothing Fast local runs without trace evidence
on Every run Every trace Short focused diagnosis only
on-first-retry First retry Always Lean CI evidence for flaky tests
on-all-retries Every retry Every retry trace Compare retry behavior across attempts
retain-on-failure Every run Failed runs Capture the exact failed attempts
retain-on-first-failure Initial run only If initial run fails Preserve the triggering failure without tracing retries
retain-on-failure-and-retries Every run Failures and every retry Maximum retry-chain evidence with pruning

on-first-retry is a strong default for medium or large CI suites because successful initial runs incur no trace recording. Its limitation is the missing original failure. If your failures frequently mutate shared state or disappear on retry, retain-on-first-failure may be more diagnostic. It records the initial attempt and keeps it only if that attempt fails.

retain-on-failure records every attempt, then deletes a trace for any attempt that passes. If a test fails initially and passes on retry, the initial failure trace remains. retain-on-failure-and-retries additionally keeps retry attempts even when they pass, enabling direct comparison at higher cost.

Choose a policy from evidence. Do not set on across a huge suite merely because traces are useful. The recording cost, artifact volume, and sensitive data surface all increase.

5. Customize Trace Contents and Cost

The object form lets you choose a mode and turn trace features on or off:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  retries: 1,
  use: {
    trace: {
      mode: 'on-first-retry',
      screenshots: true,
      snapshots: true,
      sources: true,
      attachments: true,
    },
  },
});

Screenshots create the visual timeline preview. Snapshots capture the DOM state used for before and after inspection. Sources include relevant test source files. Attachments include items attached through testInfo.attach() and related test features. These defaults are generally useful, but source and attachment policies deserve privacy review in regulated repositories.

Turning off one feature makes the archive smaller but removes a diagnostic dimension. If you disable snapshots, you lose much of Trace Viewer's inspectable time-travel experience. If you disable screenshots, there is no useful filmstrip. If you exclude sources, engineers must match the trace to the exact checked-out revision themselves.

Start with the normal full feature set on first retry. Measure artifact volume and runtime in your own suite, because application traffic and step count matter more than generic estimates. Reduce features only for a defined constraint. It is usually better to record fewer attempts with rich evidence than every test with a stripped artifact nobody can use.

Also control test attachments. Avoid attaching huge raw datasets or secrets. Prefer a bounded request identifier, sanitized response summary, and external log reference. Trace retention should follow the same data classification policy as test reports.

6. Generate and Open a Retry Trace

The following learning test deliberately fails on the initial run and passes on retry. It demonstrates artifact behavior, but it is not a production test pattern:

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

test('trace retry demonstration', async ({ page }, testInfo) => {
  const status = testInfo.retry === 0 ? 'Processing' : 'Ready';
  await page.setContent(`
    <main>
      <h1>Import job</h1>
      <p role='status'>${status}</p>
      <button>Open results</button>
    </main>
  `);

  await expect(page.getByRole('status')).toHaveText('Ready');
});

Run it with a config containing retries: 1 and trace: 'on-first-retry':

npx playwright test tests/retry-demo.spec.ts --project=chromium
npx playwright show-report

The report classifies a test that fails and then passes as flaky. Open the test details and select the trace attachment for the retry. The initial failure has no trace under this mode. Delete the demonstration or replace the artificial branch after confirming the workflow. Never make production tests pass based on testInfo.retry.

For a downloaded trace archive, open it directly:

npx playwright show-trace test-results/path-to-result/trace.zip

You can also force local tracing without changing configuration:

npx playwright test tests/checkout.spec.ts --grep 'places order' --trace=on

Use a focused test and one worker when diagnosing. A global CLI override can create many traces if the filter is broad.

7. Read Trace Viewer Like an SDET

Begin at the failed assertion or last incomplete action, then work backward to the earliest incorrect state. Select the action and compare the before and after snapshots. Check whether the locator resolved to the intended element and read its actionability log. A click may be waiting because the target is hidden, moving, disabled, detached, or covered.

Next inspect network activity. Filter for failed or slow requests around the state transition. Confirm URL, method, status, timing, and response relationship. A missing dashboard heading may be downstream of a 401 profile request. A checkout spinner may come from a 500 order response. The visible failure is not always the root cause.

Review console errors and page errors. Framework hydration errors, uncaught exceptions, or resource failures can explain a DOM that never became ready. Use source context and test steps to locate the test line, but verify the application state rather than blaming the final assertion automatically.

A disciplined trace note has four parts: expected state, actual state, earliest divergence, and supporting evidence. For example: expected a paid receipt after the POST order response, actual state remained on checkout, earliest divergence was a 409 response for duplicate order ID, supported by the network entry and error banner snapshot. This is much stronger than stating that the test is flaky.

For locator-related failures, fixing Playwright strict mode violations provides a focused selector workflow.

8. Use Playwright trace on Retry in CI and Preserve Artifacts

CI must retain the report and result directories even when the test command exits nonzero. A minimal GitHub Actions sequence looks like this:

- name: Run Playwright tests
  run: npx playwright test

- name: Upload Playwright report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: playwright-report
    path: |
      playwright-report/
      test-results/
    if-no-files-found: ignore
    retention-days: 14

if: always() is essential because the evidence is most valuable after failure. Choose a retention period based on investigation time, storage policy, and data classification rather than copying the example blindly. Limit artifact access because traces can contain URLs, DOM text, request headers, response payloads, source files, and attachments. Use test accounts and synthetic data.

Record environment metadata beside the run: commit SHA, Playwright package version, browser project, shard, worker count, base URL, and deployment identifier. When a trace is downloaded, that context tells the investigator which application and test revision produced it.

If your CI shards the suite, give every artifact a unique name containing shard or matrix values. Otherwise uploads may collide or make the relevant trace hard to find. The Playwright sharding across machines guide covers result organization for distributed runs.

9. Manual Tracing Versus Test Runner Tracing

When you use Playwright Test, prefer the use.trace configuration. It integrates test assertions and steps with Trace Viewer, manages archive names, follows retry modes, and attaches traces to results. Manual browserContext.tracing is primarily for Playwright Library users or a special context that the runner does not own.

A library-level example is:

import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext();
await context.tracing.start({ screenshots: true, snapshots: true });
const page = await context.newPage();
await page.goto('https://example.com');
await context.tracing.stop({ path: 'trace.zip' });
await browser.close();

This script is runnable, but manual context tracing does not provide the same Playwright Test assertion integration. It also makes you responsible for reliable stop() calls, unique paths under parallel execution, and cleanup after exceptions. Use try and finally in real library scripts.

Do not start manual context tracing inside a normal Playwright Test just to reproduce the configured retry behavior. Mixing recorders can produce confusing artifacts and lifecycle conflicts. Override the test runner mode with the CLI for local diagnosis or use a project-specific test.use() policy when scope must be narrow.

10. Fix Flakiness Instead of Depending on Retries

A retry is a second observation under a fresh worker, not a repair. Playwright reports a test that fails and later passes as flaky. Teams should monitor and triage that classification instead of counting the job as uncomplicated success. A passing retry may indicate shared data, race conditions, eventual consistency, environment contention, or an incorrect readiness signal.

Use the retry trace to answer specific questions:

  1. Did the retry begin with state left by the first attempt?
  2. Was the application response different, and why?
  3. Did the locator identify a different element or wait on actionability?
  4. Did an assertion observe a real business state or a proxy such as elapsed time?
  5. Did parallel workers share an account, record, port, or rate limit?
  6. Did the deployment or dependency change during the run?

Fix unique-data creation, await observable state, remove fixed sleeps, isolate accounts, and right-size worker concurrency. Keep retries low enough that flakiness remains visible. A suite with many retries can consume more time, mutate more data, and obscure the first symptom.

The Playwright timeout diagnosis guide helps when a trace shows a locator or test deadline rather than a product response failure.

Interview Questions and Answers

Q: What does trace: 'on-first-retry' capture?

It records and keeps a trace only for retry index 1. It does not trace a test that passes on the original run, and it does not capture the initial failure that caused the retry. With multiple retries, later retry indexes are not traced under this mode.

Q: Why is on-first-retry a common CI default?

Most tests pass initially, so they avoid trace recording cost. When a test fails, the first retry provides rich diagnostic evidence and may reproduce the flaky state. The mode balances artifact volume with useful coverage, although it is not ideal when the original failing attempt is essential.

Q: Which mode captures the original failure but not retries?

retain-on-first-failure records only the initial run and retains its trace if that run fails. It is useful when the first attempt can mutate state or the retry often passes. If comparison across the failure and retry is needed, retain-on-failure-and-retries is more complete.

Q: What can Trace Viewer show that video cannot?

It connects actions and assertions with DOM snapshots, locator logs, timing, requests, console output, errors, attachments, and source. A video is useful for visible motion but lacks most of that structured technical evidence. I generally start with the trace for CI debugging.

Q: How do retries affect worker processes?

After a test failure, Playwright discards the worker and starts a new worker for the retry. Worker fixtures and beforeAll run again. External state is not automatically rolled back, so test data and remote resources must still be isolated and idempotent.

Q: Is a passed retry a passed-quality signal?

It is a flaky result, not a clean pass. It proves the outcome changed between attempts. I preserve evidence, identify the differing state, and fix or quarantine according to team policy rather than treating retries as synchronization.

Q: How would you secure trace artifacts?

I use synthetic accounts, redact or avoid secrets in application traffic and attachments, restrict artifact access, and set an appropriate retention period. I also consider source inclusion and response payloads in the data classification. A trace is handled like a potentially sensitive test report.

Common Mistakes

  • Setting trace: 'on-first-retry' while leaving retries at zero, then expecting an artifact.
  • Believing the first retry trace contains the original failed attempt.
  • Putting retries inside use or putting browser context options at the config root.
  • Raising retries to hide an intermittent failure without tracking flaky outcomes.
  • Recording every test forever with trace: 'on' and overwhelming artifact storage.
  • Uploading only the HTML report while omitting result attachments or linked trace archives.
  • Branching test behavior on testInfo.retry so the retry avoids the real scenario.
  • Reading only the final assertion instead of finding the earliest incorrect state in the timeline.
  • Ignoring external state left by the failed attempt after Playwright creates a fresh worker.
  • Sharing traces publicly even though they may contain DOM data, requests, source, and attachments.
  • Starting manual context tracing inside Playwright Test without a lifecycle reason.

Conclusion

Playwright trace on retry is a practical default when you want rich CI diagnostics without tracing every normal pass. Configure retries separately, understand that on-first-retry records retry index 1, preserve the report and attachments, and inspect the trace as a connected sequence of state transitions.

Choose another mode when the original failure or every retry matters. Most importantly, use the evidence to remove flakiness. A retry trace has delivered value only when it helps the team explain and correct why two attempts produced different outcomes.

Interview Questions and Answers

Explain Playwright trace on first retry precisely.

The original run is retry index 0. If it fails and retries are enabled, Playwright records index 1 when trace mode is `on-first-retry`. That trace is kept whether index 1 passes or fails, while later retries are not recorded by this mode.

How do you choose between on-first-retry and retain-on-first-failure?

I choose based on which attempt carries the most diagnostic value. `on-first-retry` minimizes cost for initial passes and records the retry environment. `retain-on-first-failure` captures the original failure, which is better when attempts mutate external state or retries often pass.

What is your workflow for analyzing a trace?

I start at the failed assertion, inspect its before and after snapshots and call log, then work backward to the earliest incorrect state. I correlate that point with network and console evidence. I document expected state, actual state, first divergence, and supporting trace entries.

How do you manage trace artifact cost?

I record only failure or retry attempts, keep rich trace features, upload artifacts only when useful, and apply a bounded retention policy. I monitor suite volume and focus test filters during diagnosis. Fewer complete traces are often better than many stripped traces.

Why can a retry trace show a different failure from the initial run?

The initial attempt may mutate a database, queue, account, or remote session before failing. Playwright starts a fresh worker, but it does not roll back those external effects. The retry can therefore encounter duplicate or partially completed state.

What security risks exist in trace files?

Traces can contain DOM text, URLs, request and response details, console messages, source files, and test attachments. I use synthetic data, minimize secrets, restrict artifact permissions, and configure retention according to data policy.

Can manual BrowserContext tracing replace Test runner trace configuration?

It can produce a library-level trace, but it lacks the same integrated assertion and test-step experience and requires manual lifecycle and path handling. In Playwright Test I prefer `use.trace`; I reserve context tracing for library scripts or contexts outside runner ownership.

Frequently Asked Questions

How do I enable Playwright trace on retry?

Set `retries` at the top level of `playwright.config.ts` and set `use: { trace: 'on-first-retry' }`. The trace is produced only when the first retry actually runs.

Does on-first-retry record the initial Playwright failure?

No. The initial run has retry index 0, while `on-first-retry` records index 1. Use `retain-on-first-failure` or another retention mode when you need the original attempt.

Where is the Playwright trace file stored?

It is stored with that test result under the configured output directory, commonly `test-results`, and linked from the HTML report. Exact subdirectory names are generated to keep test results separate.

How do I open a Playwright trace.zip file?

Use `npx playwright show-trace path/to/trace.zip`, or open the associated test in the HTML report and select its trace attachment. Keep the archive and report from the same run when possible.

Why is no trace created with on-first-retry?

The test may have passed on the original run, retries may be configured as zero, or the relevant artifact may not have been uploaded. Confirm the retry count, result classification, output directory, and CI upload paths.

Should Playwright trace be on for every test?

Usually no. Full tracing on every run adds runtime and artifact volume. A retry or failure retention mode provides better economics for CI, while `--trace=on` is useful for focused local diagnosis.

Does a Playwright retry use the same worker?

No. After failure, Playwright discards the worker process and starts a new one. External application and data state remain your responsibility, so retries still require isolated tests.

Related Guides