How to Attach Screenshots to Playwright Test Reports
Attach Playwright screenshots to test reports manually, on failure, or per step, then inspect and host the resulting evidence.
Direct answer: capture a screenshot as a Buffer and await testInfo.attach() with contentType: 'image/png'. For every failed test, set screenshot: 'only-on-failure' in Playwright Test configuration. To associate an image with one step, use step.attach() in Playwright v1.51 or later.
Playwright reporters decide how attachments are displayed. The HTML Reporter exposes attachments in the generated report, while other reporters may expose them differently. Playwright’s API documentation notes that “Some reporters show test attachments.” Read the TestInfo API documentation.
1. Attach a screenshot to the current test
This is the most precise method when you know exactly which state the report should show.
import { test, expect } from '@playwright/test';
test('checkout page renders', async ({ page }, testInfo) => {
await page.goto('https://example.com/checkout');
await expect(page.getByRole('heading', { name: 'Checkout' })).toBeVisible();
await testInfo.attach('checkout screenshot', {
body: await page.screenshot(),
contentType: 'image/png',
});
});
- Import
testandexpect. - Accept
testInfoas the second test argument. - Call
page.screenshot()to get a Buffer. - Await
testInfo.attach()and provide a name, body, and media type.
attach() accepts either body or path, not both. Await the call: Playwright copies an attached file to a reporter-accessible location after the promise resolves. If you use a temporary file, it can be removed after the awaited attachment completes. See the official attach options.
Attach a file path instead
import { test } from '@playwright/test';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
test('attach an existing image', async ({ page }, testInfo) => {
await page.goto('https://example.com');
const imagePath = join(testInfo.outputDir, 'homepage.png');
await page.screenshot({ path: imagePath, fullPage: true });
await testInfo.attach('homepage screenshot', {
path: imagePath,
contentType: 'image/png',
});
});
Use path when another tool already produced the file. Use body when you want to avoid managing a screenshot filename yourself.
2. Capture screenshots automatically on failure
For failure evidence across a suite, configure Playwright Test instead of repeating attachment code in every test.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
screenshot: 'only-on-failure',
},
});
The supported screenshot modes are:
| Mode | Behavior |
|---|---|
'off' |
No automatic screenshots. This is the default. |
'on' |
Capture screenshots for tests regardless of outcome. |
'only-on-failure' |
Capture screenshots when a test fails. |
Automatic recordings are written to the test output directory, typically test-results. Configure this in playwright.config.ts alongside other use options. The documented configuration is the broad suite-level route; explicit attach() calls give you control over the exact state and name.
Combine automatic and explicit screenshots
You can enable failure screenshots and still attach named checkpoints. This is useful when the automatic image shows the final failure state but a manual image records an earlier transition.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
screenshot: 'only-on-failure',
},
});
import { test } from '@playwright/test';
test('payment flow', async ({ page }, testInfo) => {
await page.goto('https://example.com/payment');
await testInfo.attach('payment form before submit', {
body: await page.screenshot(),
contentType: 'image/png',
});
// Continue the flow. A failure screenshot is also produced automatically.
});
3. Attach a screenshot to a specific test step
Use step.attach() when the report should show the image under one named step instead of at the test level. TestStepInfo.attach was added in Playwright v1.51.
import { test, expect } from '@playwright/test';
test('checkout summary is visible', async ({ page }) => {
await page.goto('https://example.com/checkout');
await test.step('verify checkout summary', async step => {
await expect(page.getByRole('heading', { name: 'Order summary' })).toBeVisible();
await step.attach('order summary', {
body: await page.screenshot({ fullPage: false }),
contentType: 'image/png',
});
});
});
Use testInfo.attach() when the screenshot describes the whole test. Use step.attach() when a report reader needs to connect the image to a particular action or assertion. On versions before 1.51, attach at the test level or upgrade Playwright.
4. Choose screenshot options that make the evidence useful
Viewport versus full page
await testInfo.attach('viewport', {
body: await page.screenshot({ fullPage: false }),
contentType: 'image/png',
});
await testInfo.attach('full page', {
body: await page.screenshot({ fullPage: true }),
contentType: 'image/png',
});
A viewport capture focuses on what the user currently sees. A full-page capture includes content below the fold and can be taller than a normal viewport.
Capture one element
const card = page.getByTestId('order-summary');
await testInfo.attach('order summary card', {
body: await card.screenshot(),
contentType: 'image/png',
});
Element screenshots reduce irrelevant page content and make a report easier to scan. Ensure the locator resolves to a visible element before capturing.
Use a deterministic state
Wait for the assertion or selector that defines the state you want to document. Avoid attaching immediately after navigation if fonts, images, or asynchronous data can still change the page.
await page.goto('https://example.com/dashboard');
await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
await testInfo.attach('dashboard ready', {
body: await page.screenshot({ animations: 'disabled' }),
contentType: 'image/png',
});
Use a stable media type
PNG is the safest default for report attachments. Match the declared type to the file: use image/png for PNG, image/jpeg for JPEG, and image/webp for WebP.
5. Open the HTML report
After the test run, start the latest HTML report with:
npx playwright show-report
The HTML Reporter lets readers inspect test results, errors, steps, and attachments. If you use a custom output folder, configure the reporter explicitly:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['html', { outputFolder: 'playwright-report' }]],
});
When attachment files are hosted separately from the report, use the reporter’s attachmentsBaseURL option so attachment links resolve from that location:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['html', {
outputFolder: 'playwright-report',
attachmentsBaseURL: 'https://reports.example.com/attachments/',
}]],
});
The exact storage provider and CI artifact workflow are deployment choices. Keep the report and its attachment paths available to the people who consume the report. Playwright UI Mode also provides an Attachments view for exploring captured artifacts; it is separate from the generated HTML report. See the reporter documentation and UI Mode documentation.
6. A complete example
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['html', { outputFolder: 'playwright-report' }]],
use: {
screenshot: 'only-on-failure',
},
});
// tests/checkout.spec.ts
import { test, expect } from '@playwright/test';
test('checkout report contains useful evidence', async ({ page }, testInfo) => {
await page.goto('https://example.com/checkout');
await test.step('checkout form loaded', async step => {
await expect(page.getByRole('heading', { name: 'Checkout' })).toBeVisible();
await step.attach('checkout form', {
body: await page.screenshot({ fullPage: false }),
contentType: 'image/png',
});
});
await expect(page.getByRole('button', { name: 'Place order' })).toBeVisible();
await testInfo.attach('full checkout page', {
body: await page.screenshot({ fullPage: true }),
contentType: 'image/png',
});
});
npx playwright test
npx playwright show-report
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No image appears in the report | The call was not awaited, the reporter does not display attachments, or the report was opened from the wrong output directory. | Await attach(), confirm the reporter, and run npx playwright show-report from the project that produced the report. |
body and path error |
Both attachment sources were supplied. | Choose exactly one. Use body for a Buffer or path for an existing file. |
| Attachment has the wrong type | The declared content type does not match the screenshot format. | Use image/png for the default PNG screenshot, or declare the actual format. |
| Screenshot is blank or incomplete | The page or target element was not ready when capture ran. | Wait for a locator, assertion, network state, or application-specific ready signal before capturing. |
step.attach is undefined |
The installed Playwright version predates v1.51. | Upgrade Playwright or attach with testInfo.attach() at test scope. |
| Hosted attachments return 404 | The report points at the wrong base URL or files were not uploaded. | Set attachmentsBaseURL to the public attachment directory and preserve the generated filenames. |
| Reports become very large | Full-page or always-on screenshots create many image files. | Use only-on-failure, attach only meaningful checkpoints, and prefer element or viewport captures where they answer the debugging question. |
8. Performance, reliability, and cost considerations
- Performance: screenshots add capture and file I/O work. Limit full-page and always-on captures when a smaller image answers the question.
- Reliability: attach only after the page state is deterministic. Await every attachment before the test exits so the reporter can copy it.
- Parallel runs: keep Playwright’s per-test output directories and avoid writing all screenshots to one shared filename.
- CI retention: store the HTML report and its attachment directory together, or configure
attachmentsBaseURLfor separately hosted files. - Cost: Playwright screenshots are local artifacts. Your cost is the storage and CI retention policy you choose; the cited Playwright documentation does not provide a storage-size benchmark.
9. Or skip the browser setup
If you need screenshots of URLs for reports, documentation, or monitoring rather than browser assertions, ScreenshotNeo returns an image or PDF from one GET request. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Responses identify the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for all options.
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}`);
Every plan includes the full feature set: full-page and element capture, dark mode, device presets, custom viewport and retina scale, PDF output, custom CSS and JavaScript, clicks, waits, blocking rules, headers, cookies, user agent, authorization, timezone, geolocation, transparency, resizing, caching, signed links, async webhooks, bulk capture, usage data, and an OpenAPI specification. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
10. FAQ
Does testInfo.attach() require a file on disk?
No. Pass the Buffer returned by page.screenshot() as body. A path is optional.
Can I attach screenshots to steps and tests in one run?
Yes. Use step.attach() for step-specific evidence and testInfo.attach() for test-level evidence.
What is the default automatic screenshot setting?
Automatic screenshots are off by default. Set screenshot: 'only-on-failure' for failure evidence.
Will every reporter display attachments?
No. Reporter behavior differs. The HTML Reporter exposes attachments; verify the reporter you use.
Which Playwright version supports step attachments?
TestStepInfo.attach is documented as available from Playwright v1.51.


