---
title: "Playwright UI Mode: Reproduce CI Failures Locally"
description: "Use Playwright UI mode to reproduce a CI failure locally: run the CI project and setup, match the CI Docker image, and start from the CI trace."
canonical: "https://currents.dev/posts/playwright-ui-mode-reproduce-ci-failures-locally"
updated: "2026-11-04"
---

# Playwright UI Mode: Reproduce CI Failures Locally

Published 2026-11-04 by Walter Galvão.

Canonical URL: https://currents.dev/posts/playwright-ui-mode-reproduce-ci-failures-locally

Use Playwright UI mode to reproduce a CI failure locally: run the CI project and setup, match the CI Docker image, and start from the CI trace.

---


A test goes red in CI. You pull the branch, run `npx playwright test --ui` on your laptop, and the test passes. Or it fails on a different line with a different error, and you spend the afternoon fixing a failure CI never had while the CI run stays red.

UI mode is still the right place to fix it. [Playwright's docs](https://playwright.dev/docs/running-tests) recommend running tests in UI mode, where you can walk through each step and see what happened before, during and after it. The catch is that UI mode filters tests by the projects set in your `playwright.config` file, and UI mode will not take setup tests from project dependencies into consideration, so you have to run them manually first. When your local run differs from what CI ran, you get a precise view of the wrong failure.

The failure that matters is the one CI saw. Start from CI's trace and make UI mode run the project, setup and browser build CI used. Then a fix that passes locally is one CI will agree with.

## How UI mode runs tests

To open UI mode, run `npx playwright test --ui`. UI mode lives inside the Playwright test runner, and you start it with that command's `--ui` option. If a framework wrapper drives the browser for you, run the specs with `npx playwright test` before you go looking for the UI.

The flag also changes how the run behaves. By default, tests run in parallel and headless, and results appear in the terminal. `--headed` runs them with a browser window. `--ui` runs them in UI mode with a full trace of each test. You don't need a trace setting for that: to record a trace during development, you set the `--trace` flag to on, while UI mode traces each test automatically.

The sidebar lists every test file. Expand a file or a describe block, and you can run, view, watch and debug each test on its own. Running one test from the sidebar and stepping through its trace beats adding `console.log` lines and rerunning the file in the terminal.

If the sidebar stays empty, suspect a test file that throws on import before you suspect UI mode. Issue [#43536](https://github.com/microsoft/playwright/issues/43536) reports that test listing in UI mode, watch mode and VS Code hangs forever when the out-of-process loader dies while importing a test file. The reporter said a fix and tests were ready. The issue was closed as not planned.

Put the launch commands in `package.json` scripts so nobody retypes the project name:

```json
{
  "scripts": {
    "test": "playwright test",
    "test:headed": "playwright test --headed",
    "test:ui": "playwright test --ui",
    "test:ui:ci": "playwright test --ui --project=chromium"
  }
}
```

The `--project` option runs only tests from the specified list of projects, supports a '*' wildcard, and runs all projects by default. Swap in the project your pipeline uses.

## Reproducing one CI failure

Opening the whole suite to look at one red test is the slow way in. By default every project in the config runs, so a config with `chromium`, `firefox` and `webkit` projects runs each test once per project. Pass `--project` with the project that failed in CI. The option takes a list of projects and a `*` wildcard, as the [CLI reference](https://playwright.dev/docs/test-cli) shows.

The CI log gives you the rest: the failing test's file and line. Pass that location, and Playwright runs the tests at that line:

```bash
npx playwright test tests/checkout.spec.ts:42 --project=chromium --ui
```

Leave `--shard` off locally. The location already picks the one test you need.

Once UI mode is open, you can filter the sidebar's tests by text, @tag or status. You can filter by name, by the projects set in your `playwright.config` file, by @tag, or by status: passed, failed or skipped. A test gets a tag from a details object or from an @-token in its title, and tags must start with `@`:

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

test('guest can complete checkout', { tag: '@checkout' }, async ({ page }) => {
  await page.goto('/checkout'); // resolved against use.baseURL in playwright.config.ts
  await page.getByLabel('Email').fill('guest@example.com');
  await page.getByRole('button', { name: 'Place order' }).click();
  await expect(page.getByText('Order confirmed')).toBeVisible();
});
```

Type the tag into the filter, and the sidebar narrows to the tests that carry it. If your suite has no tag conventions yet, our tags guide covers [keeping a small, enforced tag set](/posts/playwright-tags-and-annotations-organize-large-ci-suites#keep-the-tag-set-small-and-enforce-it).

One test with the CI project beats the whole suite twice over. A single test opens faster, and its project matches the one CI used, which is the first thing a reproduction has to get right.

## Setup projects and dependencies

UI mode will not run the `setup` project by default to improve testing speed. On the command line, every filter (`--grep`, `--shard`, a file location, `test.only`) selects primary tests, and Playwright also runs all tests of those tests' dependency projects. A typical CI config [signs in once in a setup project](/posts/testing-authentication-with-playwright-the-complete-guide#the-storagestate-setup-project) and makes each browser project depend on it:

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

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: process.env.BASE_URL,
  },
  projects: [
    // Signs in and saves the session for the browser projects
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
      dependencies: ['setup'],
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'], storageState: 'playwright/.auth/user.json' },
      dependencies: ['setup'],
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'], storageState: 'playwright/.auth/user.json' },
      dependencies: ['setup'],
    },
  ],
});
```

On the command line, a file location filter selects the checkout test, and because that test belongs to a project with dependencies, all tests from those dependencies will also run. UI mode does not. The [UI mode docs](https://playwright.dev/docs/test-ui-mode) say it doesn't take setup tests from project dependencies into account, so you have to run them yourself first.

<AntiPattern title="Expecting UI mode to run setup" id="anti-pattern-expecting-ui-mode-to-run-setup">

You open the failing checkout test in UI mode with no valid saved session, press run, and it fails on the first page that needs a signed-in user. The CI failure was on a different step.

<AntiPattern.Fix>

Run the setup tests from the sidebar before the test that depends on them. First enable the `setup` project in the filters, then click the triangle button next to the `auth.setup.ts` file, and then disable the `setup` project in the filters again. UI mode now starts the test after the same setup CI ran.

</AntiPattern.Fix>

</AntiPattern>

## Reading a failure in UI mode

The red line only helps if it lands where CI's did. When a test fails, UI mode lists its error messages in the Errors tab, and the timeline shows a red line where the error occurred. Before you debug anything, compare that spot with the failing step in the CI run's trace, which Trace Viewer opens after the run.

If both point at the same action with the same error, you have reproduced the failure. Whatever fixes it locally fixes it in CI.

If they point at different actions, or the local run fails with a different error, stop. You are debugging a different failure. A local run that passes sends the same signal. Look upstream of the test first. UI mode does not take setup tests from project dependencies into account, so a test you run on its own starts without whatever its setup test prepares. Then check the sidebar's project filter against the project that failed in CI. Go back to the setup and project steps, and rerun until the two failures match.

Once they match, the Actions tab tells you whether to fix the locator or the page. The tab shows the locator each action used and how long the action took, and hovering an action shows the change in the DOM snapshot. A slow action on the right element points at the page. A fast action on the wrong element points at the locator.

When the locator is the suspect, find the right one on the snapshot before you edit the test. Click the [pick locator button](https://playwright.dev/docs/test-ui-mode) and hover over the DOM snapshot: UI mode highlights the locator for each element as you hover. Clicking an element adds its locator to the locator playground. Put the locator for the element the step should have hit into the test, and rerun.

Skip the tour of UI mode's other panels at this stage. The comparison with the CI trace decides the fix, and the Actions tab and the locator playground are enough to act on it.

## UI mode inside Docker

Some failures depend on the OS or the browser build, and no amount of rerunning on a laptop will reproduce those. The CI Docker image will. Run UI mode inside the image CI uses, and the OS and browser build match CI's. Our CI debugging guide covers the other [browser and config mismatches between local and CI](/posts/how-to-debug-playwright-tests-in-ci#debugging-browserconfig-mismatches-between-local-and-ci).

Two things change in a container. UI mode runs in your browser, and its endpoint must be bound to the 0.0.0.0 interface to be reachable from outside the container. `--ui-port` sets the port (0 picks any free port) and opens UI mode in a browser tab, and `--ui-host` sets the host to serve UI mode on and also opens UI mode in a browser tab.

The [official Docker image](https://playwright.dev/docs/docker) includes the browsers and their system dependencies, but not the Playwright package. Install your project's dependencies inside the container before you start anything.

<AntiPattern title="An unpinned image" id="anti-pattern-an-unpinned-image">

You pull the image by the `latest` tag. The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published:

```bash
# Anti-pattern: Playwright no longer publishes the latest tag, so it doesn't match the Playwright version in package.json
docker run -it --rm --ipc=host -p 8080:8080 -v "$(pwd)":/work -w /work mcr.microsoft.com/playwright:latest /bin/bash
```

Playwright no longer publishes the `:latest` tag, so the image's Playwright version does not follow the one in your project. Once the two don't match, Playwright can't locate the browser executables, and you end up debugging the container instead of the test.

<AntiPattern.Fix>

Pin the image to the Playwright version in your `package.json`, keep `--ipc=host`, and publish the port UI mode will use:

```bash
docker run -it --rm --ipc=host -p 8080:8080 -v "$(pwd)":/work -w /work mcr.microsoft.com/playwright:v1.61.0-noble /bin/bash
```

The docs recommend `--ipc=host` for Chromium, because without it Chromium can run out of memory and crash. A crash like that is a container problem, not the bug CI hit.

Inside the container, install the dependencies and start UI mode on the published port, bound to 0.0.0.0:

```bash
npm ci
npx playwright test tests/checkout.spec.ts:42 --project=chromium --ui --ui-host=0.0.0.0 --ui-port=8080
```

Then open `http://localhost:8080` in a browser on the host. Bump the image tag in the same commit that upgrades Playwright.

</AntiPattern.Fix>

</AntiPattern>

### Network exposure

With `--ui-host=0.0.0.0`, UI mode is reachable from other machines on your network, and so are your traces, passwords and secrets. Run UI mode that way only on a machine and a network you trust, and stop the container when you're done.

## CI traces in Trace Viewer

Reproduction is the second step, not the first. UI mode lets you explore, run, and debug tests. Trace Viewer is a GUI for exploring traces recorded after a run, and the docs recommend traces for debugging tests that fail on CI. Open the CI trace before you reproduce anything, because it shows the failure you need to match. Trace Viewer can open a remote trace directly from its URL, so you don't have to download the file from the CI run first. For reading that trace from the action log to the Network tab, see [debugging CI failures in Trace Viewer step by step](/posts/playwright-trace-viewer-debug-ci-failures-step-by-step).

Opening a trace only helps if CI kept one. The docs recommend recording traces on CI on the first retry of a failed test with `trace: 'on-first-retry'`, which produces a `trace.zip` for each retried test.

<AntiPattern title="Trace on retry, no retries" id="anti-pattern-trace-on-retry-no-retries">

You copy the recommended trace setting and never set `retries`:

```ts
// Anti-pattern: 'on-first-retry' needs a retry, and failing tests are not retried by default
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    trace: 'on-first-retry',
  },
});
```

By default failing tests are not retried, so no failed test ever reaches a first retry. The red run leaves no trace.

<AntiPattern.Fix>

Set `retries` for CI, and switch the trace mode to `'retain-on-failure'`:

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

export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  use: {
    trace: 'retain-on-failure',
  },
});
```

With `'retain-on-failure'`, Playwright records a trace for every run and keeps it only for runs that failed, even when a later retry passes. When you're hunting a flake, the failed attempt's trace is the one you need. `'on-first-retry'` records the retry instead.

</AntiPattern.Fix>

</AntiPattern>

### Getting traces out of CI

Whatever CI you run, upload the trace files as build artifacts; Trace Viewer can open a remote trace directly from its URL. [Currents Run Attachments](https://docs.currents.dev/dashboard/runs/run-attachments) lists every file of a run, including its traces, screenshots and videos, and filters the list by type. Click Open trace on a trace to open it in the trace viewer.

## When UI mode and CLI disagree

A test that fails in UI mode and passes on the CLI, or the reverse, isn't automatically flaky. The two runs are different runs. UI mode traces each test automatically. Tests run in headless mode by default. The `trace` option sets whether a trace is recorded for each test, and it defaults to `'off'`. And as the setup section showed, a location filter on the command line also runs the tests of the dependency projects, which UI mode leaves out.

Rule out setup first. If the test depends on a setup project, run that project from the sidebar and rerun the test before you read anything into the result.

Then run the same test with `--headed` or the plain CLI before you call a UI-mode-only failure a test bug. A user reported a test on Playwright 1.61.0 that consistently failed only in UI mode with 'file data stream has unexpected number of bytes' and passed in headed mode ([#41351](https://github.com/microsoft/playwright/issues/41351)). The issue was closed as completed, labelled v1.61.

Whatever you changed, confirm it with the command CI runs, scoped by project and location:

```bash
npx playwright test tests/checkout.spec.ts:42 --project=chromium
```

The checkout test's project depends on the setup project, so this command also runs the setup project's tests: a location filter on the command line selects the primary tests, and if those tests belong to a project with dependencies, all tests from those dependencies also run. So a green result here means the fix holds with CI's setup, not only with state you rebuilt by hand in the sidebar.

## Where UI mode stops helping

UI mode is for runs you start on a machine you control. For a failure that only happens on CI, the evidence is the trace CI recorded, and [Trace Viewer](https://playwright.dev/docs/trace-viewer) opens it from its URL. Serving UI mode from a headless box doesn't change that. A user asked for UI mode to run tests automatically ([#32115](https://github.com/microsoft/playwright/issues/32115)). A maintainer replied that there is no UI mode on CI and to use a reporter such as HTML instead. The request was closed as not planned.

Don't turn a UI mode container into a shared debugging service. Bound to 0.0.0.0, it lets other machines on the network reach it, along with the traces, passwords and secrets it holds.

Know when to stop rerunning. If the failure doesn't reproduce inside the pinned CI image, with the CI project selected and its setup run first, more UI mode runs won't make it appear. Go back to the CI trace and work from what it recorded.

## From red CI to fix

The loop runs in one direction, from CI's evidence back to CI's command:

1. Open the failing attempt's trace from CI and find the failing step.
2. Start UI mode on that test's file and line with the CI project, inside the pinned CI image when the OS or browser build matters.
3. Run the setup project's tests from the sidebar, then the failing test.
4. Fix the test with watch mode on, so UI mode re-runs it on every change.
5. Confirm on the command line with the same project and location filter; a location filter selects the primary tests, and if those tests belong to a project with dependencies, all tests from those dependencies will also run.

The first step doesn't have to start in a browser tab: the [Currents IDE extension](https://docs.currents.dev/ai/connect/ide-extension) shows CI failures and flaky tests in the editor, jumps to a test's file and line, and opens a failed attempt's trace in an editor tab beside the code, streamed with nothing to download.

Keep the pieces in the repo: `retries` and `trace: 'retain-on-failure'` in `playwright.config.ts`, and the UI mode scripts in `package.json`. Then every engineer reproduces a CI failure the same way, from the same trace and the same project.

## Frequently asked questions


### How do I run Playwright tests in UI mode?

Run `npx playwright test --ui`.


### Why is Playwright UI mode not showing tests?

Check for a test file that throws on import before you blame UI mode. [How UI mode runs tests](#how-ui-mode-runs-tests) covers this.


### Does Playwright UI mode run setup projects?

No. UI mode doesn't take setup tests from project dependencies into account, so you have to run them yourself first. Enable the `setup` project in the filters, click the triangle button next to the `auth.setup.ts` file, and then disable the `setup` project in the filters again, as [the setup anti-pattern box](#anti-pattern-expecting-ui-mode-to-run-setup) shows.


### Should I use UI mode or Trace Viewer for CI failures?

Start with Trace Viewer. The docs recommend traces for debugging tests that fail on CI, and Trace Viewer can open a remote trace directly from its URL. Open the CI trace before you reproduce anything, because it shows the failure you need to match, as [CI traces in Trace Viewer](#ci-traces-in-trace-viewer) explains.


### Why does a test fail in UI mode but pass on the CLI?

The two runs are different runs, so the test isn't automatically flaky.


### How do I filter tests in Playwright UI mode?

You can filter by name, by the projects set in your `playwright.config` file, by @tag, or by status: passed, failed or skipped.


### Why did my failed CI run leave no trace?

The docs recommend recording traces on CI on the first retry of a failed test with `trace: 'on-first-retry'`, but by default failing tests are not retried, so no failed test ever reaches a first retry. Set `retries` for CI, and switch the trace mode to `'retain-on-failure'`, as [the trace anti-pattern box](#anti-pattern-trace-on-retry-no-retries) shows.




---

## About the author

**Walter Galvão**, Software Engineer. Walter Galvão is a software engineer at Currents. He writes about Playwright in practice: debugging flaky tests, structuring suites for CI, and the engineering workflows that keep large test suites reliable.

LinkedIn: https://www.linkedin.com/in/waltergalvao/
