ScreenshotNeo

BlogHow-to

Puppeteer Screenshot Testing in GitHub Actions: Setup for Indian Developers

Set up repeatable Puppeteer screenshots in GitHub Actions with Node.js, browser caching, Linux fonts, and saved artifacts. The workflow is the same for developers in India.

By the ScreenshotNeo team4 October 20267 min read

To run Puppeteer screenshot tests in GitHub Actions, add Puppeteer to your Node.js project, commit the lockfile, install dependencies and the browser in the workflow, capture a page with page.screenshot(), and upload the output as an artifact. The hosted workflow runs on GitHub’s selected runner, so being in India does not require a different Puppeteer configuration.

The example below assumes an npm project with a package-lock.json. Puppeteer’s install process normally downloads a compatible Chrome for Testing browser; if a package manager skips install scripts, that browser may be missing. See the Puppeteer installation guide.

1. Add a screenshot script

Install Puppeteer and commit both package.json and package-lock.json:

npm install --save-dev puppeteer

Create scripts/screenshot.mjs. This runnable example accepts a URL and output path, fixes the viewport and device scale factor, waits for the page to reach a useful state, and saves a full-page PNG.

import puppeteer from 'puppeteer';

const url = process.env.SCREENSHOT_URL ?? 'http://127.0.0.1:3000/';
const output = process.env.SCREENSHOT_PATH ?? 'artifacts/home.png';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({
    width: 1440,
    height: 900,
    deviceScaleFactor: 1,
  });
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });
  await page.screenshot({ path: output, fullPage: true, type: 'png' });
} finally {
  await browser.close();
}

The official screenshot guide documents Page.screenshot() and its capture options. Add a package script so local and CI runs use the same command:

{
  "scripts": {
    "screenshot": "node scripts/screenshot.mjs"
  }
}

The target application must be running before this script executes. For a static page, serve the build locally; for an application, start its development or preview server and wait for its ready signal before capturing.

2. Run it in GitHub Actions

Create .github/workflows/screenshots.yml. This workflow checks out the repository, selects Node, installs from the lockfile, caches Puppeteer’s browser download, captures the page, and retains the image even when capture fails.

name: Screenshot

on:
  push:
  pull_request:

