Running Cypress in headless mode is actually a performance optimization that can make your tests run faster in CI, not just a way to avoid seeing a browser window pop up.
Let’s see it in action. Imagine you have a simple test file, cypress/e2e/spec.cy.js:
// cypress/e2e/spec.cy.js
describe('My First Test', () => {
it('Visits the app and checks the title', () => {
cy.visit('https://example.cypress.io');
cy.contains('Kitchen Sink');
});
});
To run this in headless mode, you’d typically use your package.json script:
// package.json
{
"scripts": {
"cy:run": "cypress run"
}
}
And then execute it from your terminal:
npm run cy:run
You won’t see a browser window, but Cypress will still execute the tests, render the application in a virtual DOM, and report the results.
The core problem headless mode solves is the overhead of rendering a full graphical browser. When you run Cypress with cypress open (the default interactive mode), it launches a real browser instance (Chrome, Firefox, Edge, Electron) and a separate Cypress application that communicates with it. This is great for debugging because you see exactly what the test is doing.
However, in a Continuous Integration (CI) environment, there’s no human to watch the browser. The graphical rendering, window management, and user interface of the Cypress app are unnecessary. cypress run bypasses all of this. It uses a minimal Electron browser instance (by default) that’s optimized for running tests without a visible UI. This means less CPU and memory usage, and crucially, faster test execution because there’s no graphical rendering pipeline to contend with.
The primary lever you control is the browser choice. While Electron is the default for cypress run, you can explicitly specify other browsers that have headless modes, like Chrome or Firefox, using the --browser flag:
cypress run --browser chrome
cypress run --browser firefox
This is useful if your application has browser-specific behaviors you need to test. Cypress will launch these browsers in headless mode, meaning they run in the background without a visible window. The command cypress run --browser chrome --headless is redundant because --headless is implied when using cypress run.
You can also control the output format. By default, cypress run outputs a summary to the console and generates a detailed JSON report and HTML reports in the cypress/results directory. You can customize this with the --reporter and --reporter-options flags. For example, to use the junit reporter for integration with CI systems:
cypress run --reporter junit --reporter-options="mochaFile=cypress/results/results.xml"
This generates a results.xml file that many CI platforms can parse to display test results and failures.
The most surprising thing most people don’t realize is that cypress run doesn’t just skip rendering; it uses a stripped-down, optimized version of the browser engine. This means it’s not exactly the same environment as a headed browser. While this is usually a performance win and sufficient for most tests, you might encounter edge cases where subtle differences in rendering or event handling occur between a headed browser and headless Electron. If your tests pass in headed mode but fail consistently in headless, this is the first place to investigate.
The next step after mastering headless execution is often parallelizing your test runs to further reduce CI times.