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.
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
- Sign up for ScreenshotAPI and copy your API key. 5 free credits are included.
- Add the key as a masked CI/CD variable in your GitLab project.
- Create pipeline jobs that capture screenshots.
Installation
Add the API key as a CI/CD variable:
- Go to your project Settings > CI/CD > Variables.
- Click Add variable.
- Set Key to
SCREENSHOTAPI_KEYand Value to your API key. - Check Mask variable and optionally Protect variable.
- 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:
yamlcapture_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:
yamlscreenshot_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:
yamlscheduled_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
- The GitHub Actions integration covers similar patterns for GitHub workflows.
- See the Bitbucket Pipelines integration for Bitbucket CI/CD.
- Learn about visual regression testing for a comprehensive testing strategy.
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.