How to Run Visual Regression Tests for a React Website in GitHub Actions from India
Set up Playwright visual regression tests for a React site in GitHub Actions, keep screenshots stable, and investigate failures. The workflow is the same from India; check your repository’s GitHub billing settings.
Run Playwright visual regression tests in GitHub Actions by starting your React app (or Storybook), opening a page at a fixed viewport, and comparing its screenshot with a committed baseline. For stable results, keep the browser and rendering environment consistent, use one worker in CI to start, and inspect screenshot diffs before updating baselines. The workflow does not require a special India-specific setup; check your GitHub account and repository settings for applicable hosted-runner billing.
1. Add Playwright and a visual test
This example assumes a React app with an npm run build script and a production server available as npm run start. Adapt those commands and the page URL to your project. Playwright creates the reference screenshot on the first run; review and commit it as described below.
npm install --save-dev @playwright/test
npx playwright install
Create playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI
? [['list'], ['html', { outputFolder: 'playwright-report', open: 'never' }]]
: 'list',
globalTimeout: 10 * 60 * 1000,
use: {
baseURL: 'http://127.0.0.1:4173',
...devices['Desktop Chrome'],
trace: 'retain-on-failure',
},
webServer: {
command: 'npm run preview -- --host 127.0.0.1',
url: 'http://127.0.0.1:4173',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
If your project does not have a Vite preview script, replace the webServer.command and URL with the project’s actual server command and ready address. Playwright’s webServer waits for the configured URL before starting tests.
Create tests/homepage.spec.ts:
import { test, expect } from '@playwright/test';
test('homepage matches its visual baseline', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
animations: 'disabled',
});
});
Run npx playwright test. On the first run, Playwright reports a missing snapshot. Create the baseline intentionally with npx playwright test --update-snapshots, inspect the generated image, and commit the approved snapshot with the test. Do not accept a new baseline just to clear an unexplained failure.
2. Add the GitHub Actions workflow
Save this as .github/workflows/playwright.yml. The Node setup and checkout action versions shown follow the researched workflow pattern; keep action versions aligned with your repository’s current supported versions. This example builds and serves the app through the Playwright webServer configuration above.
name: Playwright visual tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
visual:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
The global Playwright timeout is ten minutes and the job timeout is fifteen minutes. Keep the job limit above the Playwright global limit so Playwright can stop cleanly and write its report. The artifact step runs after test failures, allowing you to inspect the HTML report when the job finishes.
3. Keep screenshots comparable
A screenshot baseline records pixels, so differences in fonts, browser versions, operating system rendering, data, page state, or timing can create diffs even when the intended design has not changed. Playwright recommends containers for consistent visual regression environments across operating systems. Its CI guidance also recommends one worker by default for stability; increase parallelism only when the environment can support it, or shard a large suite.
- Match the environment: Generate and update baselines in the same browser and operating system environment used by CI where practical. If local development uses another OS, use CI artifacts to understand platform-specific rendering differences.
- Fix the viewport and browser: Keep viewport dimensions, device scale factor, browser project and screenshot options stable. Avoid changing them without deliberately regenerating affected baselines.
- Control application state: Use deterministic fixtures and seeded data. Avoid live APIs, changing timestamps, rotating banners, random IDs and user-specific content in the captured area.
- Wait for readiness: Navigate only after the server is ready, and wait for the specific page content needed by the assertion. Avoid arbitrary sleeps when a locator or readiness condition can express the requirement.
- Reduce motion noise: The example disables animations for the screenshot. This is useful for small or intermittent shifts caused by motion; do not use it to conceal a real layout defect.
- Start with one CI worker: Keep
workers: 1while diagnosing instability. For larger suites, consider sharding and gather the report and artifacts from each failed shard. - Review every changed image: Treat a diff as evidence to inspect. Update snapshots only after confirming the visual change is expected.
4. Inspect a failed run
- Open the failed test in the GitHub Actions job log and identify the page, browser project and screenshot assertion.
- Download the
playwright-reportartifact. Review the expected, actual and diff images, along with the trace retained on failure. - Decide whether the difference is an intended UI change, unstable test data, an environment mismatch, or a timing issue.
- If the UI change is intended, regenerate snapshots in the chosen baseline environment, inspect the output, and commit the approved files with the code change.
- If the result is intermittent, first stabilize inputs and readiness conditions. Keep CI at one worker until the suite is repeatable; then evaluate sharding or additional workers.
5. GitHub Actions from India: setup and cost
The Playwright workflow is a general GitHub Actions setup; the reviewed documentation does not specify a special India-only configuration, runner region, rendering behavior or regional price. Your location alone does not establish where a hosted job runs or what it costs. Check the current billing settings for the GitHub account and repository that own the workflow.
GitHub’s billing guidance says standard hosted-runner usage is free for public repositories and self-hosted runners. For private repositories, included hosted-runner minutes and storage depend on the account plan; usage above the included allowance may be billed. A self-hosted runner gives the team more control over its machine and environment, but the team maintains that machine. A Playwright container can isolate dependencies and help make rendering more consistent, while still requiring suitable runner infrastructure. Compare options using reproducibility, maintenance, report handling, execution needs and the account’s actual billing configuration.
6. Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| Server URL never becomes ready | The configured command, port or readiness URL does not match the React app. | Verify the build and start scripts locally. Set webServer.url to the address the server actually serves, and bind to an address reachable by the test process. |
| Browser executable or system dependency is missing | The workflow installed npm packages but not Playwright browsers and their Linux dependencies. | Run npx playwright install --with-deps in the workflow after npm ci. |
| Snapshot differs only in CI | Local and CI operating systems, fonts, browser builds, viewport or device scale differ. | Generate baselines in the CI-equivalent environment where practical; verify the project and viewport configuration before updating images. |
| Small regions shift between runs | Animations, asynchronous content, clocks or uncontrolled data affect capture timing. | Disable animations for the screenshot, use deterministic data, and wait for a meaningful locator or page-ready condition. |
| Job ends without a useful report | The job timeout is shorter than Playwright’s global timeout, or artifact upload is skipped on failure. | Set the job timeout above globalTimeout and use an artifact condition such as if: ${{ !cancelled() }}. |
| Tests pass locally but fail under concurrency | Parallel tests may share state or compete for limited resources. | Begin with one CI worker. Isolate state and only add workers or sharding when the runner can support the load. |
| Baseline update creates many unexplained changes | Snapshots were regenerated without reviewing the visual cause or environment. | Revert unapproved baseline changes, inspect actual and diff images, and update only after confirming the intended UI output. |
| Unexpected Actions charges or exhausted minutes | Private repository allowances and storage are plan-dependent. | Review the account’s current included allowances and usage before increasing workflow frequency or artifact retention. |
7. Or skip the browser setup
If you need screenshots as artifacts or inputs without maintaining a browser runner, ScreenshotNeo provides a website screenshot API and MCP server. Its API returns a screenshot or PDF from one GET request; see the API documentation for configuration.
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 supported 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 report the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, inspect page information and capture PDFs.
- The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
8. FAQ
Should screenshot baselines be committed?
Yes. Committed baselines let each run compare against an explicit, reviewable reference. Include approved baseline changes with the UI change that caused them.
Can I test a React component instead of a whole page?
Yes. A common React component workflow is to run Storybook and capture its stories. Primer’s React contributor guidance describes component visual tests against Storybook; configure the server readiness URL and snapshot target for your own project.
Does being in India change the GitHub Actions YAML?
The reviewed official guidance does not describe an India-specific workflow. Use the same workflow pattern and confirm current runner and billing details in your GitHub account.
Should I use a hosted or self-hosted runner?
Choose based on the control, maintenance and reproducibility your team needs, plus the billing settings for the repository. Hosted runners reduce machine maintenance; self-hosting shifts that work to the team.
Sources
- Playwright: Setting up CI — workflow setup and report artifacts.
- Playwright: Continuous Integration — workers, timeouts, containers and sharding.
- Primer React: Testing — Storybook visual tests, reports and animation guidance.
- GitHub: Billing and usage — hosted-runner usage and plan-dependent allowances.


