Screenshot API for GitLab CI/CD

Automate website screenshots in GitLab CI/CD with ScreenshotAPI. Visual regression testing, deploy evidence, and pipeline screenshot workflows.

Last updated: 2026-03-25

Try ScreenshotAPI free

5 free credits. No credit card required.

Start for free

Automate Screenshots in GitLab CI/CD with ScreenshotAPI

Screenshots in CI/CD pipelines serve two critical functions: visual regression testing to catch UI changes before they reach production, and deploy evidence to document what your site looks like after each release. The traditional approach means installing Chromium on the runner, which adds minutes to your pipeline and hundreds of megabytes to the image.

ScreenshotAPI replaces all of that for your GitLab CI screenshot integration. Simple HTTP requests from your pipeline jobs return pixel-perfect images. No browser installation, no docker-in-docker, no complex runner configuration.

Quick Start

  1. Sign up for ScreenshotAPI and copy your API key. 5 free credits are included.
  2. Add the key as a masked CI/CD variable in your GitLab project.
  3. Create pipeline jobs that capture screenshots.

Installation

Add the API key as a CI/CD variable:

  1. Go to your project Settings > CI/CD > Variables.
  2. Click Add variable.
  3. Set Key to SCREENSHOTAPI_KEY and Value to your API key.
  4. Check Mask variable and optionally Protect variable.
  5. Click Add variable.

No runner packages or Docker images are required. The lightweight alpine image with curl is sufficient.

Basic Pipeline: Screenshot on Deploy

Capture screenshots after deploying to production:

yaml
# .gitlab-ci.yml stages: - deploy - screenshot deploy_production: stage: deploy script: - echo "Deploying to production..." environment: name: production url: https://yoursite.com capture_screenshots: stage: screenshot image: alpine:latest needs: [deploy_production] before_script: - apk add --no-cache curl script: - | for page in "" "/pricing" "/docs" "/blog"; do name=$(echo "$page" | sed 's/^\///' | sed 's/^$/homepage/') curl -s -o "${name}.png" \ -H "x-api-key: ${SCREENSHOTAPI_KEY}" \ "https://screenshotapi.to/api/v1/screenshot?url=https://yoursite.com${page}&width=1440&height=900&type=png" echo "Captured ${name}.png" done artifacts: paths: - "*.png" expire_in: 30 days

Visual Regression Testing

Compare screenshots between the target branch and the merge request:

yaml
# .gitlab-ci.yml visual_regression: stage: test image: node:20-alpine rules: - if: $CI_MERGE_REQUEST_IID script: - node scripts/capture-screenshots.js - node scripts/compare-screenshots.js artifacts: paths: - screenshots/ expire_in: 14 days when: always

Capture Script

javascript
// scripts/capture-screenshots.js const fs = require('node:fs'); const path = require('node:path'); const API_BASE = 'https://screenshotapi.to/api/v1/screenshot'; const API_KEY = process.env.SCREENSHOTAPI_KEY; const BASE_URL = process.env.CI_ENVIRONMENT_URL || 'https://staging.yoursite.com'; const pages = [ { name: 'homepage', path: '/' }, { name: 'pricing', path: '/pricing' }, { name: 'docs', path: '/docs' }, ]; const outputDir = 'screenshots/current'; fs.mkdirSync(outputDir, { recursive: true }); async function capture(page) { const url = `${BASE_URL}${page.path}`; const params = new URLSearchParams({ url, width: '1440', height: '900', type: 'png', waitUntil: 'networkidle', }); const response = await fetch(`${API_BASE}?${params}`, { headers: { 'x-api-key': API_KEY }, }); if (!response.ok) { console.error(`Failed: ${page.name} (${response.status})`); return; } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(path.join(outputDir, `${page.name}.png`), buffer); console.log(`Captured ${page.name} (${buffer.length} bytes)`); } async function main() { await Promise.all(pages.map(capture)); } main();

Comparison Script

javascript
// scripts/compare-screenshots.js const fs = require('node:fs'); const path = require('node:path'); const currentDir = 'screenshots/current'; const baselineDir = 'screenshots/baseline'; if (!fs.existsSync(baselineDir)) { console.log('No baseline found. Current screenshots saved as baseline.'); fs.cpSync(currentDir, baselineDir, { recursive: true }); process.exit(0); } const files = fs.readdirSync(currentDir).filter(f => f.endsWith('.png')); let changed = false; for (const file of files) { const current = fs.readFileSync(path.join(currentDir, file)); const baselinePath = path.join(baselineDir, file); if (!fs.existsSync(baselinePath)) { console.log(`New page: ${file}`); changed = true; continue; } const baseline = fs.readFileSync(baselinePath); if (!current.equals(baseline)) { console.log(`Changed: ${file}`); changed = true; } else { console.log(`Unchanged: ${file}`); } } if (changed) { console.log('\nVisual changes detected. Review screenshots artifacts.'); process.exit(1); }

