ScreenshotNeo

BlogHow-to

How to Add Cypress UI Tests to an Angular DevOps Pipeline

Add Cypress UI tests to an Angular pipeline with a reliable server-readiness check, a GitHub Actions example, and practical CI troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

To add Cypress UI tests to an Angular DevOps pipeline, install Cypress as a development dependency, start the Angular app version you want to test, wait until it responds, and run cypress run. Make the Cypress command part of the CI job so a failing test fails the pull request or build. The readiness check matters: starting a server and immediately launching Cypress can make tests visit the app before it is available.

This guide uses GitHub Actions and Angular’s development server for a compact end-to-end example. The same sequence applies to other CI providers. If your tests must exercise production output, build and serve that output instead, as described below. Cypress’s CI overview documents the general workflow and readiness options.

1. Confirm the local test baseline

This walkthrough assumes you have an Angular application, Cypress installed or are ready to install it, and at least one end-to-end spec. First run the test locally so that CI setup is not confused with a broken test or app.

npm ci
npx cypress run

cypress run runs the suite from the command line without opening Cypress’s interactive app, which is appropriate for a headless CI job. Angular describes end-to-end tests as testing an application from start to finish through user-like interaction. Its testing guide explains the distinction between end-to-end and unit tests; ng e2e delegates to an e2e builder configured for the project.

2. Install Cypress and define project commands

Install Cypress as a development dependency and commit the lockfile. CI should install the exact dependency tree represented by that lockfile.

npm install --save-dev cypress

Add scripts to package.json, adapting the build and start commands to the scripts and Angular configuration already in your repository:

{
  "scripts": {
    "build:ci": "ng build",
    "start:ci": "ng serve --host 0.0.0.0",
    "cy:run": "cypress run"
  }
}

If the project already has a production build script, use it rather than adding a competing command. Check angular.json for the build configuration and output path. Older tutorials may use ng build --prod or a hard-coded dist/ProjectName directory; do not copy those values without checking your current project.

Configure Cypress’s base URL in cypress.config.js (or the TypeScript equivalent already used by the project):

const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}'
  }
});

With a base URL configured, specs can use paths such as cy.visit('/'). Keep credentials and environment-specific values out of committed test code; pass them through your CI provider’s secret store when needed.

3. Add a GitHub Actions workflow

Create .github/workflows/cypress.yml. This example checks pull requests and pushes to main, installs from the lockfile, builds Angular, starts its server, waits for the app URL, and runs Cypress. The Cypress GitHub Action handles the start and wait steps and returns a failing status when the test command fails.

name: Angular Cypress UI tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  cypress:
    runs-on: ubuntu-24.04
    steps:
      - name: Check out repository
        uses: actions/checkout@v7

      - name: Run Cypress
        uses: cypress-io/github-action@v7
        with:
          build: npm run build:ci
          start: npm run start:ci
          wait-on: 'http://localhost:4200'
          browser: chrome

The action versions and runner above follow Cypress’s current GitHub Actions example in the referenced guide; verify the current supported versions when implementing, or pin an exact release if your team controls dependency upgrades centrally. This sample uses the Angular development server as the test target. Since the action waits for the URL before invoking Cypress, the tests do not race server startup. See the Cypress GitHub Actions guide for action inputs and alternatives.

If you prefer explicit package scripts and a separate readiness utility, Cypress documents start-server-and-test. It starts the server, waits for a successful response, runs the test command, and stops the server afterward. For example, add the utility and define a single CI command:

npm install --save-dev start-server-and-test
{
  "scripts": {
    "ci:e2e": "start-server-and-test start:ci http://localhost:4200 cy:run"
  }
}

Then the job can run npm ci followed by npm run ci:e2e. Use either this orchestration or the action’s start/wait-on approach; the essential property is to wait for a real readiness response rather than relying on a fixed delay.

4. Choose the correct Angular test target

The app under test should match what the check is intended to protect. A development server is convenient and exercises the checked-out source, but a production build served from its generated output can catch build-specific routing, asset, and configuration problems.

Test the development server

The GitHub Actions example runs ng serve. This is usually the simplest first setup. The server compiles the checked-out application and serves it locally on the runner. Keep the host and port aligned across the start command, Cypress baseUrl, and wait-on URL.

Test production output

For production behavior, build with the repository’s production configuration and serve the generated directory with a static server. The output directory and base path depend on the Angular project, so inspect the actual build output rather than assuming a folder name. Your start command must serve that directory on the expected port, and the static server must support the application’s client-side routes if specs visit nested URLs directly.

Whichever target you choose, make the distinction explicit in the workflow. Tests against an already deployed environment should wait for that environment’s URL instead of building and serving a local copy. A deployed target offers environment fidelity, while a local target generally makes test data and cleanup easier to control; the repository’s release process determines which matters more.

5. Make the check useful on pull requests

Run the job on pull requests so a failed UI flow is visible before merge, and on the branches or release events that your repository uses for deployment. The example uses main; change it to match your branch policy. Mark the job as a required status check in repository settings if merging must be blocked when Cypress fails.

