ScreenshotNeo

BlogHow-to

How to Run Selenium Screenshot Tests in GitLab CI

Capture Selenium screenshots in GitLab CI, keep them as job artifacts, and link them to failed tests with JUnit attachments.

By the ScreenshotNeo team4 October 20268 min read

To run Selenium screenshot tests in GitLab CI, save browser screenshots to a directory inside the checked-out project, then upload that directory as a job artifact. Set artifacts:when: always to retain screenshots when tests fail. If you want images linked from failed test details, also produce a JUnit XML report with a GitLab attachment tag and upload both the report and the screenshot files.

GitLab stores and displays the report and artifacts; Selenium captures the images. GitLab does not automatically compare screenshots or decide whether pixels have changed.

1. Save screenshots inside the project

Use a stable project-relative directory such as screenshots/. Create it before saving, and make the test responsible for capturing the browser state at the point that matters, usually when an assertion fails. A screenshot path outside the checkout may not be included in the job artifacts.

This Python example shows the essential capture step:

from pathlib import Path
from selenium import webdriver

Path("screenshots").mkdir(parents=True, exist_ok=True)
driver = webdriver.Chrome()
try:
    driver.get("https://example.com")
    driver.save_screenshot("screenshots/example.png")
finally:
    driver.quit()

Selenium’s Python API provides save_screenshot; the Selenium documentation also shows screenshot capture and browser window sizing examples. Keep the browser, driver, and application setup appropriate to your runner and pin versions in the project configuration for repeatability. Selenium: working with windows and tabs.

Capture only when a test fails (pytest example)

A test framework hook can capture the current driver after a test failure. The exact hook depends on how your project creates and shares WebDriver instances. This example assumes a fixture named driver and a pytest report hook; adapt the driver lookup to your fixture design.

# conftest.py
from pathlib import Path
import pytest

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == "call" and report.failed:
        driver = item.funcargs.get("driver")
        if driver is not None:
            directory = Path("screenshots")
            directory.mkdir(parents=True, exist_ok=True)
            path = directory / f"{item.name}.png"
            driver.save_screenshot(str(path))
            item.user_properties.append(("screenshot", str(path)))

This hook illustrates capture timing; it does not configure JUnit attachment output by itself. Use your test framework’s supported mechanism to put the attachment tag into the failing test’s <system-out> element.

2. Upload screenshots as GitLab job artifacts

Add the screenshot directory to the job’s artifacts:paths. To retain files even after a failed test command, use when: always. The paths are relative to the job’s project directory.

selenium_tests:
  stage: test
  script:
    - python -m pytest --junitxml=junit.xml
  artifacts:
    when: always
    paths:
      - screenshots/
      - junit.xml
    reports:
      junit: junit.xml

This is the artifact and report wiring, not a complete browser installation recipe. The runner image, Python dependencies, browser and driver, application startup, and test configuration vary by project. GitLab documents artifacts:paths for file retention and artifacts:when: always for collecting artifacts after failure. You can browse or download job artifacts from the pipeline job details. Review artifact access and retention settings if screenshots may contain sensitive data. GitLab job artifacts.

GitLab can display a screenshot attachment in a failed test’s details when the JUnit XML contains an attachment tag and both the XML and referenced image are available in the job. For example, add this text within the failed test case’s <system-out>:

[[ATTACHMENT|screenshots/failure.png]]

Use a path relative to $CI_PROJECT_DIR, and ensure the corresponding image is uploaded. The report configuration is artifacts:reports:junit; uploading the directory under artifacts:paths makes the image files available as artifacts too. Consult GitLab’s instructions for the supported report format and attachment behavior. GitLab unit test reports.

JUnit reports help GitLab show test results, but they do not determine whether the CI job succeeds. The test command must return a non-zero exit status when tests fail. Avoid broad exception handling or cleanup code that hides the original failure or exits successfully after a failed test.

4. Choose where the browser runs

Setup Useful when Things to configure
Browser in the test job You want a direct setup with the browser available to the test process. Install and pin browser and driver versions; ensure the runner has the required packages and display/headless configuration.
Remote Selenium service or Grid You need browser or machine coverage beyond one local test environment, or want browser execution separated from the test job. Set the reachable WebDriver endpoint, credentials if applicable, networking to the app under test, concurrency, and version management.

