How to Take Playwright Screenshots in GitHub Actions
Capture page, full-page, and element screenshots with Playwright in GitHub Actions, upload them as artifacts, and diagnose visual failures with traces.
Use Playwright’s screenshot APIs inside your test, then upload the directory containing those files with actions/upload-artifact. Install the project dependencies and matching Playwright browsers in the GitHub Actions job. For visual regression, use Playwright Test’s toHaveScreenshot(); for investigating failed tests, retain a trace as well as the image.
1. Capture screenshots in a Playwright test
Install Playwright Test in your project if it is not already installed, then create a test such as tests/screenshots.spec.ts. The test below creates its output directory, visits a page, and saves page, full-page, and element screenshots.
import { test, expect } from '@playwright/test';
import { mkdir } from 'node:fs/promises';
test('captures page and element screenshots', async ({ page }) => {
await mkdir('artifacts', { recursive: true });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'artifacts/page.png' });
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true });
await page.locator('h1').screenshot({ path: 'artifacts/heading.png' });
});
Replace https://example.com and the selector with the page and element relevant to your application. Prefer waiting for a meaningful locator or application-ready signal when the site has long-lived network activity; networkidle can be unsuitable for pages with polling or analytics requests.
Choose the capture scope
| Need | API | Notes |
|---|---|---|
| Current viewport | page.screenshot({ path }) |
Captures the visible page state. |
| Entire scrollable page | page.screenshot({ path, fullPage: true }) |
Produces a tall image. Lazy-loaded content may need scrolling or explicit loading first. |
| One component or region | page.locator(selector).screenshot({ path }) |
Useful for focused review and smaller visual diffs. |
| Image bytes in memory | const buffer = await page.screenshot() |
Omit path to receive a buffer for processing or comparison tools. |
The screenshot API supports image format and capture behavior options. Use the official Playwright screenshots guide and Page API reference for the option set supported by the Playwright version pinned in your project.
2. Configure GitHub Actions to install browsers and retain output
This workflow follows Playwright’s documented GitHub Actions setup. The artifact paths must match the paths your tests write. The upload condition allows the artifact step to run after test failures, while a cancelled workflow does not continue uploading.
name: Playwright screenshots
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-output
path: |
artifacts/
playwright-report/
retention-days: 30
Action versions and runner images can change; confirm versions appropriate for your repository. The Playwright CI guide documents the workflow setup, browser installation, containers, workers, and artifact example: Playwright continuous integration.
Download screenshots from a run
- Open the completed workflow run in GitHub Actions.
- Find the artifact named
playwright-outputin the run summary. - Download it and inspect the
artifacts/andplaywright-report/directories.
Artifacts belong to a workflow run and remain available according to the configured retention period and repository settings. If the files are missing, check that the test wrote them to the uploaded path and that the upload step ran. Upload screenshots after the test command in the same job, or pass files between jobs using workflow artifacts.
3. Make visual comparisons reproducible
A screenshot file is a record of one rendered state. To assert that a page matches an expected image, use Playwright Test’s toHaveScreenshot() matcher:
import { test, expect } from '@playwright/test';
test('homepage matches its visual baseline', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png', { fullPage: true });
});
On its first run, Playwright generates a baseline snapshot. Review the generated image and commit it only if it represents the intended appearance. When a UI change is deliberate, review and update the baseline deliberately rather than accepting an unexplained diff. See Playwright visual comparisons for snapshot behavior and configuration.
Rendering can vary with operating system, browser version, browser settings, hardware, power source, and headless mode. Generate and compare baselines in a consistent environment. For example, use the same runner image and Playwright package/browser versions in baseline creation and CI. If using a Playwright container image, follow the compatibility guidance for that specific release in the CI documentation.
4. Keep traces for failures
A screenshot shows the final image, but a trace can show the test timeline, screenshots, DOM snapshots, and network context. In playwright.config.ts, capture traces on the first retry:
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: 1,
use: {
trace: 'on-first-retry',
},
reporter: [['html', { outputFolder: 'playwright-report', open: 'never' }]],
});
The workflow above uploads the HTML report directory. Open the report and follow its trace link, or download a trace and inspect it with npx playwright show-trace path/to/trace.zip. Trace capture on every test can add substantial overhead; first-retry capture is a practical starting point for CI debugging. Configure tracing through the test runner when debugging tests, since the lower-level context tracing API does not record test assertions. See the Trace Viewer guide, Playwright best practices, and tracing API reference.
5. Keep CI runs reliable and efficient
- Install the matching browser: install browser binaries and Linux system dependencies with
npx playwright install --with-deps, or use a compatible Playwright container image. - Stabilize visual output: keep the operating system, browser, fonts, viewport, and relevant settings consistent between baseline generation and comparison.
- Wait for the page condition you need: a screenshot taken before application content is ready may capture a loading state. Wait for a locator or application signal; avoid relying on arbitrary sleeps unless the page has no better readiness signal.
- Start with one CI worker: Playwright recommends one worker in CI as a stability and reproducibility default. Teams with capable runners can increase parallelism or shard tests across jobs when throughput matters.
- Keep artifact volume in check: capture only useful screenshots, avoid duplicating huge full-page images for every test, and set an artifact retention period that fits your debugging needs.
See Playwright’s CI guidance for its recommendations on workers, sharding, and containerized runs. CI runtime and storage costs depend on your runner, test count, image size, and retention choices; the cited documentation does not provide a universal cost or performance benchmark.
6. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable is missing | Dependencies were installed but the Playwright browser binary was not installed, or the package and browser versions do not match. | Run npx playwright install --with-deps after installing the project dependencies. Keep the Playwright package and selected container/browser image compatible. |
| Screenshot file is absent from the run | Upload path does not match the test output, or the upload step was skipped. | Check the path in page.screenshot(), include its directory in the artifact step, and retain the if: ${{ !cancelled() }} condition so failures still upload outputs. |
| Image is blank or shows a loading state | The capture happened before the page rendered its content. | Wait for a stable locator or app-ready condition before capturing; inspect a trace to see what loaded. |
| Full-page image omits lazy content | Content loads only after scrolling into view. | Scroll through the page or trigger the site’s load behavior before capturing, then verify the resulting image. |
| Element screenshot times out | The selector matches no visible element, or the element never becomes ready. | Use a locator that uniquely identifies the target and wait for it to become visible before taking the screenshot. |
| Visual test fails intermittently | Rendering environment or dynamic page content changes between runs. | Use a consistent runner and browser version, stabilize data and page state, and inspect the diff and trace before updating a baseline. |
| No trace appears for a failed test | The trace is configured for first retry, but the test did not retry, or trace/report files were not retained. | Check retry settings and the report output path. Upload both playwright-report/ and any other configured output directories. |
7. Or skip the browser setup
If you need a screenshot of a URL without running a browser in your workflow, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call API returns an image or PDF; the request below saves a WebP screenshot. See the ScreenshotNeo API documentation.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server lets AI agents use screenshot, page information, and PDF capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
FAQ
Can Playwright save a screenshot without a file path?
Yes. page.screenshot() returns image bytes when no path is supplied, so a test can pass the buffer to another tool or process it in memory.
Should I use a screenshot or a trace to debug a failure?
Use a screenshot to inspect a particular rendered state. Use a trace when you need the actions and page context leading to that state; retaining both gives a more complete failure record.
Can I use a full-page screenshot as a visual baseline?
Yes. Use toHaveScreenshot() with fullPage: true, and create and review the baseline in the same rendering environment used by CI.


