What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Keep visual baselines and retry diagnostics in two different path spaces. Use expect(page).toHaveScreenshot() with testInfo.snapshotPath() for the single, stable baseline that every retry compares against. Save failure artifacts with page.screenshot({ path: testInfo.outputPath(...) }), adding testInfo.retry to a diagnostic filename or directory. Configure a deterministic snapshotPathTemplate with forward-slash separators so the same layout works on Windows and POSIX CI.

The path model that prevents retry collisions

Playwright produces two fundamentally different kinds of screenshots. Mixing them is the usual reason a retry appears in a new folder, overwrites an artifact, or fails with a path error.

Screenshot purpose API and location Retry rule
Visual-regression baseline expect(page).toHaveScreenshot(name); resolve with testInfo.snapshotPath(name, { kind: 'screenshot' }) Keep the baseline name unchanged when retries check the same expected image.
Runtime failure or debugging artifact page.screenshot({ path: testInfo.outputPath(...) }) Add testInfo.retry to a filename or subdirectory so every attempt remains observable.

snapshotPath() is constrained to the configured snapshot directory, while outputPath() returns a safe path inside the current test’s isolated output directory. That isolation matters when workers run tests in parallel: two tests can use the same diagnostic filename without writing to the same physical directory.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure one deterministic baseline layout

Set a template that derives folders from Playwright’s project and test-file identity rather than from a developer’s absolute machine path. Relative templates resolve from the configuration directory, and forward slashes are valid on every platform.

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

export default defineConfig({
  snapshotPathTemplate: '__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
  retries: process.env.CI ? 2 : 0,
  use: {
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
  },
});

What each template token contributes

  • __screenshots__ is the repository-relative root for expected images.
  • {projectName} separates browser or device projects.
  • {testFilePath} preserves the test file’s identity.
  • {arg} is the argument passed to toHaveScreenshot(), such as checkout.png.
  • {ext} preserves the image extension selected by Playwright.

Do not insert an absolute path tied to one workstation. Do not pass unsanitized user input as a path segment. Use documented tokens or a controlled sanitizer for names that originate in data.

Implement stable baselines and retry-specific diagnostics

The following test deliberately keeps the baseline stable while giving every attempt its own diagnostic file.

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

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

  // One expected image, regardless of whether this is the first run or a retry.
  await expect(page).toHaveScreenshot('checkout.png');

  const attempt = testInfo.retry; // 0 for the first run, 1 for the first retry
  await page.screenshot({
    path: testInfo.outputPath(
      'diagnostics',
      `checkout-retry-${attempt}.png`,
    ),
    fullPage: true,
  });
});

The first execution has retry value 0; subsequent retries increment it. A retry that is checking the same UI state must continue to call toHaveScreenshot('checkout.png'), not toHaveScreenshot(`checkout-${attempt}.png`). Changing the argument creates a different baseline and hides visual regressions instead of measuring them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When you need the resolved baseline path

Use snapshotPath() when a fixture, reporter, or helper needs to log or inspect the exact expected-image location. The path remains inside the snapshot directory; attempts to escape that directory are rejected.

test('logs baseline location', async ({ page }, testInfo) => {
  const baseline = testInfo.snapshotPath('checkout.png', { kind: 'screenshot' });
  console.log(`Expected image: ${baseline}`);
  await expect(page).toHaveScreenshot('checkout.png');
});

When you need an artifact only after failure

For a screenshot that is useful only when a test fails, keep Playwright’s automatic mode and add a fixture or failure hook that writes through outputPath(). The important invariant is the destination, not whether the capture occurs in the test body.

Control retries, traces, and automatic screenshots

Retries can be set globally, per project, or for a group. A project-level setting is useful when Chromium, Firefox, and WebKit need different policies; a group-level setting is useful for a known-flaky file.

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

export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
    { name: 'firefox', use: { browserName: 'firefox' } },
  ],
  use: {
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
  },
});

