ScreenshotNeo

BlogHow-to

How to Use Playwright Snapshot Path Templates

Configure Playwright snapshot paths with reusable templates, project-aware folders, assertion overrides, tokens, safety rules, and troubleshooting.

By the ScreenshotNeo team1 October 20269 min read

Use snapshotPathTemplate in playwright.config.ts to control where Playwright stores snapshots. The template applies to screenshots and snapshot assertions, and you can provide separate layouts for screenshot and ARIA snapshot assertions. Include {testFilePath} and {arg}{ext} in most projects so files stay grouped by test file and assertion name.

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

export default defineConfig({
  testDir: './tests',
  snapshotPathTemplate:
    '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
  expect: {
    toHaveScreenshot: {
      pathTemplate:
        '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
    },
    toMatchAriaSnapshot: {
      pathTemplate:
        '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
    },
  },
});

snapshotPathTemplate was added in Playwright 1.28. It controls snapshots created by expect(page).toHaveScreenshot(), expect(locator).toMatchAriaSnapshot(), and expect(value).toMatchSnapshot(). See the Playwright TestConfig documentation for the API reference.

1. Create a project-aware snapshot layout

A practical default is:

{testDir}/__screenshots__/{/projectName}/{testFilePath}/{arg}{ext}

For a test at tests/account/profile.spec.ts with an assertion named profile-dark.png, this produces a path like:

tests/__screenshots__/chromium/account/profile.spec.ts/profile-dark.png

The optional slash before {projectName} matters. Playwright emits the preceding character only when the token has a value, so {/projectName} creates a project directory for named projects and no empty directory for unnamed projects.

Minimal configuration

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

export default defineConfig({
  testDir: './tests',
  snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
});

This project-neutral layout is useful when every browser project should share one expected snapshot tree. Add {/projectName} when Chromium, Firefox, and WebKit need separate baselines.

Separate image and ARIA snapshot trees

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

export default defineConfig({
  testDir: './tests',
  snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
  expect: {
    toHaveScreenshot: {
      pathTemplate:
        '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
    },
    toMatchAriaSnapshot: {
      pathTemplate:
        '{testDir}/__aria__/{testFilePath}/{arg}{ext}',
    },
  },
});

The assertion-specific settings let visual images and accessibility snapshots use different roots while the global template remains the fallback for other snapshot assertions.

2. Choose the right template tokens

Token Value Typical use
{arg} Relative snapshot path without its extension, from the assertion argument or generated name. End the filename before {ext}.
{ext} Snapshot extension, including the leading dot. Preserve PNG or an explicitly selected format.
{platform} The current process.platform. Separate baselines when operating-system rendering differs.
{projectName} Filesystem-sanitized project name, or empty for an unnamed project. Keep browser or device projects isolated.
{snapshotDir} The project’s snapshot directory. Anchor output to Playwright’s project snapshot location.
{testDir} The project’s test directory. Keep snapshots near tests.
{testFileDir} Directories between testDir and the test file. Recreate only the test’s directory structure.
{testFileBaseName} The test filename without its last extension. Build a short filename from the spec name.
{testFileName} The test filename including its extension. Retain the complete spec filename in a folder or filename.
{testFilePath} The path from testDir to the test file. Group snapshots by the full test-file path.
{testName} Filesystem-sanitized test title, including parent describe titles and excluding the file name. Use the test title when assertion names are omitted.

Normally finish with {arg}{ext}. {arg} is extensionless, while {ext} supplies the leading-dot extension. A template ending only in {arg} creates names without an extension.

Optional separators for empty tokens

One character immediately before a token is conditional. For example, {/projectName} emits a slash only when {projectName} is non-empty. This avoids an empty directory for unnamed projects:

{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}

Relative templates resolve from the configuration directory. Forward slashes work on every platform, including Windows.

3. Configure named and unnamed projects

Project names are part of the path only when you include {projectName}. Consider these two projects:

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

export default defineConfig({
  testDir: './tests',
  snapshotPathTemplate:
    '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      use: { ...devices['Desktop Firefox'] },
    },
  ],
});

The named Chromium project writes beneath a chromium directory. The unnamed Firefox project omits that directory because its project name is empty. This is useful when one project should act as the default while named projects receive explicit isolation.

Use project-aware paths when rendering engines, device profiles, or color schemes intentionally produce different baselines. Use a project-neutral path when all projects are expected to compare against the same files and overwriting would be undesirable.

4. Override layouts for specific assertion types

Set expect.toHaveScreenshot.pathTemplate for visual screenshots and expect.toMatchAriaSnapshot.pathTemplate for ARIA snapshots. These are configuration-level assertion overrides; they are not path strings passed as a second argument to the assertion.

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

export default defineConfig({
  testDir: './tests',
  snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
  expect: {
    toHaveScreenshot: {
      pathTemplate:
        '{testDir}/visual/{testFilePath}/{arg}{ext}',
    },
    toMatchAriaSnapshot: {
      pathTemplate:
        '{testDir}/accessibility/{testFilePath}/{arg}{ext}',
    },
  },
});

If no assertion-specific template is configured, Playwright falls back to the global snapshotPathTemplate. The older snapshotDir option remains the base-directory setting for toMatchSnapshot, but the template option is the flexible choice for customized layouts.

5. Name snapshots explicitly when paths matter

Explicit assertion names make paths stable and readable:

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

test('profile page', async ({ page }) => {
  await page.goto('/profile');
  await expect(page).toHaveScreenshot('profile.png');
});

test('settings form', async ({ page }) => {
  await page.goto('/settings');
  await expect(page.locator('form')).toHaveScreenshot('settings-form.png');
});

