Cypress is a JavaScript end-to-end testing framework built specifically for the modern web. Unlike Selenium-based tools, Cypress runs directly inside the browser โ not outside it. That architectural decision changes everything about how you write and debug tests.
What Makes Cypress Different
Most testing tools work by sending commands to the browser over a protocol (WebDriver). Cypress takes a different approach: it runs in the same browser process as your application.
This means:
- No async headaches โ Cypress automatically waits for elements, no
sleep()calls needed - Real-time reloading โ tests re-run as you edit them
- Time travel debugging โ hover over any step in the test runner to see a screenshot of the DOM at that moment
- Direct access to the browser โ you can stub network requests, control the clock, and access application code
When to Use Cypress
Cypress is excellent for:
- Frontend-heavy applications โ React, Vue, Angular, anything with a JavaScript frontend
- Fast feedback loops โ the interactive test runner is best-in-class for debugging
- Teams new to testing โ the learning curve is gentler than Selenium/WebdriverIO
- Component testing โ Cypress can test React/Vue/Angular components in isolation
Cypress has limitations too:
- JavaScript/TypeScript only โ no Python, Java, or C# support
- Single domain per test โ you can't navigate between different origins in one test (by default)
- No native mobile support โ it's a browser tool only
- Slower than Playwright for large test suites
If you need multi-browser support, mobile testing, or cross-origin workflows, consider Playwright. For single-page applications with a JavaScript codebase, Cypress is a great choice.
Core Concepts
Commands Are Asynchronous (but feel synchronous)
Cypress commands don't return values โ they yield subjects to the next command:
cy.get('#username') // finds the element
.type('standard_user') // types into it
.should('have.value', 'standard_user') // asserts on it
You never use await. Cypress handles the async nature internally โ each command waits for the previous one to complete.
Automatic Waiting
This is Cypress's biggest practical advantage. By default, Cypress waits up to 4 seconds for:
- An element to exist in the DOM
- An element to become visible
- An element to become enabled
- An assertion to pass
// Cypress waits for the button to appear โ no need for explicit waits
cy.get('[data-testid="submit-btn"]').click()
// Waits for the URL to change
cy.url().should('include', '/dashboard')
This eliminates most flaky test issues that plague Selenium-based setups.
Chainability
Every Cypress command chains off the previous one. The "subject" (the thing you're working with) flows through the chain:
cy.get('ul.products') // subject: the <ul> element
.children() // subject: all <li> children
.first() // subject: first <li>
.find('h2') // subject: <h2> inside that <li>
.should('contain', 'Backpack') // assertion on the <h2>
The Cypress Test Runner
When you run npx cypress open, you get an interactive UI with:
- Spec list โ all your test files
- Command log โ every command executed, with timestamps
- App preview โ the actual browser running your tests
- Time travel โ click any command to "snapshot" the DOM at that point
This is what makes debugging Cypress tests fast. You see exactly what happened, when, and what the DOM looked like at every step.
What We'll Build
In this tutorial, we'll test SauceDemo โ a practice e-commerce site built for automation testing. We'll cover:
- Installation & Setup โ installing Cypress, configuring it, writing your first test
- Selectors & Commands โ finding elements, interacting with them, handling common scenarios
- Assertions โ making your tests actually assert something meaningful
- Page Object Model โ organising tests so they don't fall apart at scale
- Network Interception โ stubbing API calls for faster, more reliable tests
- CI/CD โ running Cypress in GitHub Actions
SauceDemo credentials you'll use throughout:
- Username:
standard_user - Password:
secret_sauce
Let's get started.