How to Capture Playwright Screenshots in Azure Pipelines
Capture Playwright screenshots on failures, retain traces and reports, and publish every artifact reliably in Azure Pipelines.
To capture Playwright screenshots in Azure Pipelines, configure Playwright to save screenshots and traces, run the tests, then publish playwright-report/ and test-results/ with PublishPipelineArtifact@1. Set both publication tasks to condition: always() so a failed test does not erase the evidence you need.
This workflow gives you three kinds of evidence:
- A PNG or JPEG showing what the page looked like at failure time.
- A Playwright trace containing action order, DOM snapshots, network details, console output and a film-strip timeline.
- An HTML report that lets you browse test status and open attached screenshots and traces.
1. Configure Playwright to retain screenshots and traces
In playwright.config.ts, retain screenshots on failures and traces for failed tests. The on-first-retry trace mode keeps normal runs smaller while recording the retry that usually contains the useful diagnostic context.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
expect: {
timeout: 5_000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report', open: 'never' }],
['junit', { outputFile: 'test-results/results.xml' }],
],
use: {
baseURL: process.env.BASE_URL || 'http://127.0.0.1:3000',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'retain-on-failure',
actionTimeout: 10_000,
navigationTimeout: 30_000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
Playwright’s CI guidance covers browser installation and supported CI environments in its CI documentation. The HTML reporter writes to playwright-report/; failure screenshots, videos and traces are normally written below test-results/.
What each setting does
| Setting | Use | Trade-off |
|---|---|---|
screenshot: 'only-on-failure' |
Captures a screenshot when a test fails. | Much smaller artifacts than capturing every step. |
screenshot: 'on' |
Captures screenshots for every test. | Useful for audits, but increases storage and upload time. |
trace: 'on-first-retry' |
Records a trace on the first retry. | Good diagnostic detail without tracing every successful run. |
trace: 'retain-on-failure' |
Records traces and removes them for successful tests. | Can create larger failed-run artifacts. |
trace: 'on' |
Records every test. | Best for intermittent failures; highest storage cost. |
video: 'retain-on-failure' |
Retains a video only when a test fails. | Helpful for visual timing issues, with larger files. |
2. Write tests that produce useful failure evidence
import { test, expect } from '@playwright/test';
test('checkout page shows the order summary', async ({ page }) => {
await page.goto('/checkout');
await page.getByRole('heading', { name: 'Order summary' }).waitFor();
await expect(page.getByTestId('order-total')).toHaveText('$49.00');
});
For visual regression, use toHaveScreenshot() and commit the expected baseline images. Playwright compares the actual image with the configured snapshot and reports the difference. Keep the browser, viewport, fonts, data and color scheme stable so a genuine UI change is not hidden by environmental noise.
import { test, expect } from '@playwright/test';
test('home page visual baseline', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('home-page.png', {
fullPage: true,
animations: 'disabled',
});
});
Use deterministic seed data, wait for the important UI to settle, and avoid asserting on timestamps, random IDs or live third-party content. A failure screenshot answers “what did the page look like?” A trace answers “what happened before it looked that way?”
3. Add the Azure Pipelines job
The following complete pipeline installs dependencies, installs Playwright browsers, runs tests, publishes the HTML report and publishes test results. The artifact tasks run even when the test command exits with a failure.
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- script: npm ci
displayName: Install dependencies
- script: npx playwright install --with-deps
displayName: Install Playwright browsers
- script: npx playwright test
displayName: Run Playwright tests
'task: PublishPipelineArtifact@1'
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/playwright-report'
artifact: 'playwright-report'
publishLocation: 'pipeline'
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/test-results'
artifact: 'playwright-test-results'
publishLocation: 'pipeline'
- task: PublishTestResults@2
condition: always()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '$(System.DefaultWorkingDirectory)/test-results/results.xml'
mergeTestResults: true
failTaskOnFailedTests: false
testRunTitle: 'Playwright tests'
Replace the first artifact task’s accidental string key with the normal YAML task syntax shown below; this is the version to use in your file:
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/playwright-report'
artifact: 'playwright-report'
publishLocation: 'pipeline'
The PublishPipelineArtifact@1 task uploads a directory as a pipeline artifact. $(System.DefaultWorkingDirectory) is safe when the repository is checked out into the standard agent workspace. If your job changes directory or uses a custom checkout path, point targetPath at the actual absolute directory.
Why condition: always() matters
Without an explicit condition, a later task is commonly skipped after npx playwright test fails. That leaves the pipeline with a red status but no screenshot, trace or report. Apply always() to every diagnostic publication task, including JUnit results.
4. Install browsers on different Azure agents
- Windows and macOS: install Playwright and run the tests. Playwright’s CI documentation states that no additional configuration is required for these agents beyond installing Playwright.
- Linux: use
npx playwright install --with-deps, or run inside an official Playwright container with the required browser libraries. - Self-hosted agents: make sure the agent user can launch the browser and that the required system libraries, fonts and sandbox permissions are available.
A container-based job can make Linux dependencies reproducible:
pool:
vmImage: ubuntu-latest
container: mcr.microsoft.com/playwright:v1.52.0-noble
steps:
- script: npm ci
displayName: Install dependencies
- script: npx playwright test
displayName: Run Playwright tests
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/playwright-report'
artifact: 'playwright-report'
publishLocation: 'pipeline'
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/test-results'
artifact: 'playwright-test-results'
publishLocation: 'pipeline'
Pin the container tag to a Playwright version compatible with your project and verify the current tag in Playwright’s CI documentation before changing it.
5. Open the screenshots, trace and HTML report
- Open the completed pipeline run in Azure DevOps.
- Open the Artifacts section.
- Download
playwright-reportandplaywright-test-results. - Serve the downloaded HTML report locally, or open it according to your team’s artifact policy.
- Open a
.ziptrace with Playwright Trace Viewer:
npx playwright show-trace path/to/trace.zip
The report is best for browsing test status and attachments. The screenshot is best for a quick visual check. The trace is best when you need the action sequence, DOM state, network activity, console messages and film-strip timeline.
6. Publish results from parallel or sharded jobs
Parallel jobs can write to separate directories or publish distinct artifact names. Avoid two jobs uploading the same directory at the same time.
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/test-results'
artifact: 'playwright-results-$(System.JobAttempt)'
publishLocation: 'pipeline'
If you need one combined report, merge results in a follow-up job after all shards finish. Keep each shard’s screenshots and traces identifiable by browser, shard index and job name.
7. Add JUnit reporting to Azure DevOps Test reporting
The JUnit reporter makes test cases visible in Azure DevOps test reporting. The configuration above writes test-results/results.xml, and PublishTestResults@2 imports it after the run. Screenshot, recording and trace attachment behavior is version-sensitive; verify the current Playwright and Azure task behavior before depending on direct attachment links. Microsoft’s guidance describes associating failure artifacts with JUnit results for supported Playwright versions.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No screenshot files | Screenshot policy is disabled, or the test passed. | Set screenshot: 'only-on-failure' or 'on'; inspect test-results/ before publishing. |
| Artifacts missing after a failure | Publish tasks were skipped by the default condition. | Add condition: always() to every report, result and artifact task. |
| “Path does not exist” from PublishPipelineArtifact | The test process wrote files elsewhere, or the path is relative to another directory. | List the directories before publication and use $(System.DefaultWorkingDirectory) plus the real path. |
| Browser executable not found | Playwright browsers were not installed on the agent. | Run npx playwright install --with-deps on Linux or use the official Playwright container. |
| Browser fails to launch on Linux | Missing system libraries, fonts or sandbox permissions. | Use --with-deps, a supported Playwright container, or install the self-hosted agent dependencies. |
| Blank or inconsistent screenshots | The UI was still loading, data was nondeterministic, or the viewport differed. | Wait for a selector or network idle, seed test data, disable animations and standardize browser and viewport settings. |
| Trace is unavailable | Trace mode did not cover that test, or the trace artifact was not downloaded. | Use retain-on-failure or on-first-retry, publish test-results/, then open the downloaded ZIP with Trace Viewer. |
| Visual baseline mismatch | Browser, viewport, fonts, OS rendering or page data changed. | Reproduce with the same environment, inspect expected and actual images, and update baselines deliberately. |
| JUnit report is empty | The output path differs from the publication path, or the reporter was not enabled. | Confirm the junit reporter and check that results.xml exists before PublishTestResults@2. |
9. Performance, reliability and cost considerations
- Artifact size: failure-only screenshots and traces keep uploads smaller. Videos and full traces can grow quickly on suites with many failures.
- Runtime: browser installation is repeated on fresh hosted agents unless you use a supported container or caching strategy. Keep browser and Playwright versions aligned.
- Reproducibility: pin Node, Playwright, browser, viewport, timezone, locale and test data where possible.
- Reliability: use retries for transient CI failures, but keep the original failure evidence. A retry can pass while the first attempt’s screenshot and trace explain the problem.
- Retention: configure Azure DevOps artifact retention to match your debugging window. Delete or restrict old artifacts when they contain sensitive page data.
- Security: screenshots, traces and reports may include tokens rendered in the UI, customer information or internal URLs. Upload them only to trusted artifact stores and apply access controls or encryption.
Or skip the browser setup
If you need a clean screenshot of a deployed URL rather than a test trace, ScreenshotNeo provides a single HTTP request. Its capture service removes cookie and consent banners, newsletter popups and chat widgets before the shot. Bot checks, blank pages, failed loads and cache hits are not billed, and the response reports the verdict and billing status in headers. An MCP server also lets Claude, Cursor and other MCP clients call screenshot tools directly.
See the ScreenshotNeo API documentation for all options.
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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));
Every plan includes the same features: full-page and element capture, device presets and custom viewports, dark mode, retina scale, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, geolocation, PDF output, caching, signed links, asynchronous jobs, bulk capture and usage reporting. You get 1,000 screenshots each month free with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
FAQ
Where do Playwright screenshots go in Azure Pipelines?
Failure screenshots normally appear under test-results/. The HTML report is written to playwright-report/. Publish both directories as pipeline artifacts.
Should I publish screenshots or traces?
Publish both for failed CI runs. Screenshots are faster to inspect; traces explain the sequence of actions and page state that led to the failure.
Can Azure Pipelines show Playwright tests in its Test tab?
Yes. Enable Playwright’s JUnit reporter and import the XML with PublishTestResults@2.
Why use always() instead of succeededOrFailed()?
always() also runs when the job is canceled or reaches another terminal state, making it the safer choice for collecting diagnostics.
Are screenshots safe to publish publicly?
Assume they are sensitive. They can contain rendered secrets, customer data and internal URLs, so restrict artifact access and set an appropriate retention period.


