ScreenshotNeo

BlogHow-to

How to Integrate End-to-End Testing Into CI/CD Pipelines

Add reliable end-to-end checks to CI/CD: start the right build, wait for readiness, run browser tests, keep diagnostics, and scale the suite deliberately.

By the ScreenshotNeo team4 October 202610 min read

To integrate end-to-end (E2E) tests into a CI/CD pipeline, add a job that checks out the change, installs pinned test dependencies and browsers, starts or targets the application build under test, waits for a real readiness signal, runs browser flows, and saves reports and failure evidence. Run it on pull or merge requests and on the main branch as appropriate. Make the job required if those flows must protect merges; add workers or CI job sharding only when runtime warrants the added complexity.

The examples below use Playwright Test with GitHub Actions. The same sequence applies to Cypress and other CI providers, but their syntax and browser setup differ. Use the official framework and provider instructions for your project’s current runtime and versions. Playwright CI documentation and Cypress CI documentation provide supported patterns.

1. Decide what the pipeline should validate

Choose the event and application revision before writing YAML. For pull or merge requests, build and test the proposed revision, including any required migrations or configuration. For main-branch checks, test the revision that was pushed. If tests target a deployed preview instead of a local server, make the deployment URL an explicit job input and ensure that environment corresponds to the change.

  • Start with critical user journeys: sign-in, checkout, or another workflow whose failure would block release.
  • Use test data and accounts isolated from production and safe to reset.
  • Decide which browser projects and test tags run on each event.
  • Set a policy for when the E2E check is required, who may approve an exception, and when a broader suite runs.

There is no universal event filter or deployment arrangement. Keep the tested revision and the merge policy clear so the result means what reviewers think it means.

2. Add a Playwright job that waits for the app

This example assumes a Node project whose npm run start command serves the built app at http://127.0.0.1:3000, and that npm run build creates the production build. Adapt the commands to the application. Commit the lockfile and Playwright configuration so CI installs the same dependency graph used locally.

# .github/workflows/e2e.yml
name: End-to-end tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  e2e:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium

      - name: Build application
        run: npm run build

      - name: Run E2E tests
        run: npx playwright test

      - name: Upload Playwright report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          if-no-files-found: ignore
          retention-days: 14

Set retention-days to match your team’s debugging and storage needs; the example value is a configuration choice, not a universal recommendation. Playwright’s CI guide includes workflow examples and HTML report publishing. By default, a failing Playwright test causes the command and job to fail, allowing required-check settings to block a merge.