test.describe.configure({ retries: 1 }) can override retry behavior for a file or describe block. Automatic screenshots, traces, and videos are emitted beneath the per-test output directory (commonly test-results). They should not be treated as baselines: they describe one run and are safe to discard after diagnosis.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Windows and CI portability rules

  • Use forward slashes in snapshotPathTemplate; Playwright resolves them on Windows and POSIX systems.
  • Keep the template relative to the configuration directory so a checkout in a different workspace has the same structure.
  • Let Playwright create platform-specific separators through snapshotPath() and outputPath(); do not concatenate a Windows drive prefix or manually mix slash styles.
  • Separate projects in the path. A mobile viewport and a desktop viewport can legitimately have different pixels even when the test file is identical.
  • Do not derive a path from a title containing slashes, .., or control characters. Map external labels to a safe, known filename.

Parallel workers

Two workers may execute the same test file at different times or projects. Baselines are intentionally shared and deterministic; runtime artifacts are isolated by the test output directory. If you manually write to a repository-level directory, you lose that isolation and invite collisions.

Choosing a layout: a practical decision table

Question Recommended choice Reason
Should a retry compare with the same expected pixels? Stable toHaveScreenshot() argument Retries measure whether the same assertion becomes reliable.
Must you inspect every attempt? outputPath('diagnostics', `name-${testInfo.retry}.png`) Attempt identity is visible without changing the baseline.
Do multiple browser projects share a test file? {projectName} in the template Prevents one project’s baseline from replacing another’s.
Will CI run on another operating system? Relative template with forward slashes Avoids machine-specific roots and separator assumptions.

Troubleshooting common path failures

“Screenshot path must stay inside the snapshot directory”

Cause: a baseline name contains an absolute path, .., or another segment that escapes the snapshot root. Fix: pass a simple controlled name such as checkout.png and use snapshotPathTemplate for hierarchy. Never attempt to force baselines into test-results.

Retries overwrite one diagnostic image

Cause: every attempt uses the same output filename. Fix: include testInfo.retry in the filename or a retry subdirectory, while continuing to use outputPath().

Every retry creates a new baseline

Cause: the retry number was added to the toHaveScreenshot() argument. Fix: remove the attempt number from the baseline name. Put it only in runtime diagnostics.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Paths differ between a laptop and CI

Cause: an absolute local root, a hand-built separator, or a project name omitted from the template. Fix: use a relative snapshotPathTemplate, forward slashes, documented tokens, and separate project identity.

Two projects report confusing image mismatches

Cause: both projects resolve to the same baseline file even though their browser, viewport, or device differs. Fix: include {projectName} and regenerate the intended baseline for each project.

The artifact is missing after a crash

Cause: the process terminated before the manual capture ran, or the automatic mode was not enabled. Fix: use screenshot: 'only-on-failure' for framework-managed captures and reserve manual screenshots for points at which the page is known to exist. Pair retries with trace: 'on-first-retry' to inspect the first retry without collecting a trace for every successful run.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and storage considerations

Full-page and high-resolution screenshots cost more time and disk space than viewport captures, especially when lazy images must load. Capture the smallest diagnostic image that answers the question; keep fullPage: true for failures where scroll position or below-the-fold content matters. Retaining every retry can multiply artifacts, so configure CI retention and archive only failed attempts when storage is limited.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Retries improve signal only when the environment is otherwise repeatable. Keep browser versions, fonts, locale, timezone, and viewport consistent across attempts. A deterministic path cannot make nondeterministic pixels stable; it only ensures that the evidence is stored and compared correctly.

Or skip the browser setup

If you need a clean image of a public URL rather than a Playwright assertion, ScreenshotNeo provides a single HTTP request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

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

See the ScreenshotNeo API documentation for the complete option list. The service also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Free usage includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does testInfo.retry count the initial run?

Yes. The initial run is retry 0; the first retry is 1, and later attempts increment from there.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can one test have both a stable baseline and retry images?

Yes. Use toHaveScreenshot() for the expected image and outputPath() for diagnostic copies. They serve different purposes and should remain in different roots.

What happens if retries are disabled locally?

With retries: 0, only attempt 0 runs. The same naming scheme remains valid when CI enables retries.

Frequently Asked Questions

Does testInfo.retry count the initial run?

Yes. The initial run is retry 0; the first retry is 1, and later attempts increment from there.

Can one test have both a stable baseline and retry images?

Yes. Use toHaveScreenshot() for the expected image and outputPath() for diagnostic copies.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What happens if retries are disabled locally?

With retries set to 0, only attempt 0 runs; the naming scheme still works when CI enables retries.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.