Cypress is the most popular end-to-end testing framework with 47K+ GitHub stars — and its free API lets you test anything from React components to full user flows without Selenium.
Why Cypress Dominates E2E Testing
- No Selenium — runs directly in the browser, not through WebDriver
- Time travel — snapshots at each step, hover to see what happened
-
Automatic waiting — no
sleep(), no flaky selectors - Network stubbing — intercept and mock any API call
- Component testing — test React/Vue/Angular components in isolation
- Dashboard — free tier with test recordings and analytics
Quick Start
# Install
npm install -D cypress
# Open interactive runner
npx cypress open
# Run headless
npx cypress run
Your First E2E Test
// cypress/e2e/login.cy.js
describe("Login Flow", () => {
beforeEach(() => {
cy.visit("/login");
});
it("logs in with valid credentials", () => {
cy.get("[data-testid=email]").type("user@example.com");
cy.get("[data-testid=password]").type("password123");
cy.get("[data-testid=submit]").click();
// Automatic waiting — no sleep needed
cy.url().should("include", "/dashboard");
cy.get("[data-testid=welcome]").should("contain", "Welcome back");
});
it("shows error for invalid credentials", () => {
cy.get("[data-testid=email]").type("wrong@email.com");
cy.get("[data-testid=password]").type("wrongpassword");
cy.get("[data-testid=submit]").click();
cy.get("[data-testid=error]")
.should("be.visible")
.and("contain", "Invalid credentials");
});
});
Network Interception (The Killer Feature)
describe("API Mocking", () => {
it("displays products from API", () => {
// Intercept the API call and return mock data
cy.intercept("GET", "/api/products", {
statusCode: 200,
body: [
{ id: 1, name: "Widget", price: 9.99 },
{ id: 2, name: "Gadget", price: 19.99 },
],
}).as("getProducts");
cy.visit("/products");
cy.wait("@getProducts");
cy.get("[data-testid=product]").should("have.length", 2);
cy.get("[data-testid=product]").first().should("contain", "Widget");
});
it("handles API errors gracefully", () => {
cy.intercept("GET", "/api/products", {
statusCode: 500,
body: { error: "Internal Server Error" },
}).as("getProducts");
cy.visit("/products");
cy.wait("@getProducts");
cy.get("[data-testid=error-message]")
.should("be.visible")
.and("contain", "Something went wrong");
});
it("tests loading states", () => {
cy.intercept("GET", "/api/products", (req) => {
req.reply({
delay: 2000, // Simulate slow network
body: [{ id: 1, name: "Widget", price: 9.99 }],
});
}).as("getProducts");
cy.visit("/products");
cy.get("[data-testid=loading-spinner]").should("be.visible");
cy.wait("@getProducts");
cy.get("[data-testid=loading-spinner]").should("not.exist");
});
});
Component Testing (React Example)
// cypress/component/Button.cy.jsx
import Button from "../../src/components/Button";
describe("Button Component", () => {
it("renders with text", () => {
cy.mount(<Button>Click me</Button>);
cy.get("button").should("contain", "Click me");
});
it("calls onClick handler", () => {
const onClick = cy.spy().as("clickHandler");
cy.mount(<Button onClick={onClick}>Click me</Button>);
cy.get("button").click();
cy.get("@clickHandler").should("have.been.calledOnce");
});
it("renders variants", () => {
cy.mount(<Button variant="primary">Primary</Button>);
cy.get("button").should("have.class", "btn-primary");
cy.mount(<Button variant="danger">Danger</Button>);
cy.get("button").should("have.class", "btn-danger");
});
it("disables correctly", () => {
cy.mount(<Button disabled>Disabled</Button>);
cy.get("button").should("be.disabled");
});
});
Custom Commands (DRY Your Tests)
// cypress/support/commands.js
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("apiLogin", (email, password) => {
cy.request("POST", "/api/auth/login", { email, password })
.its("body.token")
.then((token) => {
window.localStorage.setItem("authToken", token);
});
});
// Usage in tests:
it("shows dashboard after login", () => {
cy.apiLogin("user@example.com", "password123"); // Fast, no UI
cy.visit("/dashboard");
cy.get("[data-testid=welcome]").should("be.visible");
});
Cypress vs Playwright vs Selenium
| Feature | Cypress | Playwright | Selenium |
|---|---|---|---|
| Setup | npm install |
npm install |
Driver + bindings |
| Speed | Fast | Fastest | Slowest |
| Auto-wait | Yes | Yes | No |
| Network mock | Built-in | Built-in | External tool |
| Component test | Yes | Yes | No |
| Browsers | Chrome, Firefox, Edge | Chrome, Firefox, Safari, Edge | All |
| Parallel | Dashboard (paid) | Built-in | Grid (complex) |
| Debugging | Time travel UI | Trace viewer | Screenshots |
Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.
Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.
Top comments (0)