Cypress Testing: Complete Beginner's Guide (2026 Step-by-Step)
Learn Cypress testing from scratch in 2026. Step-by-step beginner's guide covering installation, first test, locators, assertions, fixtures, custom commands, CI/CD, component testing, and the Cypress Dashboard.

In this article
- 1. Why Cypress in 2026
- 2. Install Cypress Step by Step
- 3. Your First Cypress Test
- 4. Locators and the Auto-Wait Magic
- 5. Assertions Deep Dive
- 6. Fixtures and Test Data
- 7. Custom Commands
- 8. Network Stubbing with cy.intercept
- 9. Hooks: Before, BeforeEach, After
- 10. Component Testing
- 11. Page Object Model in Cypress
- 12. Parallelization and the Cypress Dashboard
- 13. Running Cypress in CI/CD
- 14. Debugging and Time-Travel
- 15. Cypress Best Practices for 2026
- 16. Migrating From Selenium to Cypress (When It Makes Sense)
- 17. The Cypress Ecosystem in 2026
- 18. Cypress vs Playwright vs Selenium vs WebdriverIO (Honest 2026 Comparison)
- 19. Common Cypress Errors and How to Fix Them
- Next, level up your Cypress skills
- Frequently asked questions
Last updated: June 25, 2026 · Reading time: 25 minutes · By SoftwareTestPilot Editorial Team
What you'll learn: By the end of this Cypress testing tutorial you will have Cypress installed, your first test passing, network stubs working, component tests running, and a CI pipeline executing everything in headless mode — even if you have never written a single line of test code before.
1. Why Cypress in 2026
Cypress is a JavaScript end-to-end testing framework built for the modern web. Unlike Selenium, which drives the browser from outside, Cypress runs inside the same execution context as your application. That single architectural difference unlocks four superpowers:
- Auto-waiting — Cypress waits for elements, animations, network calls, and re-renders before acting. Tests rarely need
cy.wait(2000). - Time-travel debugging — hover over any command in the runner and see the DOM snapshot, network state, and console logs at that exact moment.
- Real-time reloads — save a file, see the test re-run instantly. The feedback loop is sub-second.
- Network control — stub any HTTP call with a one-liner to test edge cases that are hard to reproduce against a real backend.
The trade-offs are real: limited to Chrome/Edge/Firefox/WebKit, no multi-tab support, and a smaller community than Selenium. For new JavaScript front-end projects, those trade-offs are usually worth it. Compare deeper with Playwright.
2. Install Cypress Step by Step
Prerequisites
- Node.js 18 LTS or 20 LTS — download from nodejs.org. (Node 22 is supported but Cypress has best stability on LTS.)
- A code editor — VS Code is recommended.
- A project — any web app or a fresh demo you want to test against.
Step 1 — Create a project folder
mkdir cypress-demo
cd cypress-demo
npm init -yStep 2 — Install Cypress
npm install --save-dev cypressThis will download Cypress and its bundled browsers. It can take a few minutes the first time because Cypress downloads Electron, Chrome, Firefox, and WebKit.
Step 3 — Open the Test Runner
npx cypress openCypress creates a cypress/ folder, an initial cypress.config.js, and opens the Test Runner with example specs. Pick E2E Testing → Chrome and Cypress scaffolds a starter spec.
Step 4 — Verify the install
npx cypress run --browser chrome --spec "cypress/e2e/1-getting-started/todo.cy.js"Tip: Cypress caches browsers in
~/.cache/Cypress. Cache that directory in your CI to shave minutes off every build.
3. Your First Cypress Test
Create cypress/e2e/login.cy.js:
describe('Login flow', () => {
it('logs in with valid credentials', () => {
cy.visit('https://example.com/login')
cy.get('[data-testid="email"]').type('admin@example.com')
cy.get('[data-testid="password"]').type('Sup3rSecret!')
cy.get('[data-testid="submit"]').click()
cy.url().should('include', '/dashboard')
cy.contains('Welcome, admin').should('be.visible')
})
})What's happening:
describegroups related tests.itis a single test case.cy.visitopens the URL.cy.getqueries a DOM element..type,.clicksimulate user actions..shouldmakes assertions.
Run it from the Test Runner or with npx cypress run. Cypress re-runs the test every time you save the file.
4. Locators and the Auto-Wait Magic
The single biggest Cypress mistake is using fragile XPath like /html/body/div[3]/form/input[1]. Use stable, semantic selectors instead.
| Priority | Selector | Example | When to use |
|---|---|---|---|
| 1 | data-testid | [data-testid="submit"] | Always preferred — explicitly added for tests, never restyled |
| 2 | role | cy.getByRole('button', { name: 'Submit' }) | Best for accessibility and stability |
| 3 | id | #email | Acceptable but IDs often change |
| 4 | CSS | .btn-primary | Fine for stable class names |
| 5 | Text | cy.contains('Sign in') | Useful when text is part of the contract |
| 6 | XPath | //button | Avoid unless no alternative |
Cypress auto-waits up to 4 seconds (configurable) for each locator. There is no waitForElement; if the element does not appear, the test fails with a clear message.
import '@testing-library/cypress/add-commands'
cy.getByRole('button', { name: /submit/i }).click()
cy.getByLabelText('Email address').type('admin@example.com')
cy.getByTestId('password').type('Sup3rSecret!')5. Assertions Deep Dive
Cypress bundles Chai and Sinon-Chai for assertions. The chainable .should() is the most common form.
cy.get('[data-testid="cart-count"]')
.should('be.visible')
.and('have.text', '3')
.and('have.css', 'color', 'rgb(22, 163, 74)')
cy.get('[data-testid="submit"]').should('be.disabled')
cy.url().should('eq', 'https://example.com/dashboard')
cy.window().its('localStorage.user').should('exist')Other useful assertions:
expect(value).to.deep.equal(expected)— deep object equality.cy.get('.row').should('have.length', 12)— count.assert.isAbove(response.duration, 0)— performance guard.
6. Fixtures and Test Data
Fixtures are static files in cypress/fixtures/ that hold deterministic test data. Use them for reusable, known-good data; use factories for unique-per-test data.
Step 1 — Create a fixture
Add cypress/fixtures/users.json:
[
{ "email": "admin@example.com", "role": "admin" },
{ "email": "user@example.com", "role": "viewer" }
]Step 2 — Load it
cy.fixture('users.json').then((users) => {
cy.login(users[0].email, 'Sup3rSecret!')
})Step 3 — Use multiple data sets
cy.fixture('users.json').then((users) => {
users.forEach((user) => {
it(`logs in as ${user.role}`, () => {
cy.login(user.email, 'Sup3rSecret!')
cy.get('[data-testid="role"]').should('have.text', user.role)
})
})
})7. Custom Commands
Custom commands turn repetitive sequences into reusable keywords, the Cypress equivalent of POM helper methods.
Define a command
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login')
cy.get('[data-testid="email"]').type(email)
cy.get('[data-testid="password"]').type(password)
cy.get('[data-testid="submit"]').click()
cy.url().should('include', '/dashboard')
})
Cypress.Commands.add('loginByRole', (role) => {
cy.fixture('users.json').then((users) => {
const user = users.find((u) => u.role === role)
return cy.login(user.email, 'Sup3rSecret!')
})
})Use the command
beforeEach(() => {
cy.loginByRole('admin')
})Custom commands are typed automatically via JSDoc if you use TypeScript.
8. Network Stubbing with cy.intercept
cy.intercept is one of Cypress's killer features. You can stub any HTTP call to test edge cases that are hard to reproduce against a real backend.
Stub a response
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
cy.get('[data-testid="user-row"]').should('have.length', 2)Modify a real response
cy.intercept('GET', '/api/profile', (req) => {
req.reply((res) => {
res.body.role = 'admin'
})
})Simulate latency and errors
cy.intercept('POST', '/api/orders', (req) => {
req.reply({ statusCode: 500, body: { error: 'boom' } })
}).as('createOrder')Wait on a real call
cy.intercept('POST', '/api/orders').as('createOrder')
cy.get('[data-testid="buy"]').click()
cy.wait('@createOrder').its('response.statusCode').should('eq', 201)For advanced patterns see our API Testing Guide.
9. Hooks: Before, BeforeEach, After
Cypress provides Mocha-style hooks for setup and teardown.
describe('Orders', () => {
before(() => { cy.seedDatabase() })
beforeEach(() => { cy.loginByRole('admin') })
afterEach(() => { cy.clearCookies() })
after(() => { cy.resetDatabase() })
it('creates an order', () => { /* ... */ })
it('cancels an order', () => { /* ... */ })
})Anti-pattern: Never put application state setup inside
before()if you run tests in parallel — it will leak across files. UsebeforeEach()for per-test isolation.
10. Component Testing
Cypress can mount individual React, Vue, Angular, or Svelte components in isolation and test them against props, events, and rendered output.
Install the component adapter
npm install --save-dev @cypress/react @cypress/webpack-dev-serverWrite a component test
import Button from '../../src/components/Button'
describe('<Button />', () => {
it('renders the label', () => {
cy.mount(<Button label="Submit" />)
cy.getByRole('button').should('have.text', 'Submit')
})
it('fires onClick', () => {
const onClick = cy.stub()
cy.mount(<Button label="Submit" onClick={onClick} />)
cy.getByRole('button').click()
cy.wrap(onClick).should('have.been.calledOnce')
})
})Component tests run in milliseconds and are the new bottom of the test pyramid for front-end teams.
11. Page Object Model in Cypress
POM keeps locators in one place so a UI change touches one file, not fifty.
export class LoginPage {
visit() { cy.visit('/login') }
fillEmail(email) { cy.get('[data-testid="email"]').clear().type(email) }
fillPassword(password) { cy.get('[data-testid="password"]').clear().type(password) }
submit() { cy.get('[data-testid="submit"]').click() }
loginAs(email, password) {
this.visit()
this.fillEmail(email)
this.fillPassword(password)
this.submit()
}
}import { LoginPage } from '../support/pages/LoginPage'
const login = new LoginPage()
it('logs in', () => {
login.loginAs('admin@example.com', 'Sup3rSecret!')
cy.url().should('include', '/dashboard')
})12. Parallelization and the Cypress Dashboard
Once your suite grows past ~5 minutes, parallelize.
Run Cypress in parallel
cypress run --parallel --record --key $CYPRESS_RECORD_KEYSelf-hosted parallelism
Split specs across machines manually using --spec "cypress/e2e/a/**/*.cy.js" on one runner and --spec "cypress/e2e/b/**/*.cy.js" on another, or use the open-source cypress-split plugin.
Cypress Dashboard
The Cypress Dashboard provides test replay (video + DOM snapshots), flake detection, load balancing of specs across runners, and historical analytics. The free tier covers up to 500 test results per month.
Tip: Cypress's Smart Orchestration balances specs by historical runtime, so slow specs do not bottleneck. Use it when you have more than 4 CI workers.
13. Running Cypress in CI/CD
GitHub Actions
name: Cypress Tests
on: [push, pull_request]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- uses: cypress-io/github-action@v6
with:
browser: chrome
record: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}GitLab CI
cypress:
image: cypress/included:14.0.0
script:
- cypress run --browser chrome --record --key $CYPRESS_RECORD_KEY
artifacts:
when: always
paths:
- cypress/videos
- cypress/screenshots
expire_in: 7 daysJenkins
Use the official Cypress Jenkins plugin or call cypress run from a pipeline stage. Publish JUnit XML reports for trend graphs.
Heads up: Always cache
~/.cache/Cypressin CI to avoid re-downloading ~600 MB of browsers every run.
14. Debugging and Time-Travel
Time-travel
Open the Test Runner, hover any command in the command log — Cypress shows the DOM snapshot at that moment. Click to inspect the network request, console log, and cookies that were live then.
cy.pause and cy.debug
it('debugs a flow', () => {
cy.visit('/login')
cy.pause()
cy.get('[data-testid="email"]').type('admin@example.com')
cy.debug()
cy.get('[data-testid="submit"]').click()
})Console and logs
Use cy.log('arrived at dashboard') to add breadcrumbs that show in the runner.
Video and screenshots
Every headless run records a video and captures a screenshot on failure. They are stored in cypress/videos/ and cypress/screenshots/.
15. Cypress Best Practices for 2026
Do
- Use
data-testidattributes for selectors. - Test from the user's perspective, not implementation details.
- Stub network calls with
cy.interceptfor speed and reliability. - Use the Page Object Model for any non-trivial app.
- Run smoke on every PR, full suite nightly.
- Set a 4–6 second
defaultCommandTimeout— avoid silent 30s waits. - Use TypeScript for spec files to catch selector typos early.
Don't
- Don't use
cy.wait(2000)for synchronization. - Don't share state across tests via globals.
- Don't test the same flow in many specs — one canonical spec per journey.
- Don't rely on real third-party APIs in CI — stub them.
16. Migrating From Selenium to Cypress (When It Makes Sense)
You should migrate a Selenium suite to Cypress only when (a) your stack is JavaScript/TypeScript, (b) you primarily test in Chrome/Edge/Firefox/WebKit, and (c) the maintenance cost of the Selenium suite has grown faster than the value it delivers.
Phase 1 — Audit and freeze
Inventory every Selenium test. Tag each as keep, rewrite, drop. Anything that tests visual regression, complex file uploads, or native OS dialogs is usually a drop in Cypress.
Phase 2 — New tests in Cypress
All new end-to-end coverage goes into Cypress. Use POM in both suites temporarily to share business concepts.
Phase 3 — Decommission
Once coverage parity is reached (usually after 3–6 months), turn off the Selenium pipeline.
Common pitfall: Trying to convert Selenium tests 1:1 to Cypress tests. Cypress has different idioms — rewrite around Cypress's strengths.
17. The Cypress Ecosystem in 2026
| Tool | Purpose | When to use |
|---|---|---|
| Cypress Cloud (Dashboard) | Test Replay, flake analytics, smart orchestration | Teams with >5 CI workers and >200 specs |
| @cypress/react | Component testing for React | Replacing Jest + RTL for component-level E2E |
| @cypress/vue | Component testing for Vue 3 | Vue 3 component libraries |
| @cypress/angular | Component testing for Angular | Angular component libraries |
| cypress-real-events | Real native browser events (hover, swipe) | When built-in .click() does not trigger app handlers |
| @testing-library/cypress | Role and label locators | Always — huge accessibility win |
| cypress-axe | Accessibility assertions (WCAG) | Teams with a11y SLOs |
| cypress-image-diff | Visual regression | Replacing Percy or Chromatic for free-tier teams |
18. Cypress vs Playwright vs Selenium vs WebdriverIO (Honest 2026 Comparison)
| Dimension | Cypress | Playwright | Selenium | WebdriverIO |
|---|---|---|---|---|
| First install to first green test | ~5 minutes | ~10 minutes | ~30 minutes | ~20 minutes |
| Auto-waiting | Native | Native | Manual | Manual |
| Time-travel debugging | Yes | Yes (Trace Viewer) | No | No |
| Browser support | Chrome, Edge, Firefox, WebKit | Chrome, Edge, Firefox, WebKit + mobile | All browsers + mobile + legacy | All + mobile |
| Languages | JS / TS only | JS, TS, Python, Java, .NET | Java, C#, Python, Ruby, JS, Kotlin | JS, TS |
| Multi-tab | No | Yes | Yes | Yes |
| Component testing | First-class | Yes (Playwright CT) | No | No |
| Native mobile | No | Experimental | Via Appium | Via Appium |
| Best fit | JS/TS front-end teams | Polyglot, mobile + web | Enterprise, legacy, cross-language | Web + mobile via Appium |
2026 rule of thumb: pick Playwright for greenfield projects, pick Cypress for JS-only front-end teams that prize DX, pick Selenium when you need breadth (languages, browsers, mobile, legacy), pick WebdriverIO when you want Selenium ergonomics plus Appium in one runner.
19. Common Cypress Errors and How to Fix Them
1. Timed out after waiting 4000ms for your expected elements
Cause: Selector typo, wrong element, or element never renders. Fix: Run the test in headed mode, open DevTools, verify the selector. Add data-testid if brittle.
2. Cypress detected a cross-origin error
Cause: App navigates to a different origin mid-test (e.g., OAuth). Fix: Use cy.origin('https://oauth.provider.com', () => { ... }) or stub the redirect.
3. cy.visit() failed trying to load
Cause: Network failure, wrong URL, blocked by CORS, or auth redirect loop. Fix: Verify URL, check network tab, ensure auth cookies are set.
4. Cannot read property 'click' of undefined
Cause: cy.get(...) returned no element. Fix: Re-check selector; use cy.contains; add .should('exist') before the action.
5. cy.intercept() method mismatch
Cause: HTTP method mismatch in the stub. Fix: Match the actual method: cy.intercept('POST', '/api/orders', ...).
6. The test has finished but Cypress has not received any commands
Cause: Async operation without awaiting, or setTimeout instead of Cypress commands. Fix: Replace setTimeout with cy.wait; use .then() Promises.
7. Chromium failed to start
Cause: Docker / CI memory limits or missing libraries. Fix: Increase memory, install libgbm, libnss3, or run with --browser electron.
Next, level up your Cypress skills
- Playwright interview questions
- Selenium interview questions
- Software Testing Interview Questions (Master List)
- Manual Testing Complete Guide
Rehearse Cypress concepts live with the AI Mock Interview, polish your CV with the free Resume ATS Review, and join 11K+ testers in the QA Network for daily questions and referrals.
Frequently asked questions
Is Cypress better than Selenium?
It depends. Cypress wins on developer experience, speed, and built-in features for JavaScript teams. Selenium wins on language coverage (Java, C#, Python, Ruby, JavaScript), browser coverage (including Safari on real devices), and maturity in enterprise environments.
Does Cypress support Safari?
Yes — Cypress ships WebKit (Safari's engine). For real Safari on macOS or iOS, use a cloud grid such as BrowserStack or Sauce Labs.
Can Cypress test mobile apps?
Cypress tests web apps on mobile viewports and emulators via cy.viewport('iphone-x') and integrated device emulation. For native iOS or Android apps, use Appium.
How long does a Cypress test typically take?
A well-designed E2E test takes 1–5 seconds. Component tests run in 50–300 ms. A full suite of 200 E2E tests should finish in under 10 minutes when parallelized.
What's the difference between Cypress Studio and record-and-playback?
Cypress Studio lets you record interactions directly into a spec file and edit them — useful for prototyping. It is not recommended for production suites; hand-written POM-based specs are more maintainable.
Is Cypress parallel testing free?
Splitting specs manually with --spec or with the open-source cypress-split plugin is free. The Cypress Cloud orchestrated parallel mode is paid.
Should I learn Cypress in 2026?
Worth knowing if your team already uses it or you work on a JS/TS front-end. For a fresh learning bet across the broader job market, prioritize Playwright and Selenium.
Practice these questions
Rehearse Selenium and Playwright automation questions covering framework design, waits, locators and CI/CD.
Was this article helpful?
Keep building your QA edge
Pillar guidesContinue reading

Why Every QA Engineer Must Master CI/CD Pipelines in 2026 (Or Risk Obsolescence)
12 min read
Is Cypress Dead? Analyzing 2026 Playwright Market Share
12 min read
Why Tests Pass Locally But Fail in CI/CD (And the 6 Fixes That Actually Work in 2026)
13 min readJoin the QA Community
Connect with fellow testers, share job leads, and get career advice.
Stop Reinventing the Wheel. Upgrade Your QA Arsenal.
Take your testing skills from beginner to Lead Engineer. Supercharge your daily workflow with our premium digital resources.
- ⚡ Ready-to-use testing strategy templates
- 🔥 Advanced API & UI automation guides
- ⏱️ Save 10+ hours a week on test planning