ScreenshotNeo

BlogHow-to

How to Continue Playwright Tests After a Failure

Learn how to continue Playwright tests after assertion failures using soft assertions, runner modes, retries, and safe test isolation.

By the ScreenshotNeo team1 October 20267 min read

Use await expect.soft(...) when later checks in the same test should still run. The assertion is recorded as a failure, but Playwright continues executing the test body. If you want later test cases to run after one case fails, keep tests independent, avoid serial groups for those cases, and do not use the CLI fail-fast option -x. Use retries only when you want Playwright to attempt the failed test again.

This distinction matters because “continue” can mean three different things:

Goal Use Result
Continue statements in the same test expect.soft The test keeps running but remains failed
Run later independent tests Default or parallel mode Playwright starts the next test, restarting the worker after a failure
Run the failed test again retries The failed test is retried before the run proceeds

1. Continue inside the same test with soft assertions

A regular assertion normally stops the current test when it fails. A soft assertion records the error and allows subsequent statements to execute:

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

test('checkout summary', async ({ page }) => {
  await page.goto('https://example.com/checkout');

  await expect.soft(page.getByTestId('status')).toHaveText('Success');
  await expect.soft(page.getByTestId('eta')).toHaveText('1 day');

  // This action runs even when either soft assertion failed.
  await page.getByRole('link', { name: 'next page' }).click();
});

Soft assertions still use Playwright’s normal matcher behavior, including web-first waiting for conditions such as visibility or text. They change what happens after a matcher ultimately fails; they do not turn every operation into a retry.

Guard actions that depend on a passing check

Continuing blindly can make a failure harder to diagnose. Check the accumulated errors before taking an action whose precondition may be missing:

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

test('account flow', async ({ page }) => {
  await page.goto('https://example.com/account');

  await expect.soft(page.getByTestId('account-status')).toHaveText('Active');

  if (test.info().errors.length > 0) {
    return;
  }

  await page.getByRole('button', { name: 'Continue' }).click();
});

Playwright documents test.info().errors for this purpose. Use a normal expect when a failed precondition makes every later step unsafe.

2. Let later test cases run

Playwright normally runs tests in a file in order and runs files in parallel. When a test fails, its worker is shut down so following tests get a clean worker. The whole run can still continue unless another setting changes that behavior.

Check for serial mode

Tests in a serial group are dependent by definition. If one fails, Playwright skips the remaining tests in that group:

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

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

test('creates a record', async ({ page }) => {
  // ...
});

test('edits the record', async ({ page }) => {
  // Skipped if the previous serial test fails.
});

For independent scenarios, remove the serial configuration and give each test its own setup and data:

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

test('homepage has a heading', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading')).toBeVisible();
});

test('footer has a contact link', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.getByRole('link', { name: /contact/i })).toBeVisible();
});

Do not enable fail-fast

The -x command-line option stops the run after the first failure. Omit it when you want the remaining tests to execute:

npx playwright test

# Fail fast: stops after the first failure
npx playwright test -x

3. Use parallelism safely

Set fullyParallel: true when tests are independent and can run concurrently. The workers setting limits the number of worker processes; it does not control whether failures are ignored.

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

export default defineConfig({
  fullyParallel: true,
  workers: process.env.CI ? 2 : undefined,
  use: {
    baseURL: 'https://example.com'
  }
});

Parallel tests must not rely on another test’s in-memory variables, order, or side effects. Create required records in the test or fixture, and clean up data so another worker can run the same scenario.

4. Retry failed tests with retries

Retries rerun a failed test; they do not continue the original test body after an assertion failure. Configure them in playwright.config.ts:

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

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

Or set the maximum attempts from the command line:

npx playwright test --retries=3

A test that fails initially and passes on a retry is reported as flaky. Retries add runtime and can conceal a persistent defect, so use them to investigate intermittent failures rather than as a replacement for isolation and deterministic setup. In serial mode, retries rerun the serial group together.

5. A complete configuration example

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 2 : undefined,
  reporter: [['html'], ['line']],
  use: {
    baseURL: 'https://example.com',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  }
});

This configuration allows independent tests to continue, retries failures in CI, and captures artifacts useful for diagnosing the failed attempt. It does not make dependent tests safe to run in parallel; that requires independent data and setup.

6. Troubleshooting

Symptom Cause Fix
Later lines in the same test never run A regular assertion failed Use expect.soft for independent checks, or fix the prerequisite before continuing
Later tests are skipped Tests are in mode: 'serial' Remove serial mode or split dependent workflows from independent tests
The run stops at the first failure The command includes -x Run npx playwright test without fail-fast
A failed test runs again instead of moving on retries is enabled Set retries: 0 when diagnosing failures; keep retries when intermittent failures are expected and understood
Soft assertions produce confusing follow-up errors Later actions depended on a failed check Inspect test.info().errors and return before unsafe actions
Parallel tests interfere with each other Shared mutable state or reused accounts Use isolated fixtures, unique records, and independent cleanup
workers: 1 did not change failure behavior Workers control concurrency, not continuation semantics Check serial mode, -x, retries, and test dependencies separately

7. Performance, reliability, and cost considerations

  • Soft assertions: can save time when several independent checks share one page load, but continuing after a broken navigation may create noisy secondary failures.
  • Parallel workers: can reduce wall-clock time for isolated tests. More workers increase resource usage and can expose shared-state bugs.
  • Retries: increase execution time and browser usage in proportion to the number of additional attempts.
  • Worker restarts: provide a clean environment after a failure. Do not depend on memory or browser state surviving between tests.
  • Failure artifacts: traces, screenshots, and videos help distinguish assertion defects from navigation, timing, and environment failures.

8. Or skip the browser setup

If your goal is to capture pages for visual checks, documentation, or test artifacts, ScreenshotNeo provides a website screenshot API without maintaining a Playwright browser in your application. Cookie and consent banners are accepted before capture, and more than 60 known consent platforms, newsletter popups, and chat widgets are removed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; each response reports its result in X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for all options. A basic request:

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);

ScreenshotNeo also supports full-page captures with lazy images loaded, CSS selector element captures, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click and wait conditions, request blocking, headers and cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, caching with a chosen TTL, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

There is a free plan with 1,000 screenshots per month and no card. Paid plans start at $5 for 3,000 screenshots; yearly billing gives two months free, and every feature is available on every plan.

Create a free ScreenshotNeo account and get 1,000 screenshots each month with no card.

9. FAQ

Does a soft assertion make the test pass?

No. The test continues, but Playwright still marks it as failed when the run finishes.

Can retries continue from the failed line?

No. A retry starts the test again from its beginning in a replacement worker.

Should every assertion be soft?

No. Use soft assertions for independent observations. Use regular assertions when later actions require the condition to be true.

Why did a serial test group skip later cases?

Serial mode treats the group as one dependent workflow. A failure skips the remaining cases so they do not run on invalid state.

Is one worker the same as continue-on-failure?

No. workers: 1 limits concurrency. Continuation depends on assertions, serial mode, retries, and fail-fast settings.

How can I see why a retry passed?

Enable traces, screenshots, or video for retries and inspect the first failed attempt alongside the passing retry.