Keep the pipeline sequence understandable: checkout, install from lockfile, build if required, start or select the app, wait for readiness, run specs, and retain useful diagnostics. Cypress’s CI documentation covers provider-specific setup for GitHub Actions, CircleCI, GitLab, Jenkins, AWS CodeBuild, and other systems. For Azure Pipelines, Microsoft’s JavaScript pipeline guidance covers Angular CLI usage and test result publishing primitives; combine those with Cypress’s own readiness guidance rather than treating generic Karma or Protractor examples as Cypress configuration.

6. Add reports, artifacts, and parallel work when needed

A basic pipeline does not require Cypress Cloud. Cypress Cloud is an optional hosted service for recorded runs and additional reporting and debugging context, including screenshots or videos, flaky-test signals, and parallelization when configured. Decide whether those features are useful for your team’s suite and collaboration needs before adding the extra setup.

When CI failures are hard to diagnose, configure the project’s Cypress artifact behavior and your CI provider’s artifact retention to preserve relevant screenshots, videos, logs, or test reports. Avoid retaining sensitive page content or credentials in artifacts. For a growing suite, Cypress documents caching and parallelization options. Add them when runtime or feedback needs justify the added configuration.

Keep runner and browser versions predictable when reproducibility matters. Cypress notes that container jobs require Linux runners and discusses consistent browser Docker images as a way to reduce version skew when hosted runner images change. Use a container only when it fits your provider and maintenance model; it is not a prerequisite for running Cypress in CI.

7. Troubleshoot common failures

Symptom Likely cause Fix
Cypress reports that it cannot visit localhost or the connection is refused. The server did not start, is listening on another host or port, or Cypress ran before it was ready. Use a readiness check such as the action’s wait-on or start-server-and-test. Align the server port, readiness URL, and Cypress baseUrl; bind to an address reachable from the runner.
The build succeeds locally but fails in CI. CI may use a different Node environment, install command, environment variable set, or build configuration. Use the committed lockfile with npm ci, select the Node version expected by the project, and pass required non-secret configuration explicitly. Compare the CI build command with the repository’s actual script.
cy.visit('/some/path') returns a 404. The app or static server may not be configured to serve the Angular application shell for client-side routes, or the base URL may be wrong. Check baseUrl and configure the chosen server’s route fallback for the app. Test a direct visit to the nested route in the same environment.
The workflow waits until timeout. The readiness URL is incorrect, the app never binds to the port, or startup fails before serving requests. Read the server output, verify the route and port, and use a lightweight URL that responds once the app is ready. Increase a timeout only when startup is legitimately longer.
Specs pass locally but fail intermittently in CI. Tests may depend on timing, shared state, network services, or non-deterministic data. Wait for observable UI state instead of fixed sleeps, isolate test data, reset state between tests, and avoid relying on external services where a controlled test boundary is appropriate.
The workflow cannot find Cypress or the browser. Dependencies may not have been installed from the project lockfile, or runner/browser setup differs from the local machine. Use the Cypress action or follow Cypress’s provider instructions, ensure Cypress is a project dependency, and keep the runner/browser combination supported and consistent.
Tests work on pushes but not on pull requests from forks. Fork-triggered workflows may not receive repository secrets. Do not depend on private credentials for a safe fork check, or design a separate trusted workflow for secret-dependent tests. Never expose long-lived personal tokens in job definitions.
A test is green but the wrong application was exercised. The workflow may target a deployed URL or build configuration different from the intended artifact. Make the target explicit, log or otherwise identify the environment, and ensure the URL and build configuration correspond to the commit being checked.

8. Performance, reliability, and cost

Pipeline time depends on dependency installation, Angular compilation, server startup, and suite size. Cache package downloads using your provider’s supported mechanism when appropriate, but retain lockfile-based installs. Parallelization can reduce elapsed time for a sufficiently large suite, while introducing coordination and configuration overhead. No single CI provider or test-target choice is universally fastest; compare the existing runner capacity, artifact retention, caching, browser support, and operational cost for your repository.

Reliability usually improves more from correct readiness checks, stable test data, and actionable failure output than from increasing timeouts. Use Cypress Cloud only if its reporting, debugging, collaboration, or parallelization features solve a concrete need. Basic Cypress command-line execution can run without Cloud. Use the CI platform’s short-lived checkout credentials for repository access rather than placing a long-lived personal access token in the job.

Or skip the browser setup

If you need a screenshot of a URL as a visual artifact or for a separate visual review, ScreenshotNeo offers a website screenshot API and MCP server. It does not replace Cypress’s UI interactions or assertions. One GET request captures a page:

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

See the ScreenshotNeo API documentation for the request options. Cookie banners, popups, and chat widgets are removed before the shot; 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, and paid plans start at $5 for 3,000. Learn more at ScreenshotNeo.

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

FAQ

Does Cypress require Angular’s ng e2e command in CI?

No. A CI job can invoke Cypress directly with cypress run. Angular’s ng e2e runs through a configured e2e builder, so use it when that is how your project is set up.

Do I need Cypress Cloud to run UI tests in a pipeline?

No. Cloud recording and its additional reporting features are optional; the Cypress CLI can run the tests in CI without Cloud.

Should the pipeline test a local build or a deployed site?

Use the target that answers the release question. A local build ties the check to the commit and can simplify isolation; a deployed target checks a specific hosted environment and requires environment and test-data management.

Can the same approach work outside GitHub Actions?

Yes. Preserve the steps—install, build if needed, start or select the app, wait for its URL, then run Cypress—and express them using the provider’s job syntax and artifact handling.

Sources