test('profile accessibility tree', async ({ page }) => {
  await page.goto('/profile');
  await expect(page.locator('main')).toMatchAriaSnapshot('profile.yml');
});

When you omit a name, Playwright generates one from the assertion context. Generated names are convenient during exploration, but explicit names make renames and code review easier.

6. Supply nested path segments safely

toHaveScreenshot() accepts an array of path segments. This lets one test keep several related images in a subdirectory:

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

test('checkout states', async ({ page }) => {
  await page.goto('/checkout');
  await expect(page).toHaveScreenshot([
    'checkout',
    'empty-cart.png',
  ]);
});

The resulting path must stay inside that test file’s snapshots directory. If the path escapes that directory, Playwright throws instead of writing outside the expected snapshot location. Do not use .. segments or absolute paths. Treat user-provided path fragments as untrusted and map them to known names before passing them to the assertion.

An explicit .webp filename selects WebP. PNG is the default, and the Playwright guide describes the selected WebP snapshot as lossless.

7. Pick layouts for common repository shapes

Keep snapshots beside each test file’s directory

{testDir}/{testFileDir}/__screenshots__/{testFileBaseName}/{arg}{ext}

This keeps each spec’s images close to its source while avoiding a repeated full filename in every image path.

Use one central tree with project isolation

{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}

This is a good default for multi-browser CI because the complete test path is preserved and named projects cannot overwrite one another.

Separate operating-system baselines

{testDir}/__screenshots__/{platform}{/projectName}/{testFilePath}/{arg}{ext}

Use this only when your repository intentionally maintains platform-specific snapshots. Adding {platform} can multiply the number of baselines you must review.

8. Generate and review snapshots

  1. Put the template in playwright.config.ts.
  2. Give important screenshot assertions explicit names.
  3. Run the test suite in update mode to create or update expected files.
  4. Inspect the generated directory tree and confirm that each project writes where intended.
  5. Run the suite without update mode so unexpected visual changes fail against the committed files.
npx playwright test --update-snapshots
npx playwright test

Commit the expected snapshots using the same project and platform conventions used by CI. A path template changes file locations; it does not remove the need to review visual diffs.

9. Troubleshooting

Symptom Likely cause Fix
Snapshots appear in an unexpected directory. The template is relative to the configuration directory, or another config file is being loaded. Confirm the active config and resolve the relative path from that file’s directory.
An empty project directory appears. The template uses /{projectName} and the project has no name. Use {/projectName} so the slash is emitted only for named projects.
Every file has no extension. The template ends with {arg} but omits {ext}. End the template with {arg}{ext}.
Chromium overwrites Firefox snapshots. The layout does not include a project discriminator. Add {/projectName}, or use separate output roots for the projects.
The assertion throws when using an array path. The resolved path leaves the test file’s snapshots directory. Remove absolute or parent-directory segments and keep every segment beneath the test’s snapshot root.
The ARIA snapshot ignores the expected folder. Only the global template was changed, while an assertion-specific override is active. Update expect.toMatchAriaSnapshot.pathTemplate or remove the override.
snapshotDir changes one assertion but not another. snapshotDir is the older base-directory setting for toMatchSnapshot. Use snapshotPathTemplate when you need tokenized paths across assertion types.
Names differ after a test title change. {testName} or an auto-generated {arg} depends on the test title. Use explicit assertion names when file stability matters.
Windows and Linux produce different paths. Platform-specific rendering or a platform token is involved. Use forward slashes in templates and decide deliberately whether {platform} belongs in the layout.

10. Performance, reliability, and repository cost

  • Template evaluation is not the expensive part. The browser render, screenshot capture, image comparison, and CI artifact handling dominate runtime.
  • Keep names deterministic. Stable explicit names reduce unnecessary file churn and make parallel workers easier to reconcile.
  • Separate incompatible baselines. If projects use different browsers, devices, or platforms, include a project or platform token instead of allowing files to collide.
  • Limit path depth. A full {testFilePath} is easy to navigate, but very deep repositories can create long paths. Use {testFileBaseName} when your repository structure is already encoded elsewhere.
  • Review generated files in CI. A successful test run can still write snapshots to an unintended location if the configuration changed.
  • Cache dependencies, not expected images blindly. Snapshot files are test inputs and should change through code review, not opaque cache restoration.

Or skip the browser setup

If you need a rendered image from a URL rather than a Playwright test baseline, ScreenshotNeo provides a single screenshot API request. It accepts the page URL and returns PNG, JPEG, WebP, or PDF. The API can load lazy images, capture full pages or one CSS-selected element, apply a device or viewport, set dark mode and retina scale, wait for a selector, delay, or network idle, and run custom CSS or JavaScript. See the ScreenshotNeo API documentation for the complete option list.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the result in X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and start with the 1,000 monthly screenshots.

FAQ

Which template should a new project start with?

Use {testDir}/__screenshots__/{/projectName}/{testFilePath}/{arg}{ext}. It groups files by test, isolates named projects, and preserves the correct extension.

Does the template rename existing snapshots?

No. It changes where newly resolved snapshot paths point. Move or regenerate existing files when adopting a new layout.

Can I use a template for only visual screenshots?

Yes. Put the layout under expect.toHaveScreenshot.pathTemplate. Use the corresponding toMatchAriaSnapshot setting for ARIA snapshots.

Why is {/projectName} preferable to /{projectName}?

The conditional form suppresses the slash when the project is unnamed, preventing an empty directory level.

Can an assertion write outside its snapshot directory?

No. Array path segments passed to toHaveScreenshot() must resolve inside that test file’s snapshots directory.