Configure the application URL and readiness in playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  reporter: [['list'], ['html', { outputFolder: 'playwright-report', open: 'never' }]],
  use: {
    baseURL: 'http://127.0.0.1:3000',
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  webServer: {
    command: 'npm run start',
    url: 'http://127.0.0.1:3000/health',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

Add a /health endpoint that returns success only when the app can serve requests needed by the tests. If your app does not have one, choose a stable route that indicates it is ready. Playwright’s webServer waits for the configured URL before launching tests. Ensure the server command stays alive and logs startup errors to the job output.

The trace, screenshot, and video settings preserve failure evidence. They can increase artifact size and runtime, so retain them on failure unless you have a reason to collect them for every test. Confirm that the report directory is the one your reporter actually writes; otherwise the upload step may have nothing to save.

3. The same integration with Cypress

Cypress supports common CI providers, including GitHub Actions, CircleCI, GitLab CI, Jenkins, and AWS CodeBuild. Install Cypress as a project dependency, build the app, and use a readiness-aware action or script to wait for the server before invoking Cypress.

# Example package scripts
{
  "scripts": {
    "build": "your-build-command",
    "start:ci": "your-server-command",
    "e2e": "cypress run"
  }
}
# GitHub Actions step pattern
- name: Build application
  run: npm run build

- name: Run Cypress after app is ready
  uses: cypress-io/github-action@v6
  with:
    start: npm run start:ci
    wait-on: 'http://127.0.0.1:3000/health'
    command: npm run e2e

Use the current action version and options documented by Cypress when adopting this pattern. The health URL should be reachable from the runner and should represent actual readiness. Cypress warns that starting a server in the background and immediately running cypress run can race server startup; waiting for the server to respond avoids relying on a fixed delay.

4. Trigger the right tests and keep useful evidence

Keep the first CI suite focused enough to give actionable feedback, but include the flows that protect important user behavior. Use stable selectors, deterministic test data, and explicit cleanup. Test against the same app revision that the pipeline is validating.

When a job fails, a reviewer should be able to tell whether the cause was an assertion, an application startup problem, missing test data, a browser failure, or a CI environment issue. Preserve:

  • The test report and console output.
  • Failure screenshots, traces, and video if enabled.
  • Application and server logs from the same run.
  • The commit and environment details needed to reproduce the result.

Upload artifacts even when tests fail, as in the workflow example’s if: always(). Set artifact access and retention according to your CI environment and data-handling needs. Playwright documents CI report publishing; Cypress documents recorded run results as a way to inspect failures.

5. Scale runtime without hiding failures

First identify where time goes: dependency installation, browser installation, app build, server startup, or test execution. Cache package-manager data where supported and keep the browser set limited to what the job needs. Do not cache mutable build outputs or test state unless you can guarantee they are valid for the revision.

For test execution, there are two common scaling approaches:

Approach How it works Trade-off
Runner workers Run tests concurrently within a Playwright invocation. Simple to configure, but workers compete for CPU and memory on one runner. Tests must not depend on order or shared mutable state.
CI sharding Split the suite among multiple jobs, then merge reports if needed. More runner capacity and shorter wall-clock time may be possible, with more pipeline setup and artifact coordination.

Playwright supports worker parallelism and sharding. Start with a conservative setting, inspect resource pressure and failures, then adjust. A higher worker count can make a constrained runner slower or less stable. Tests that share accounts, records, or files need isolation before parallel execution is safe. GitLab’s own E2E pipeline documents selective execution and dynamic scaling, but those are project-specific patterns rather than universal settings.

Prefer fixing slow or flaky tests over automatically retrying everything. Retries can provide diagnostic signal, but a test that passes only on retry still deserves investigation. Keep failure status visible to the merge policy.

6. Make the merge policy explicit

Configure the CI job as a required status check when the covered flows must pass before merging. Decide whether a focused suite runs on each change and a broader suite runs after merge or on a schedule. Document any excluded path, skipped test, or temporary exception, including who owns restoring coverage.

A skipped or non-blocking suite offers less merge protection. GitLab’s guidance on its own E2E environment warns that skipping end-to-end tests increases regression risk. That warning supports treating skips as deliberate exceptions, not as a default way to keep a pipeline green.

7. Run the pipeline locally before relying on it

  1. Run the same install and build commands from a clean checkout.
  2. Start the CI server command and confirm the configured health URL responds.
  3. Run the exact E2E command used by CI.
  4. Force a known assertion failure and verify the job exits nonzero and still uploads evidence.
  5. Confirm the CI branch and pull-request events match your intended merge policy.

Keep the local and CI commands aligned. If developers need a separate command for a local server or test environment, document the difference so a local pass is not mistaken for proof that the pipeline configuration works.

8. Troubleshooting common failures

Symptom Likely cause Fix
Browser tests fail to connect to localhost The app was not ready, the server exited, or the job uses the wrong port or host. Use a readiness URL, inspect server logs, and align the app URL and Playwright baseURL.
Tests pass locally but fail in CI Different environment variables, browser version, data, timezone, or resource limits. Pin dependencies, set required environment explicitly, isolate test data, and use traces to inspect the failure.
Intermittent timeouts Shared state, overloaded runners, slow dependencies, or an unreliable readiness condition. Wait on a real ready endpoint, remove cross-test state, inspect runner capacity, and set timeouts based on the operation that is actually slow.
Report artifact is missing The reporter output path differs from the upload path, or setup failed before writing a report. Check reporter configuration and job logs; upload the configured directory with an always-run step.
Browser installation or launch fails Browser dependencies are missing or the installed browser does not match the framework setup. Use the framework’s documented CI browser installation command and ensure it runs in the same job image as the tests.
Sharded report is incomplete One or more shard jobs failed or their result artifacts were not collected. Check every shard status, upload each shard result even on failure, and follow Playwright’s report merge procedure.
Pipeline is green despite an E2E failure The job is allowed to fail, skipped, or is not a required check. Review job settings and branch protection; make the suite required if it is part of the merge contract.

9. Performance, reliability, and cost

E2E jobs consume CI runner time, browser resources, and artifact storage. The actual cost depends on provider pricing, runner size, test duration, browser matrix, parallel jobs, and retention; the cited documentation does not establish a universal cost or runtime target. Measure your pipeline before deciding how many browsers or shards to run.

  • Improve feedback time: avoid repeated setup where the provider supports safe caches, and run only the browser projects needed for the event.
  • Protect reliability: use readiness checks, pinned dependencies, isolated data, and failure artifacts.
  • Control concurrency: add workers or shards when there is capacity to run them and the suite is safe to parallelize.
  • Keep coverage meaningful: choose a fast required suite and schedule broader checks deliberately rather than silently skipping failures.

Or skip the browser setup

For screenshot capture inside a visual-check workflow or CI job, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. A single GET request returns a PNG, JPEG, WebP, or PDF. It can complement browser tests when the job needs a page capture, while your E2E runner continues to exercise interactive flows. See the ScreenshotNeo API documentation for the available parameters.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://your-app.example \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://your-app.example"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://your-app.example',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Set the API key as a CI secret and pass it through the job environment; do not commit it to the repository. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.

FAQ

Should E2E tests run on every pull request?

Run the checks needed to protect the changes reviewers are approving. Many teams make a focused suite part of pull-request validation and run a broader suite on another suitable event; select the policy based on coverage and available runner capacity.

Do I need a separate deployed environment?

No. The example serves the build on the CI runner. A preview deployment can also work if the job waits for that deployment to become ready and targets the revision under review.

Can a screenshot API replace E2E tests?

No. A screenshot capture gives you an image or document of a page. Browser E2E tests interact with the application and assert that user flows behave correctly.

How long should CI keep reports?

Choose retention long enough for your team to investigate failures and consistent with your artifact-storage policy. The example’s retention value is only a starting configuration.

Sources