ScreenshotNeo

BlogHow-to

How to Save Baseline Screenshots as CI Artifacts for Visual Testing

Save visual test screenshots, diffs, and reports as CI artifacts so failures are easy to review. Configure paths, upload conditions, retention, and access safely.

By the ScreenshotNeo team4 October 20268 min read

Direct answer: Configure your visual test runner to write screenshots, diffs, and reports to known paths, then upload those paths as CI artifacts after the test run. Choose whether to upload on success, failure, or every run; set retention and access deliberately; and compare images in the same browser and operating system environment used to create the approved baselines. Artifact upload preserves run output. It does not, by itself, approve or update baseline screenshots.

1. Separate approved baselines from run artifacts

Keep two kinds of files distinct:

  • Approved baselines: the reference images your tests compare against. Store them in your repository or another versioned, reviewed location, according to your framework’s workflow.
  • Run artifacts: the actual screenshots from this CI run, visual diffs, test reports, traces, and logs. These explain a pass or failure and are usually uploaded from the job workspace.

For Playwright, toHaveScreenshot() compares a rendered screenshot with a stored snapshot. A mismatch should produce evidence for review. Update the expected image only after someone decides the change is intentional. See the Playwright visual comparisons documentation.

2. Make screenshot output paths predictable

Before configuring artifact upload, find where the framework actually writes its output. A test report directory is not necessarily where actual screenshots or diffs are saved. Use explicit paths, keep per-run outputs in a dedicated directory, and avoid uploading an overly broad workspace path that might include secrets or unrelated files.

For example, a Playwright configuration can set the report output directory and retain failure evidence:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
  ],
  outputDir: 'test-results',
  use: {
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
  },
});

Here, playwright-report/ is the HTML report and test-results/ holds test output. Confirm the actual snapshot and diff locations in your project before using these paths in CI. The report does not replace the screenshot files you want reviewers to inspect.

3. Match the environment that produced the baselines

Run comparisons with the same browser and operating system setup used to generate the approved screenshots. Differences in rendering environments can make otherwise unchanged pages appear different. Pin the browser version and use a consistent CI image where practical; keep viewport, device scale factor, fonts, locale, and test data stable as part of your own test setup.

Playwright specifically recommends using the same environment for screenshot comparisons. See its visual comparison guidance and CI guide.

4. GitHub Actions: upload report and screenshot directories

Upload the directories your tests write. The official Playwright GitHub Actions example uploads playwright-report/; add the screenshot or test-result directory when you need those files too. This workflow runs tests, then uploads both paths even when tests fail:

name: Visual tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  visual-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - name: Run visual tests
        run: npx playwright test
      - name: Upload visual test evidence
        if: ${{ always() }}
        uses: actions/upload-artifact@v5
        with:
          name: visual-test-evidence-${{ github.run_id }}
          path: |
            playwright-report/
            test-results/
          if-no-files-found: warn
          retention-days: 14

Check the current supported action version in the upload-artifact documentation when adopting or maintaining this workflow. Set retention-days to your team’s review window and repository policy. A run-specific artifact name makes separate runs easier to identify. if-no-files-found: warn avoids turning a missing output directory into another failure; use error if producing evidence is mandatory.

Choose when to upload

  • if: ${{ always() }} uploads after success, failure, or cancellation, subject to the job reaching that step. Useful when you want a complete audit trail.
  • if: ${{ failure() }} uploads only when an earlier step failed. This saves storage when successful-run files are not useful.
  • With no condition, a step normally runs only when earlier steps succeed. That can skip the artifact upload precisely when a visual test fails.

The workflow uses always() to preserve failure evidence. If your test job can be cancelled or interrupted, confirm the desired behavior for that case too. Keep upload paths narrow, and do not include environment files or credentials.

5. GitLab CI: set paths, conditions, and expiry

In GitLab, declare artifact paths under the job. Use when: always for both passing and failing runs, or when: on_failure when only failed-run evidence matters. Include the JUnit XML report if you generate one, as well as the screenshot directory:

visual-test:
  image: mcr.microsoft.com/playwright:v1.51.0-noble
  script:
    - npm ci
    - npx playwright test --reporter=junit,html
  artifacts:
    when: always
    expire_in: 14 days
    paths:
      - playwright-report/
      - test-results/
      - results.xml
    reports:
      junit: results.xml

Adjust the container image and report filename to match the versions and reporter settings used by your project. GitLab’s job artifacts documentation describes expiry, access, upload conditions, and retention behavior. Configure who can access artifacts for your project; screenshots can reveal information visible in internal or authenticated pages.