jobs:
  screenshot:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: npm

      - name: Cache Puppeteer browser
        uses: actions/cache@v4
        with:
          path: ~/.cache/puppeteer
          key: ${{ runner.os }}-puppeteer-${{ hashFiles('package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-puppeteer-

      - name: Install dependencies
        run: npm ci

      - name: Install Puppeteer browser
        run: npx puppeteer browsers install chrome

      - name: Build application
        run: npm run build

      - name: Start application and capture
        env:
          SCREENSHOT_URL: http://127.0.0.1:3000/
          SCREENSHOT_PATH: artifacts/home.png
        run: |
          npm run preview -- --host 127.0.0.1 > /tmp/app.log 2>&1 &
          echo $! > /tmp/app.pid
          for attempt in $(seq 1 60); do
            if curl --fail --silent http://127.0.0.1:3000/ > /dev/null; then
              break
            fi
            sleep 1
          done
          curl --fail http://127.0.0.1:3000/ > /dev/null
          mkdir -p artifacts
          npm run screenshot

      - name: Upload screenshot
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: page-screenshot
          path: artifacts/
          if-no-files-found: warn
          retention-days: 7

Adjust the build and preview commands, port, URL, Node version, and action versions to match the project. Choose a Node version supported by the application and check current GitHub Actions and Puppeteer requirements when publishing or updating the workflow. Puppeteer’s own CI workflow is a first-party example of browser caching, Linux test execution, and artifact upload; its repository-specific commands and pins are not requirements for your project.

3. Make captures repeatable

A screenshot depends on more than the URL. Keep the following inputs explicit when they affect what the page displays:

  • Viewport and device scale: fix the viewport dimensions and deviceScaleFactor. Use a different preset only when the test is meant to represent that device.
  • Page readiness: select a readiness condition that matches the app. networkidle2 waits for network activity to quiet, but analytics, polling, or long-lived requests can prevent a network-idle condition. Waiting for a specific selector can be more reliable for dynamic applications.
  • Dynamic content: use stable test data, disable animations where appropriate, and wait for the exact content under test. Avoid capturing while timestamps, rotating content, or asynchronous images are still changing.
  • Fonts and locale: install fonts the app uses if the runner lacks them. Set locale and timezone in the browser context when they change visible dates, number formatting, or text. These values are practical controls for repeatability, not India-specific workflow requirements.
  • Browser and runner: browser and runner updates can change rendering. Pin project dependencies and use a consistent environment when visual comparisons need stable output; do not assume different browser or runner versions produce pixel-identical images.

For a focused capture, pass a selector instead of fullPage: true, or capture a viewport-only image. Full-page captures can be large and may expose lazy-loading behavior that a viewport capture does not. Puppeteer’s screenshot guide covers the API details.

4. Choose how the browser is installed

The standard setup lets Puppeteer manage a compatible browser download. This keeps the Puppeteer and browser pairing aligned, and the browser cache can avoid repeated downloads on later runs. An alternative is to provision a browser explicitly, but then the project must keep its executable path and browser version compatible with Puppeteer. Follow Puppeteer’s installation guidance for the chosen arrangement rather than assuming a system Chrome is present.

GitHub-hosted runners are the simplest starting point for most repositories. Self-hosted runners can provide a controlled environment, but the team then owns its installed browser, libraries, fonts, and maintenance. GitHub documents how to install additional software on hosted runners in its runner customization guide.

5. Inspect screenshots and add visual assertions

The artifact makes the actual output available from the workflow run. Open it when a capture fails or a page changes unexpectedly. Artifact retention is configurable; keep enough history for review without retaining files longer than the project needs.

Saving a screenshot is not itself a visual regression test. To fail a build on visual changes, compare the new image against an approved baseline with a visual-diff tool and define an acceptable difference threshold. Keep the browser, fonts, viewport, and input data consistent so the comparison measures application changes rather than environment drift. The cited Puppeteer workflow demonstrates artifact upload, but does not prescribe a particular visual-diff service.

Or skip the browser setup

If the goal is to capture a URL rather than run browser automation inside your project, ScreenshotNeo provides a screenshot API and MCP server. Its API returns a screenshot or PDF from one GET request; see the ScreenshotNeo documentation for its parameters.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture, and each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.

Sign up for 1,000 free screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause Fix
“Could not find Chrome” or browser executable missing The Puppeteer install download did not run, or its cache is unavailable. Check whether package installation scripts were disabled. Run npx puppeteer browsers install chrome in the workflow and confirm the cache path matches Puppeteer’s browser cache location.
Browser exits immediately or reports missing shared libraries Linux launch requirements or runner dependencies are incomplete. Use a supported runner and follow Puppeteer’s current system requirements and troubleshooting guide. Inspect browser stderr in the Actions log.
Navigation times out The route is not ready, the server did not start, or a persistent request prevents the selected wait condition. Verify the server URL and readiness loop first. Increase the navigation timeout only if the page legitimately needs longer; consider waiting for a page-specific selector instead of network idle.
Screenshot is blank or missing content The app was not ready, the route redirected, or content is loaded after the chosen capture point. Check the returned URL and app logs, then wait for a visible selector or other app-specific ready state before calling screenshot().
Text or icons render differently in CI A font available locally is absent on the Linux runner, or font loading had not finished. Install the fonts the application actually uses and wait for fonts before capture, for example with await page.evaluate(() => document.fonts.ready). Puppeteer’s troubleshooting guide notes that some character sets require additional fonts.
Repeated runs differ Browser or runner changed, or page state includes time, animation, randomness, or external data. Fix relevant environment and page inputs, wait for stable content, and avoid relying on a moving external page for a regression baseline.
No artifact appears The capture failed before creating a file, or the artifact path does not match the output path. Confirm SCREENSHOT_PATH and upload path agree, preserve upload with if: always(), and review the preceding step logs.

Performance, reliability, and cost

  • Performance: browser downloads are a common avoidable cost on repeated jobs, so cache Puppeteer’s browser directory with a key tied to the lockfile and operating system. Reuse one browser process for several pages within a single script when appropriate, then close it in a finally block.
  • Reliability: use npm ci with a committed lockfile, wait for the app’s actual readiness, and upload artifacts even after a failure. Keep a screenshot job isolated from unrelated tests if its browser setup or artifacts need separate diagnosis.
  • Cost: the dossier does not establish India-specific GitHub Actions pricing or runner allowances. Check the current GitHub plan and usage terms for the repository. Browser screenshots also consume workflow time and artifact storage, so use suitable capture scope and retention.
  • India-specific setup: no special hosted-runner configuration follows from the developer’s physical location. Set locale and timezone only to match the product behavior being tested.

FAQ

Does a developer in India need a different GitHub Actions workflow?

No India-specific workflow is established by the cited documentation. The job runs on the runner selected in its workflow configuration.

Does saving a screenshot automatically test the design?

No. It creates an artifact for inspection. Failing on visual changes requires a baseline comparison and a defined threshold.

Can I use this for a page that requires authentication?

Yes, if the test supplies the required state securely, such as through test credentials or an authenticated browser session. Do not commit secrets; use repository or environment secrets and avoid including sensitive page data in retained artifacts.