Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Set snapshotPathTemplate in playwright.config.ts to define where Playwright stores snapshots, then use expect.toHaveScreenshot.pathTemplate or expect.toMatchAriaSnapshot.pathTemplate when one assertion needs a different layout. The configuration below keeps files grouped by test and assertion, separates named projects safely, and works for screenshots, ARIA snapshots, and value snapshots.
Start with a working configuration
Playwright added snapshotPathTemplate in version 1.28. It controls snapshots produced by expect(page).toHaveScreenshot(), expect(locator).toMatchAriaSnapshot(), and expect(value).toMatchSnapshot(). Put it in the configuration file used by your test run.
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}',
},
},
});
The top-level template is the default. The nested templates override it for their respective assertion types. In this example, screenshot assertions include a project directory when one exists, while ARIA snapshots use a separate tree.
How template resolution works
A relative template is resolved from the directory containing your Playwright configuration. Forward slashes are accepted on every operating system. Playwright substitutes tokens when the snapshot path is created.
#1 Best Overall
| Token | Value | Typical use |
|---|---|---|
{arg} |
The assertion’s relative snapshot path without its extension, or an automatically generated name. | Keep each assertion’s name in the filename. |
{ext} |
The snapshot extension, including the leading dot. | End a template with {arg}{ext}. |
{platform} |
The current process.platform. |
Separate baselines created on different operating systems. |
{projectName} |
The filesystem-sanitized project name, or an empty value for an unnamed project. | Keep Chromium, Firefox, and WebKit baselines apart. |
{snapshotDir} |
The project’s snapshot directory. | Build paths relative to Playwright’s configured snapshot location. |
{testDir} |
The project’s test directory. | Keep snapshots near the test tree. |
{testFileDir} |
Directories between testDir and the test file. |
Mirror nested test folders. |
{testFileBaseName} |
The test filename without its final extension. | Use a short file-based directory or prefix. |
{testFileName} |
The test filename including its extension. | Retain the exact source filename. |
{testFilePath} |
The path from testDir to the test file. |
Group snapshots by test file while preserving folders. |
{testName} |
The filesystem-sanitized test title, including parent describe titles but not the filename. |
Use the test title as a directory or filename component. |
{arg} is extensionless. The extension comes from {ext}, so omitting {ext} can create files without the expected suffix. For predictable organization, include both tokens at the end of the template.
Choose a layout that stays maintainable
One tree for all projects
Use a project-neutral template when every project intentionally shares the same baseline files:
snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}'
This is compact, but it is appropriate only when projects are equivalent for snapshot purposes. Different browsers, viewport settings, themes, or operating systems can produce incompatible images in the same location.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallA separate directory for named projects
For a multi-project configuration, add an optional separator before {projectName}:
snapshotPathTemplate: '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}'
The slash in {/projectName} is conditional. If the project is named chromium, the path contains a chromium directory. If the project has no name, the slash and empty token are omitted, so Playwright does not create an empty directory level.
With a test at tests/ui/example.spec.ts, an unnamed project can write to tests/__screenshots__/ui/example.spec.ts/hero.png; a named Chromium project can write to tests/__screenshots__/chromium/ui/example.spec.ts/hero.png.
Rank #2
Separate assertion families
Screenshot files and ARIA snapshots answer different review questions. Giving them different roots makes code review and cleanup safer:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →expect: {
toHaveScreenshot: {
pathTemplate: '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
},
toMatchAriaSnapshot: {
pathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
},
}
Keep {testFilePath} in both templates unless you have a strong reason to flatten files. Two test files can use the same assertion name, and flattening would make collisions more likely.
Override the path for an individual assertion
Use the assertion’s path argument when one snapshot needs a custom relative name. A screenshot can receive an array of path segments:
import { test, expect } from '@playwright/test';
test('checkout summary', async ({ page }) => {
await page.goto('/checkout');
await expect(page).toHaveScreenshot(['checkout', 'summary.png']);
});
The resulting path is still constrained to that test file’s snapshots directory. If the supplied segments would escape that directory, Playwright throws instead of writing outside the allowed location. Do not use .. segments or absolute paths to bypass this containment rule.
For value snapshots, pass a name such as cart.json to toMatchSnapshot(). For ARIA snapshots, use the API’s supported snapshot name or path form and let the configured toMatchAriaSnapshot.pathTemplate determine the surrounding directories.
Recommended Free Tools
Snapshot file types and naming
Screenshot assertions use PNG by default. Supplying an explicit .webp filename selects WebP; the Playwright guide describes that output as lossless. A template should normally finish with {arg}{ext} so an assertion’s chosen extension is preserved.
Rank #3
Prefer explicit names for durable tests:
await expect(page).toHaveScreenshot('account-header.png');
await expect(page).toHaveScreenshot(['account', 'billing-panel.webp']);
Automatic names are useful while prototyping, but they are derived from test context and can change when titles or test structure change. Stable assertion names make review diffs easier to understand.
Global template versus assertion-specific template
| Need | Best setting | Reason |
|---|---|---|
| One consistent layout for all snapshot types | Top-level snapshotPathTemplate |
Every assertion inherits one rule. |
| Screenshots need browser/project folders | expect.toHaveScreenshot.pathTemplate |
Only visual files gain the extra level. |
| ARIA snapshots need a dedicated root | expect.toMatchAriaSnapshot.pathTemplate |
Accessibility baselines remain separate. |
| One test has a special filename or subfolder | Assertion-level path argument | The exception stays local instead of complicating global configuration. |
Start with the smallest global rule that answers your repository’s needs. Add project separation when projects genuinely produce different baselines, and use assertion-level names for intentional exceptions.
Migration from snapshotDir
snapshotDir remains the older base-directory setting for toMatchSnapshot. It is useful when you only need to change that directory, but snapshotPathTemplate is the more flexible option for customized layouts and for coordinating different snapshot assertion types. During migration, avoid configuring two competing directory schemes without checking where each assertion actually writes.
- Record the current paths generated by your existing tests.
- Add a template that reproduces the desired directory structure.
- Run the suite in update mode once to move or regenerate baselines deliberately.
- Review the resulting Git diff and remove obsolete snapshot directories only after all projects pass.
Common failures and fixes
Snapshots appear in an unexpected directory
Check which configuration file the command loaded and whether an assertion-specific template overrides the top-level value. Remember that relative templates resolve from the configuration directory, not necessarily the shell’s current directory.
An empty project directory appears
Use the conditional form {/projectName} rather than a literal slash followed by {projectName}. The conditional prefix is emitted only when the project name is non-empty.
Files have no extension
Add {ext} after {arg}. The argument token does not include an extension.
Two assertions overwrite one another
Include {testFilePath}, use explicit assertion names, or add {testName}. Flattened layouts are especially prone to collisions when different test files use names such as header.png.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Playwright rejects an array path
The array form must remain inside the test file’s snapshots directory. Remove parent-directory segments, absolute components, and any attempt to traverse outside the permitted root.
Baselines differ between projects
Separate named projects with {/projectName}. If operating-system differences are expected, include {platform} as well, then regenerate each project’s baselines intentionally.
The template change creates a large diff
A path-template change changes filenames, so version control reports moves as deletions and additions when similarity is low. Make the change in its own commit, run one controlled update, and inspect that image content—not only paths—matches expectations.
Performance, reliability, and repository practices
- Keep snapshot paths deterministic. Avoid tokens or generated names that change between runs unless that variability is intentional.
- Use project-aware paths when browser engines, viewport sizes, themes, or operating systems are different.
- Keep the test-file component in the path so parallel test execution cannot make unrelated assertions compete for one filename.
- Store snapshots in version control with the tests that explain them. Review image and ARIA diffs together with the code change that caused them.
- Use explicit extensions and names when a snapshot is part of a long-lived contract.
- When reorganizing tests, treat the path change as a migration: update baselines, verify all projects, and remove only unused files.
Or skip the browser setup
If your goal is a clean screenshot rather than a Playwright assertion baseline, ScreenshotNeo provides a single HTTP request. It accepts the cookie or consent banner like a visitor, removes more than 60 known consent platforms plus newsletter popups and chat widgets before capture, and bills only clean shots. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteSee the ScreenshotNeo documentation for request options and authentication. The following calls are complete starting points.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
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)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
All 63 options are available on every plan, including full-page lazy-image loading, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page controls, HTML/CSS rendering, custom JavaScript and CSS, clicks, waits, ad and tracker blocking, custom headers and cookies, user agent, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage data, and an OpenAPI specification. Parameter names used by other screenshot APIs also work.
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create a free ScreenshotNeo account to get started.
FAQ
Which Playwright versions support snapshotPathTemplate?
The option was added in Playwright 1.28. Projects on older versions should upgrade before adopting these templates.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Can one template serve screenshots and value snapshots?
Yes. The top-level setting is shared, while assertion-specific settings let you give screenshot or ARIA assertions a different layout.
Does {projectName} always create a folder?
No. It expands to an empty value for an unnamed project. Prefixing it with a conditional separator, as in {/projectName}, prevents an empty directory level.
Frequently Asked Questions
Which Playwright versions support snapshotPathTemplate?
The option was added in Playwright 1.28.
Can one template serve screenshots and value snapshots?
Yes. Use the top-level template for a shared layout and assertion-specific templates for exceptions.
Does {projectName} always create a folder?
No. It is empty for unnamed projects; use {/projectName} to make the separator conditional.
Quick Recap
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.