Review App Screenshots

Capture screenshots of GitLab review apps automatically:

yaml
capture_review_screenshots: stage: test image: alpine:latest rules: - if: $CI_MERGE_REQUEST_IID environment: name: review/$CI_MERGE_REQUEST_IID url: https://review-${CI_MERGE_REQUEST_IID}.yoursite.com before_script: - apk add --no-cache curl script: - | REVIEW_URL="https://review-${CI_MERGE_REQUEST_IID}.yoursite.com" echo "Capturing screenshots of ${REVIEW_URL}" curl -s -o homepage.png \ -H "x-api-key: ${SCREENSHOTAPI_KEY}" \ "https://screenshotapi.to/api/v1/screenshot?url=${REVIEW_URL}&width=1440&height=900&type=png&waitUntil=networkidle" curl -s -o mobile.png \ -H "x-api-key: ${SCREENSHOTAPI_KEY}" \ "https://screenshotapi.to/api/v1/screenshot?url=${REVIEW_URL}&width=390&height=844&type=png&waitUntil=networkidle" artifacts: paths: - "*.png" expire_in: 7 days

Multi-Viewport Matrix

Capture screenshots across viewports using GitLab's parallel matrix:

yaml
screenshot_matrix: stage: test image: alpine:latest parallel: matrix: - VIEWPORT: [desktop, tablet, mobile] THEME: [light, dark] before_script: - apk add --no-cache curl script: - | case $VIEWPORT in desktop) WIDTH=1440; HEIGHT=900 ;; tablet) WIDTH=768; HEIGHT=1024 ;; mobile) WIDTH=390; HEIGHT=844 ;; esac curl -s -o "screenshot-${VIEWPORT}-${THEME}.png" \ -H "x-api-key: ${SCREENSHOTAPI_KEY}" \ "https://screenshotapi.to/api/v1/screenshot?url=https://yoursite.com&width=${WIDTH}&height=${HEIGHT}&type=png&colorScheme=${THEME}" artifacts: paths: - "*.png" expire_in: 14 days

Scheduled Monitoring

Run screenshots on a pipeline schedule:

yaml
scheduled_monitoring: stage: monitor image: alpine:latest rules: - if: $CI_PIPELINE_SOURCE == "schedule" before_script: - apk add --no-cache curl script: - mkdir -p screenshots - | TIMESTAMP=$(date +%Y%m%d-%H%M) for page in "" "/pricing" "/docs"; do name=$(echo "$page" | sed 's/^\///' | sed 's/^$/homepage/') curl -s -o "screenshots/${name}-${TIMESTAMP}.png" \ -H "x-api-key: ${SCREENSHOTAPI_KEY}" \ "https://screenshotapi.to/api/v1/screenshot?url=https://yoursite.com${page}&width=1440&height=900&type=png" done artifacts: paths: - screenshots/ expire_in: 90 days

Create the schedule in CI/CD > Schedules. Set it to run every 4-6 hours.

Production Tips

Artifacts

Use GitLab artifacts to store screenshots. Set appropriate expiration times: 7 days for MR screenshots, 30 days for deploy evidence, and 90 days for monitoring archives.

Merge Request Comments

Use the GitLab API to post screenshot links as MR comments. This gives reviewers visual context directly in the merge request.

Runner Efficiency

ScreenshotAPI keeps runner requirements minimal. An alpine:latest image with curl is all you need. No Docker-in-Docker, no specialized runner images, no GPU requirements.

Credit Planning

Calculate credits as: pages per job multiplied by jobs per month. A pipeline capturing 5 pages on 80 MRs per month uses 400 credits. Visit the pricing page for tier options.

Further Reading

Frequently asked questions

Do I need to install Chrome on the GitLab CI runner?

No. ScreenshotAPI handles browser rendering remotely. Your pipeline only needs curl or a lightweight script to make HTTP requests. No Chromium, no docker-in-docker.

Can I capture screenshots of GitLab review apps?

Yes. Use the CI_ENVIRONMENT_URL or your review app URL pattern to pass the preview URL to ScreenshotAPI. This gives you visual evidence for every merge request.

How do I store the API key in GitLab CI?

Add it as a CI/CD variable in your project settings. Set it as masked and protected so it does not appear in job logs and is only available on protected branches.

Can I use screenshots for visual regression testing in GitLab?

Yes. Capture screenshots on every merge request, compare them to baseline images committed in the repository, and fail the pipeline if visual differences exceed a threshold.

Related resources

Start capturing screenshots today

Create a free account and get 5 credits to try the API. No credit card required. Pay only for what you use.