GitLab documents a default maximum final artifact archive size of 100 MB; administrators can configure limits at instance, group, or project level. Large screenshot suites may need narrower paths or fewer retained files. GitLab also documents keep-latest behavior that can affect expiry, so check how it applies to your project rather than assuming every artifact disappears exactly on its configured date.

Show screenshots alongside GitLab test failures

If you want screenshots linked from failed test details, attach their paths to the relevant JUnit test cases using GitLab’s supported screenshot attachment format and upload both the XML report and image files. Follow the current unit test report documentation; simply uploading images does not necessarily place them beside test failures in the report.

6. Choose artifact lifecycle and access

Decision Practical choice
Upload condition Always for review history; failure-only when successful outputs are not useful.
Retention Long enough for code review and investigation. Set an explicit expiry where supported and account for provider-specific keep-latest behavior.
Access Limit retrieval to the people and automation that need it. Check whether forked or external contributions can expose artifacts.
Files Upload reports, actual images, diffs, and useful traces. Exclude credentials, broad workspace archives, and unrelated build output.
Baseline changes Review visual changes, then update the versioned baseline through the normal code review process.

Reports, traces, and logs may contain credentials, tokens, source code, or application details. Playwright advises uploading them only to trusted artifact stores or encrypting them before upload or sharing. See the security note in the Playwright CI documentation.

7. Keep artifact size and runtime manageable

  • Capture only the pages and states that provide useful regression coverage; full-page and high-resolution images create larger files.
  • Upload the specific report and test-output directories rather than the entire working directory.
  • Use failure-only uploads if successful screenshots are not part of routine review.
  • Set an expiry that fits your review and incident investigation needs. Keep particularly important baselines in version control, not only in expiring run artifacts.
  • For parallel jobs, give artifacts distinct names or paths so outputs do not overwrite or become ambiguous.
  • When artifacts approach provider limits, retain a smaller evidence set or split outputs across jobs. GitLab’s documented default final archive limit is 100 MB, but the configured limit may differ.

Artifact upload adds storage and transfer work to a CI run; the cited provider documentation does not establish a universal cost or runtime figure. Check your CI plan and instance settings for applicable storage, retention, and size limits.

8. Troubleshooting

Symptom Likely cause Fix
Artifact is missing after a visual test fails The upload step was skipped because earlier steps failed. Run the upload step with an always-run condition, such as GitHub Actions always() or GitLab when: always.
Artifact exists but contains no screenshots The configured path points at the report instead of the screenshot output, or the runner wrote files elsewhere. Inspect the job workspace and framework configuration; upload the directory that actually contains the images.
GitHub reports that no files were found Output generation failed, the path is wrong, or no report was configured. Check the test logs and configured output directory. Use if-no-files-found: error when missing evidence should fail the workflow.
Visual tests fail only in CI Browser, OS, fonts, viewport, or other rendering inputs differ from baseline generation. Use the same browser and operating system environment, and stabilize viewport and test state.
Artifact upload exceeds the size limit Too many images, large full-page captures, traces, or broad paths are included. Narrow the paths, reduce unnecessary captures, split evidence, or adjust the configured limit where you administer the GitLab instance.
Screenshots are missing from the GitLab test report Images were uploaded but not attached to the JUnit test cases in the supported format. Follow GitLab’s test-report screenshot attachment instructions and upload the referenced image files.
Artifacts expire later than expected Provider retention behavior, such as GitLab keep-latest settings, affects expiry. Review project and instance artifact settings and the provider’s documented lifecycle behavior.
Sensitive information appears in a report or trace The captured page, logs, or trace includes authenticated or internal data. Restrict access, avoid uploading sensitive paths, and follow your organization’s trusted storage or encryption policy.

9. Or skip the browser setup

If you need a screenshot of a live page as part of a pipeline or review workflow, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns an image or PDF, and its API documentation covers the available 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}`);
  • Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers say the page verdict and whether the request was billed.
  • An MCP server gives AI agents tools to take screenshots, inspect page information, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

10. FAQ

Should CI upload approved baselines?

Keep the approved references in the location your test framework uses for comparison and review updates as code changes. CI artifacts are most useful for preserving run evidence such as actual screenshots, diffs, and reports.

Should every successful run keep screenshots?

Only if successful-run evidence helps your workflow. Failure-only upload reduces stored output; always-upload can help with audits and comparisons across runs.

Does uploading a Playwright HTML report upload screenshots automatically?

It uploads the files in the report directory. If your framework stores screenshot output elsewhere, include that path too.

How long should artifacts remain available?

Choose a period that covers review and investigation, then check the CI provider’s access and expiry behavior. Keep approved baselines in a durable, reviewed location.