How to Run Cypress Tests in an Azure DevOps Pipeline
Set up Cypress end-to-end tests in Azure Pipelines with a reproducible install, reliable app startup, JUnit results, caching, and practical troubleshooting.
Run Cypress in Azure Pipelines by installing the Node version your project supports, restoring dependencies with npm ci, starting the application and waiting until it is ready, then running npx cypress run. Publish JUnit XML with PublishTestResults@2 so results appear in the pipeline summary. A reliable pipeline also retains screenshots, videos when enabled, and other useful diagnostics as artifacts.
The example below assumes an npm project, a committed package-lock.json, an application that listens on port 3000, and a Cypress configuration that writes one JUnit report per spec. Change the Node version, start command, readiness URL, and report paths to fit the repository.
1. Add Cypress and CI scripts to the project
Keep Cypress in the project’s devDependencies so the lockfile pins the version used locally and in CI. Add a readiness utility if the application needs to start inside the pipeline. For example, install start-server-and-test as a development dependency and define scripts like these:
{
"scripts": {
"start:ci": "npm run start",
"cy:verify": "cypress verify",
"cy:run": "cypress run",
"cy:ci": "start-server-and-test start:ci http://127.0.0.1:3000 cy:run"
},
"devDependencies": {
"cypress": "<project-pinned-version>",
"start-server-and-test": "<project-pinned-version>"
}
}
Use the actual versions selected by your project’s package manager and commit the resulting lockfile. If your existing start command is not suitable for CI, create a dedicated one that binds to an address reachable from the agent and stays alive. If the app is already deployed to a preview environment, omit the server-start wrapper and set Cypress’s base URL to that deployment.
The start-server-and-test script starts the server, waits for the given URL to respond, and then runs the test command. This avoids the race in a plain background start followed immediately by Cypress: the browser can otherwise launch before the app is accepting requests. Cypress documents readiness-aware CI patterns in its continuous integration guidance.
2. Create the Azure Pipelines YAML
Save this as azure-pipelines.yml at the repository root. The sample uses an Ubuntu hosted agent and Node 24, as in Cypress’s maintained Azure example. Choose a Node version compatible with the application, Cypress version, and native dependencies; the sample version is not a requirement for every project.
trigger:
- main
pool:
vmImage: ubuntu-latest
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- task: NodeTool@0
displayName: Install Node.js
inputs:
versionSpec: '24.x'
- task: Cache@2
displayName: Cache npm packages
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
- task: Cache@2
displayName: Cache Cypress binary
inputs:
key: 'cypress | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
cypress | "$(Agent.OS)"
path: $(HOME)/.cache/Cypress
- script: npm ci
displayName: Install locked dependencies
- script: npm run cy:verify
displayName: Verify Cypress binary
- script: mkdir -p results
displayName: Create test result directory
- script: npx start-server-and-test start:ci http://127.0.0.1:3000 "cypress run --reporter junit --reporter-options 'mochaFile=results/test-output-[hash].xml,toConsole=true'"
displayName: Start app and run Cypress
env:
CYPRESS_BASE_URL: http://127.0.0.1:3000
- task: PublishTestResults@2
displayName: Publish Cypress JUnit results
condition: succeededOrFailed()
inputs:
testRunner: JUnit
testResultsFiles: '**/results/test-output-*.xml'
mergeTestResults: true
failTaskOnFailedTests: true
testRunTitle: Cypress end-to-end tests
- task: PublishPipelineArtifact@1
displayName: Publish Cypress diagnostics
condition: succeededOrFailed()
inputs:
targetPath: cypress
artifact: cypress-diagnostics
publishLocation: pipeline
The example assumes start-server-and-test is installed in the project. If it is already invoked by npm run cy:ci, use that script instead and remove the duplicated wrapper. The results directory must exist before Cypress writes reports.
Cypress’s JUnit reporter supports a filename pattern with [hash], producing a distinct XML file for each spec. A single fixed filename can be overwritten when multiple specs run. Azure’s PublishTestResults@2 task reads matching files and displays test results in the run summary.
The diagnostics artifact path assumes screenshots and any enabled videos are stored below cypress. If your project uses different output directories, publish those directories instead. Azure artifacts are available after the hosted agent is discarded.
3. Configure Cypress for the application
Set the base URL in Cypress configuration so specs can use relative paths. The pipeline’s CYPRESS_BASE_URL environment variable overrides the configured value. Cypress’s CYPRESS_ environment-variable convention also supports other configuration overrides; see the configuration reference for exact names and supported settings.
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://127.0.0.1:3000',
video: true,
screenshotsFolder: 'cypress/screenshots',
videosFolder: 'cypress/videos',
reporter: 'junit',
reporterOptions: {
mochaFile: 'results/test-output-[hash].xml',
toConsole: true
}
}
});
With the reporter configured here, the Cypress command can simply be npx cypress run; the YAML example passes reporter flags explicitly to make the report destination visible in one place. Choose one configuration style and keep the report glob consistent with it.
Failure screenshots are taken during cypress run by default. Video recording is off by default, so enable video: true only when the additional diagnostic value is useful. Cypress’s screenshots and videos guide describes the output behavior.
4. Run against a deployed preview instead
When a separate pipeline or deployment system already provides a testable preview URL, do not start a local server in this job. Set CYPRESS_BASE_URL to the preview address and run Cypress after a readiness check for that environment.
- script: npx wait-on "$(PREVIEW_URL)" && npx cypress run
displayName: Run Cypress against preview
env:
PREVIEW_URL: $(previewUrl)
CYPRESS_BASE_URL: $(previewUrl)
Install wait-on in the project if you use this example. Protect preview credentials with Azure secret variables, and avoid printing tokens in script output. Confirm that the agent can reach the preview over the network before relying on this approach.
5. Handle private package feeds and Cypress Cloud
For private npm packages hosted in Azure Artifacts, configure npm authentication in the pipeline before npm ci. Microsoft documents the required setup in its npm authentication task reference. Keep registry credentials in pipeline-managed secrets rather than committing them to .npmrc.
Cypress Cloud is optional. A normal cypress run does not require Cloud recording. If you choose to record runs, keep the record key in an Azure secret variable and pass it to the test process as CYPRESS_RECORD_KEY. Cypress Cloud offers recorded-run analysis and replay features; decide whether those features justify the extra service for your team. Never put the key in YAML committed to the repository.
6. Choose hosted or self-hosted agents
| Consideration | Hosted agent | Self-hosted agent |
|---|---|---|
| Setup and maintenance | Azure provides the agent image; less machine maintenance for the team. | Your team maintains the OS, browsers, system libraries, and agent. |
| Environment control | Use the supplied image and install project requirements during the job. | Useful when tests need custom system dependencies or a controlled environment. |
| Network access | Check that the hosted agent can reach private apps and services. | Can suit networks or services that are only reachable from your own environment. |
| Persistent caches | Use pipeline caching to reuse eligible package data between runs. | Local persistent caches may be possible, with corresponding cleanup and maintenance. |
Choose based on required operating system and browser coverage, network reachability, custom dependencies, cache needs, and the cost of maintaining machines. Cypress does not require a special Azure extension for this basic workflow: install the package and run the Cypress CLI in a pipeline script.
7. Cache safely and keep runs repeatable
The YAML caches npm’s shared package cache and Cypress’s binary directory. Microsoft’s pipeline caching guidance recommends a workspace cache and keys that include the operating system and lockfile. Cypress’s Azure sample also caches its binary directory; paths can vary by operating system and agent image.
- Commit
package-lock.jsonand runnpm cito install exactly the lockfile-defined dependency tree. - Do not cache
node_moduleswhen usingnpm ci; the command removes that directory before installing. - Key caches by OS and lockfile so dependency changes lead to a new cache entry.
- Keep cache steps optional: they improve restoration time when there is a cache hit but should not be required for correctness.
- On a self-hosted agent, confirm the Cypress cache path and permissions for the account running the agent.
8. Understand reports, artifacts, and cost
JUnit output makes test outcomes visible in Azure’s pipeline summary. Screenshots and videos serve a different purpose: they help diagnose a failure after the run. Keep the artifact contents focused, since videos and screenshots add storage and transfer volume. Remove or expire artifacts according to your team’s retention needs.
Pipeline execution consumes Azure agent time, and optional Cloud recording or video output can add service, storage, or transfer costs according to the plans and policies you use. The research sources do not establish current Azure or Cypress Cloud prices, so check your organization’s current terms rather than relying on a generic estimate. For faster feedback, use caching where appropriate, keep the spec suite focused, and enable video when it provides enough diagnostic value.
9. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Cypress reports that the app is unavailable or times out visiting the base URL. | The server exited, the URL or port is wrong, or Cypress started before the app was ready. | Check the server log and listening address. Confirm CYPRESS_BASE_URL matches the actual URL, then gate the test command with start-server-and-test, wait-on, or an equivalent health check. |
| Dependency installation or Cypress binary verification fails. | Node version mismatch, lockfile or package install problem, unavailable registry, or binary download/network issue. | Review the selected Node version, npm ci output, and cy:verify output. Confirm the agent can reach the package registry and Cypress binary host; fix the dependency or network issue before changing test code. |
| Azure run summary has no tests or misses specs. | No XML was written, report directory or glob is wrong, or multiple specs overwrite a fixed filename. | Create the results directory, verify reporter configuration and generated files, use [hash] in the per-spec filename, and make the publish glob match those files. |
| JUnit publication fails the job although Cypress already failed. | The publish task is configured to fail on failed tests, or the report files do not match. | Use condition: succeededOrFailed() so publication runs after test failures. Confirm the XML is valid and matches the glob; retain failTaskOnFailedTests: true if a failing suite should fail the pipeline. |
| Failures are difficult to reproduce after the run. | Diagnostics were not retained, or video recording was never enabled. | Keep failure screenshots, enable video: true if useful, and publish the actual screenshots and video directories as pipeline artifacts. |
| Cache changes do not improve installation time. | The cache key changes each run, the wrong directory is cached, or the cache is being used for node_modules. |
Cache npm’s shared cache and the Cypress binary directory, inspect cache restore logs, and avoid caching node_modules with npm ci. |
| Private dependency returns an authorization error. | The pipeline has not authenticated to the private feed, or the credential is expired or unavailable to the job. | Configure Azure npm authentication before install, check feed permissions, and keep credentials in secret pipeline settings. |
| Cloud recording rejects the run or exposes a credential. | The record key is missing, invalid, or hard-coded in a script or loggable command. | Store it as a secret pipeline variable and pass it through CYPRESS_RECORD_KEY. Remove committed credentials and rotate any key that was exposed. |
Or skip the browser setup
If your goal is to capture a page as an image or PDF as part of a workflow, ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.
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 and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
- An MCP server gives AI agents tools to take screenshots, get 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; every feature is on every plan.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does Cypress need an Azure Pipelines extension?
No. For a basic CI run, install the project dependencies and invoke Cypress from a pipeline script. Azure tasks are useful for selecting Node, caching, and publishing results.
Do I need Cypress Cloud to run tests in Azure?
No. Cypress’s CLI runs tests without Cloud recording. Cloud is optional for teams that want its recorded-run analysis and related features.
Why use a per-spec JUnit filename?
Cypress can run multiple specs in one command. A shared output filename can be replaced as each spec finishes, so a hash in the filename preserves a separate report for each spec.
Can this pipeline test more than one browser?
Yes. Cypress can be invoked with a browser selection supported by the agent environment. Choose an agent image and browser setup that provide the browser your project needs, and make that choice explicit in the pipeline.