Selenium Grid is an option for distributing execution across machines and browsers, but it introduces endpoint and network configuration. GitLab’s Selenium server example warns that a service container cannot treat the job container’s localhost as its own address. Check the runner’s networking model and use a hostname reachable from the browser container for the application under test. Selenium documentation · GitLab Selenium server example.

5. Make screenshots useful and repeatable

  • Fix the viewport. Set a known window size before navigation or capture. Browser dimensions affect layout and rendering.
  • Stabilize the page. Use deterministic test data and account for fonts, animations, asynchronous loading, and time-dependent content.
  • Capture at the right point. Save after the page reaches the state relevant to the assertion; a premature capture may show a loader rather than the failure.
  • Use distinct names. Include a test name or worker identifier so parallel tests do not overwrite one another.
  • Keep evidence safe. Screenshots can expose account data, tokens rendered in a page, or personal information. Limit artifact access and retention appropriately.
  • Keep the failure signal. A screenshot hook should not convert a test failure into a passing job if capture itself errors.

For visual regression testing, screenshot capture is only one part of the workflow. Your project must also choose how to create and review baselines and how to compare images. GitLab’s artifact and JUnit features store and present evidence; they do not provide an automatic pixel-diff policy.

Or skip the browser setup

If you need a screenshot of a public page rather than a screenshot from your in-test browser state, ScreenshotNeo can return an image or PDF from one API request. It does not replace Selenium when you need to drive an authenticated test session, inspect intermediate UI state, or capture a failure from the browser already running your test.

See the ScreenshotNeo API documentation. Example request:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers say the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause Fix
No screenshot appears in the job The file was saved outside the checkout, the directory was never created, or the artifact path does not match. Create the directory, save under the project directory, and check the job’s working directory against artifacts:paths.
Screenshot is missing only when tests fail Artifact upload is restricted to successful jobs. Set artifacts:when: always and confirm the runner reached the artifact upload step.
Image is downloadable but absent in failed test details The report lacks the attachment tag, has an invalid relative path, or the JUnit report was not uploaded as a report. Put [[ATTACHMENT|screenshots/name.png]] in the relevant test’s <system-out>, use a path relative to the project directory, and configure both JUnit reporting and artifact upload.
Job passes despite failed tests JUnit display does not set job status, or the script swallowed the test command’s failure. Make the test command return non-zero and preserve its exit status through shell wrappers.
Browser cannot reach the application The browser runs in a different container or network namespace, or the app is not ready. Use a hostname reachable from the browser environment, check service networking, and wait for application readiness.
Capture is blank or shows a loading state The screenshot was taken before navigation or asynchronous rendering completed. Wait for an application-specific ready condition or element before capture and inspect browser logs alongside the image.
Screenshots differ between runs Viewport, browser version, fonts, animation, test data, or time-dependent content changed. Pin the environment where practical, set window size, and stabilize page data and rendering inputs.

Performance, reliability, and cost

Screenshot capture adds browser work and artifact storage to the test job. Capturing only on failure reduces routine files; parallel execution requires unique output paths. Remote Grid can increase browser coverage and throughput, while requiring reliable service connectivity and coordination. Artifact retention and access are project settings, so choose them based on how long evidence is useful and whether it contains sensitive information. The cited documentation does not establish universal runtime or storage benchmarks; measure the impact in your own pipeline.

For most teams, the direct cost of this workflow is the CI minutes and artifact storage already governed by their GitLab configuration. Selenium and GitLab’s documented capture/report mechanism does not require a screenshot API. A separate API may help for independent page capture, but it is a different workflow from capturing the state of the browser used by a Selenium test.

FAQ

How do I keep screenshots when a Selenium test fails?

Save them under the project checkout and configure the job with artifacts:when: always and a matching artifacts:paths entry.

How do I view screenshots for failed tests in GitLab?

Upload JUnit XML and the image files, and add the GitLab attachment tag with a project-relative image path to the failed test’s <system-out>. Otherwise, browse or download the job artifacts.

Does GitLab compare screenshots automatically?

No. This workflow captures, reports, and stores screenshots. Choose and configure a separate image comparison approach if you need visual regression checks.

Can I use Selenium Grid?

Yes. Configure a reachable remote WebDriver endpoint and ensure the browser environment can reach the application under test. Grid is useful for scaling or broader browser coverage, with additional networking and service setup.

References