How to Run Applitools Visual Tests in GitHub Actions
Run Applitools visual tests in GitHub Actions with secure credentials, commit-linked results, and a reviewable pull request workflow.
Run Applitools visual tests from your repository’s existing test framework in a GitHub Actions workflow. Store the Applitools API key as a GitHub Actions secret, expose it to the test process as APPLITOOLS_API_KEY, and use the commit SHA as the batch ID so results map back to the revision. Review visual differences in Applitools Test Manager before accepting a new baseline. Applitools’ GitHub Actions tutorial describes this integration pattern.
1. Add visual checks to your existing tests
Eyes integrates with supported test frameworks; the actual test calls depend on your SDK. Keep navigation, test data, and visual checkpoints in the project’s normal test suite. For example, an existing Cypress test can include Eyes checkpoints using the Cypress integration documented by Applitools. Do not assume a universal command or interchangeable SDK configuration across Cypress, Selenium, Storybook, or other frameworks.
Install and configure the SDK by following the current Applitools documentation for your framework and the version already used by the repository. Run the visual test locally or in your project’s usual test command before adding CI. This helps distinguish SDK or application setup failures from workflow configuration errors.
2. Add the API key as a GitHub Actions secret
- In the repository, open Settings → Secrets and variables → Actions.
- Select New repository secret.
- Name it
APPLITOOLS_API_KEYand paste the key from your Applitools account. - Reference it through the workflow’s
envblock. Never commit the key in YAML, test source, or a checked-in configuration file.
GitHub withholds repository secrets from workflows triggered by some forked pull requests. If a fork workflow lacks the key, do not work around this by exposing a secret to untrusted code. Choose a trusted review workflow or run the credentialed check after the change is available in a trusted context.
3. Create a GitHub Actions workflow
Save a workflow under .github/workflows/visual-tests.yml. The following is a complete workflow shape; replace the setup and test command with the versions and command your repository actually uses. The example assumes a Node.js project. It deliberately uses the project’s existing visual test command rather than an Applitools-specific command that may differ by SDK.
name: Visual tests
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
visual-tests:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- name: Install dependencies
run: npm ci
- name: Run visual tests
run: npm run test:visual
env:
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
APPLITOOLS_BATCH_ID: ${{ github.event.pull_request.head.sha || github.sha }}
Use the correct runtime and dependency installation for your project. Pin action references to reviewed versions according to your organization’s supply-chain policy. If your SDK reads the batch ID from a configuration file or a different environment variable, follow that SDK’s current instructions; the environment variable shown is a common pattern, not a guarantee for every integration.
Workflow event choices
pull_requestruns checks for pull requests and makes their status visible to reviewers. Secret availability is restricted for forked PRs.pushto the default branch gives you a post-merge run. Adjustmainto your branch name.- For private repositories or sensitive tests, decide which events are trusted to receive credentials. Avoid
pull_request_targetfor executing untrusted pull request code with secrets.
4. Give each run a stable batch identity
Set a batch ID derived from the exact commit under test. On a pull request, the example chooses the head SHA; on a push, it uses the workflow SHA. This makes it easier to connect test results to a revision. Keep the same batch ID across all jobs or shards that belong to one logical run.
Do not use a random value per test process if you expect related results to appear together. If you run multiple workflows for the same commit and need separate logical batches, define a deliberate naming scheme that distinguishes the runs while keeping all shards within each run consistent.
5. Review results and handle visual changes
- Open the pull request’s checks and inspect the workflow result and logs.
- Follow the linked Applitools result into Test Manager.
- Inspect each difference in context, including the affected page or component and the baseline.
- Accept a changed baseline only after confirming the UI change is expected. Reject or investigate unexpected differences.
A passing workflow status communicates the check result; it does not decide whether a visible change is correct. Keep baseline approval part of code review rather than treating every newly captured screen as automatically acceptable.
Example: Cypress project setup
For Cypress, keep the Eyes integration and test in the Cypress project, and make the workflow invoke that project’s configured visual-test script. Applitools has a Cypress and GitHub Actions tutorial; check its SDK setup against the current integration version before copying commands, since older integration examples may use third-party actions or outdated inputs.
# package.json script example; this invokes your project's own configured command
{
"scripts": {
"test:visual": "cypress run"
}
}
This script is runnable only after Cypress and the Applitools Cypress SDK are installed and your tests are instrumented with Eyes. The test framework’s command alone does not add visual checkpoints. Keep framework-specific test code and SDK setup alongside the project’s existing test configuration.
6. Scale with a matrix when there is a reason
Start with one job. If the suite is large enough to justify parallel execution, split the test set into non-overlapping shards and use a matrix. All shards for a commit must share the same batch ID so their results stay together. Applitools’ Storybook scaling guide shows a matrix-and-CLI approach and warns that concurrent shards need compatible batch-closing behavior. Its example is specific to Storybook; adapt the partitioning and command for your test framework.
jobs:
visual-tests:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- run: npm ci
- name: Run one shard
run: npm run test:visual -- --shard=${{ matrix.shard }}/4
env:
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
APPLITOOLS_BATCH_ID: ${{ github.event.pull_request.head.sha || github.sha }}
The shard argument above is illustrative: use the syntax supported by your runner and test framework, and ensure each shard selects a distinct portion of the suite. Before parallelizing, check the relevant Applitools account’s GitHub integration settings for automated batch closing. The Storybook article describes disabling automatic batch closing for concurrent shards under Test Manager’s Admin → Teams → Integrations → GitHub → Manage repositories. Confirm that setting applies to your account and integration before relying on it.
Options and configuration to decide
| Decision | Guidance |
|---|---|
| Runner | Use GitHub-hosted runners for a straightforward managed setup, or a self-hosted runner when your app or environment requires it. Keep credentials scoped to jobs that need them. |
| Runtime and dependencies | Match the project’s supported runtime, lockfile, browser setup, and package installation. Cache dependencies where suitable; do not let a stale cache replace a clean reproducible install. |
| Triggers | Run on pull requests for review feedback and optionally on pushes to the default branch. Consider fork secret restrictions and trusted event boundaries. |
| API key | Store as a GitHub secret and pass it through the process environment. Avoid printing environment variables or enabling shell tracing around secrets. |
| Batch ID | Use the commit under test. Reuse it across parallel jobs in the same batch. |
| Concurrency | Serialize initially. When sharding, make partitions disjoint and configure batch-closing behavior for concurrent jobs. |
| Result review | Expose the check on the PR and have reviewers inspect diffs before accepting baseline updates. |
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Authentication fails or the API key is missing | The secret name differs, it is unavailable to the event, or it was not passed to the step. | Confirm the secret is named APPLITOOLS_API_KEY, referenced under the visual-test step’s env, and that the event is allowed to access repository secrets. Do not print the key to diagnose it. |
| Fork pull request cannot authenticate | GitHub does not provide repository secrets to many fork-triggered workflows. | Use a trusted workflow for credentialed execution or a safe manual process. Do not execute untrusted code in a secret-enabled context. |
| Tests run but no visual results appear | The project command may not invoke Eyes, or the test lacks visual checkpoints. | Check SDK installation, framework integration, test instrumentation, and the command’s exit status. Consult the current SDK guide for the exact setup. |
| Results appear in separate batches | Jobs use different batch IDs or a per-job identifier. | Set one stable batch ID for the commit and reuse it for all related jobs. |
| One shard closes results before the others finish | Automated batch closing is incompatible with concurrent shard completion. | Review the account’s GitHub integration batch-closing setting and configure it for parallel runs; verify the correct option in Test Manager. |
| Workflow succeeds but the PR shows an unexpected visual change | A workflow status is not a human approval of the baseline. | Open the result in Test Manager, inspect the difference, and accept only if the UI change is expected. |
| Workflow fails after copying an old action example | The action may be third-party, changed, unmaintained, or use outdated inputs. | Prefer the project’s SDK or CLI documentation and current supported commands. Review action ownership, version pinning, and exact inputs before reuse. |
| Tests are flaky across local and CI runs | Environment, test data, timing, fonts, or browser/runtime differences may affect rendered output. | Make test data and application state deterministic, wait for the application’s actual ready condition, and align runtime/browser setup. Examine the specific diff before changing a baseline. |
Performance, reliability, and cost
CI time depends on the test framework, application startup, suite size, and Applitools configuration; the sources reviewed do not establish a general runtime benchmark. Measure your own workflow before introducing a matrix. Parallel shards can reduce elapsed time when the workload partitions cleanly, but add runner and configuration complexity.
For reliable comparisons, make the app state reproducible, use stable test data, wait for meaningful readiness conditions, and keep the batch identity tied to the tested commit. Treat timeouts and incomplete loads as test failures to investigate rather than as permission to accept a potentially incomplete baseline.
Applitools plan costs and quotas are not specified in the research for this article. Check current account pricing and usage terms before estimating CI cost. Control unnecessary runs with suitable workflow triggers and avoid rerunning a full suite for unrelated changes unless that coverage is intentional.
Or skip the browser setup
For capturing a website screenshot directly, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. This is a screenshot capture alternative, not an Applitools visual regression test suite: use it when your task is to obtain page images or PDFs without operating browser capture infrastructure.
One GET request returns an image or PDF. See the ScreenshotNeo API documentation for request options.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
- Cookie banners are accepted and removed before the shot; newsletter popups and chat widgets are removed too. Each step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; all features are on every plan.
Sign up for ScreenshotNeo free: 1,000 screenshots a month, no card required.
FAQ
Does adding the workflow create visual tests automatically?
No. The workflow runs your command; the project must already have the appropriate Applitools SDK integration and visual checkpoints.
Should I accept every new baseline after a pull request?
No. Review the difference and accept it only when it matches an intended UI change.
Can I use the same batch ID for parallel jobs?
Yes. Related shards should share the same batch ID so their results are associated with the same revision and run.
Can ScreenshotNeo replace Applitools in this workflow?
They serve different jobs. ScreenshotNeo captures web pages as images or PDFs; Applitools visual testing compares checkpoints against baselines and presents differences for review.


