Topic 1 of 120%
🌲 Complete Tutorial

Cypress
Automation Testing Tutorial

Master end-to-end web automation with Cypress — from installation to writing real tests, selectors, assertions, network interception, reports, and professional best practices.

⏱️ ~3 hrs 🎯 12 Topics 💻 Code examples 🧪 Quiz each section 🌐 JavaScript based
01
Introduction
What is Cypress?
Cypress is a free, open-source, JavaScript-based end-to-end (E2E) testing framework built specifically for modern web applications. It runs directly inside the browser — not outside like Selenium — giving it complete control over the application, network, DOM, and browser APIs in real time. It is released under the MIT License and is maintained by Cypress.io.
🧠 Simple analogy: Imagine you need to test your e-commerce website by clicking "Add to Cart," logging in, completing checkout, and verifying the order confirmation — all manually. Cypress is the robot that does all of this automatically, in seconds, in a real browser, and tells you exactly which step failed if something goes wrong.

Key facts about Cypress:

Created by
Cypress.io — first released publicly in 2017
License
Open source — MIT License. Free to use.
Language
JavaScript and TypeScript only (not Java, Python, etc.)
Runs on
Windows, Linux, macOS
Browsers
Chrome, Edge, Firefox, Electron (built-in). Safari not supported natively.
Architecture
Runs inside the browser — no WebDriver, no separate driver files needed
Best for
Front-end focused E2E testing, React/Angular/Vue apps, developer-friendly workflows

What Cypress can test:

  • End-to-End (E2E) Tests Full user flows — login, checkout, form submission — testing the entire application from browser to server.
  • Integration Tests How multiple modules or components work together — e.g. does the login module correctly update the header component?
  • Component Tests Test individual UI components in isolation (React, Angular, Vue) without running the full application.
  • API Tests Using cy.request() to test REST APIs directly — verify response status codes, body content, and headers.

Cypress key features that make it special:

  • 1
    Time Travel Cypress takes a snapshot at every test step. You can hover over any command in the Command Log and see exactly what the page looked like at that moment — like rewinding a video.
  • 2
    Automatic Waiting Cypress automatically waits for elements to appear, for animations to finish, and for network requests to complete before moving to the next step. No need to add manual waits or sleeps.
  • 3
    Real-time Reloads When you save a test file, Cypress instantly re-runs the tests. You see results live as you code — great for Test-Driven Development (TDD).
  • 4
    Network Control (cy.intercept) Intercept, modify, and stub any network request your app makes. Test error scenarios by faking API responses without touching the real server.
  • 5
    Built-in Debuggability Run tests with the browser's DevTools open. Use cy.pause() or debugger to step through tests interactively.
💡
Cypress App vs Cypress Cloud: The Cypress App is free and open-source — it's the local tool you install for writing and running tests. Cypress Cloud is an optional paid service that records test runs, shows analytics, enables parallel execution in CI, and provides test replay. You don't need Cypress Cloud to get started.
🧪 Quiz: What makes Cypress architecturally different from Selenium when executing tests?
02
Comparison
Cypress vs Playwright vs Selenium
Understanding how Cypress compares to the other popular automation tools helps you choose the right tool for the right job — and prepares you for interview questions about tool selection.
🌲 Cypress
Language:JavaScript / TypeScript only
Runs in:Inside the browser (same event loop)
Browsers:Chrome, Edge, Firefox, Electron
Auto-wait:✅ Built-in
Best for:Front-end JS/TS developers, React/Angular apps
🎭 Playwright
Language:JS/TS, Python, Java, .NET
Runs in:Outside browser via direct protocol
Browsers:Chromium, Firefox, WebKit (Safari)
Auto-wait:✅ Built-in
Best for:Multi-browser, multi-language, CI/CD pipelines
🔵 Selenium
Language:Java, Python, C#, Ruby, JS, PHP, Perl
Runs in:Outside browser via WebDriver
Browsers:Chrome, Firefox, Safari, Edge, IE
Auto-wait:❌ Manual waits required
Best for:Enterprise legacy apps, broader language support
Feature🌲 Cypress🎭 Playwright🔵 Selenium
LanguageJS / TypeScript onlyJS/TS, Python, Java, .NETJava, Python, C#, Ruby, JS, PHP
ArchitectureInside browser (same event loop)Outside browser (direct protocol)Outside browser (WebDriver)
Auto-wait✅ Built-in✅ Built-in❌ Manual waits needed
Safari / WebKit❌ Not supported✅ Supported✅ Supported
Multi-tab⚠️ Limited support✅ Native support⚠️ Via window handles
Network stubbing✅ cy.intercept() built-in✅ Built-in❌ No built-in
Time Travel debug✅ Snapshots in Test Runner✅ Trace ViewerScreenshots only
Component testing✅ Supported✅ Experimental❌ Not supported
Best use caseJS/TS projects, React/Vue/Angular front-endMulti-browser, multi-language, CI/CDLegacy enterprise, broader language needs
⚠️
Cypress limitation — no Safari support: Cypress does not support WebKit (Safari's engine). If your app needs to be tested on Safari, Playwright is the better choice. For pure Chrome/Firefox testing of JavaScript applications, Cypress excels.
ℹ️
Multi-tab limitation: Cypress has limited support for testing multiple browser tabs simultaneously. This is by design — Cypress discourages multi-tab tests and recommends testing each page in isolation. Playwright handles multiple tabs natively. Keep this in mind when choosing a tool.
🧪 Quiz: Your team uses Python for all automation. Can they use Cypress for testing?
03
Getting Started
Installation & Setup
Cypress is installed as an npm (Node Package Manager) package. You need Node.js version 18 or higher installed before you can install Cypress. The recommended way is to install it as a dev dependency in your project.

Prerequisites:

  • 1
    Node.js (v18 or higher) Download from nodejs.org. After installing, verify with: node --version
  • 2
    npm (comes with Node.js) Verify with: npm --version
  • 3
    VS Code (recommended) Best editor for Cypress. Install the Cypress extension for autocompletion and IntelliSense.

Step 1 — Create project folder and initialize npm:

mkdir my-cypress-project && cd my-cypress-project
npm init -y

Step 2 — Install Cypress as a dev dependency:

npm install cypress --save-dev

Step 3 — Open Cypress for the first time:

npx cypress open
📋 What happens when you run npx cypress open for the first time:
1. The Cypress Launchpad window opens
2. You choose E2E Testing (most common) or Component Testing
3. Cypress automatically creates all needed configuration files and folder structure
4. You choose your browser (Chrome, Firefox, or Electron)
5. Cypress creates a cypress.config.js file in your project root
6. The interactive Test Runner opens — you can now write and run tests!

Add scripts to package.json for easier running:

package.json
{
  "scripts": {
    "cy:open": "cypress open",    // opens interactive Test Runner
    "cy:run":  "cypress run"     // runs all tests headlessly (for CI)
  }
}
💡
Headless vs Headed mode:
npx cypress open — Opens the interactive Test Runner GUI. You can see the browser window, Command Log, and Time Travel snapshots. Best for local development and debugging.
npx cypress run — Runs all tests headlessly in the terminal without a visible browser. Faster. Best for CI/CD pipelines (GitHub Actions, Jenkins, etc.).
🧪 Quiz: Which command opens the Cypress interactive Test Runner where you can see the browser window and Command Log?
04
Project Setup
Project Structure & cypress.config.js
After running npx cypress open and completing the setup wizard, Cypress automatically creates a cypress/ folder with a standard structure. Understanding each folder and the configuration file is essential before writing tests.

Default Cypress folder structure:

my-cypress-project/ ├── cypress.config.js # Main config: baseUrl, timeouts, browser settings ├── package.json ├── cypress/ │ ├── e2e/ # YOUR TEST FILES GO HERE (.cy.js) │ │ └── login.cy.js │ ├── fixtures/ # Static test data (JSON files) │ │ └── users.json # e.g. test credentials, mock API data │ ├── support/ # Global setup and custom commands │ │ ├── commands.js # Define reusable custom cy.commands here │ │ └── e2e.js # Runs before every test file (global setup) │ └── downloads/ # Files downloaded during tests └── node_modules/

The cypress.config.js file — key settings explained:

cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',  // Root URL — use cy.visit('/login') instead of full URL
    specPattern: 'cypress/e2e/**/*.cy.{js,ts}', // Where Cypress looks for test files
    viewportWidth: 1280,             // Browser window width for tests
    viewportHeight: 720,            // Browser window height for tests
    defaultCommandTimeout: 10000,   // Max wait time for each command (10 seconds)
    pageLoadTimeout: 60000,         // Max wait time for page to load (60 seconds)
    video: true,                     // Record video of test runs (headless mode)
    screenshotOnRunFailure: true,   // Auto-capture screenshot when test fails
    retries: {
      runMode: 2,                   // Retry failed tests 2 times in CI (cypress run)
      openMode: 0                   // No retries in interactive mode (cypress open)
    }
  }
});
e2e/
All your test files go here. Every file must end with .cy.js or .cy.ts. Cypress automatically finds and runs them.
fixtures/
JSON files with test data. Load with cy.fixture('users.json'). Use instead of hardcoding test data in test files.
support/commands.js
Define custom Cypress commands here — e.g. cy.login() that encapsulates the full login flow.
support/e2e.js
Runs automatically before every test file. Use for global setup like importing commands or setting global variables.
💡
Important naming convention: Cypress test files must end with .cy.js (or .cy.ts for TypeScript). Files named login.spec.js or login.test.js will NOT be picked up automatically unless you change specPattern in the config.
🧪 Quiz: Where should you place your test files in a Cypress project?
05
Writing Tests
Your First Cypress Test
Cypress tests use Mocha syntax for structure — describe() to group tests and it() to declare individual test cases. Unlike Playwright, Cypress commands are NOT async/await — they are queued and run automatically in order. You do not use async or await in Cypress tests.

Anatomy of a Cypress test file:

cypress/e2e/login.cy.js
// describe() groups related tests — like a folder
describe('Login Page', () => {

  // beforeEach() runs before every it() in this describe
  beforeEach(() => {
    cy.visit('/login');  // uses baseUrl from cypress.config.js
  });

  // it() declares one test case
  it('should login with valid credentials', () => {
    // Step 1: Type email into the email field
    cy.get('[data-testid="email"]').type('[email protected]');

    // Step 2: Type password
    cy.get('[data-testid="password"]').type('Test@123');

    // Step 3: Click the Login button
    cy.get('[data-testid="login-btn"]').click();

    // Step 4: Verify we landed on the dashboard
    cy.url().should('include', '/dashboard');

    // Step 5: Verify welcome message is visible
    cy.contains('Welcome, Priya').should('be.visible');
  });

  it('should show error for wrong password', () => {
    cy.get('[data-testid="email"]').type('[email protected]');
    cy.get('[data-testid="password"]').type('WrongPass');
    cy.get('[data-testid="login-btn"]').click();
    cy.contains('Invalid credentials').should('be.visible');
  });

});
ℹ️
Key difference from Playwright — NO async/await in Cypress:
In Playwright you write: await page.click('button')
In Cypress you write: cy.get('button').click()

Cypress commands are not Promises — they are enqueued in an internal command queue and executed one by one automatically. Cypress handles all the async timing for you behind the scenes. Never add async/await to standard Cypress commands — it causes unexpected behavior.
describe()
Groups related tests together. The first argument is the group name (shown in the Test Runner). Equivalent to a test suite.
it()
Declares one individual test case. First argument = test name. Second = callback function containing the test steps.
cy.visit()
Navigates to a URL. Use relative paths like cy.visit('/login') when baseUrl is configured.
cy.get()
Finds a DOM element using a CSS selector. The most frequently used Cypress command.
cy.contains()
Finds an element containing specific text. Returns the element that contains the text.
🧪 Quiz: A developer writes a Cypress test using async/await: it('test', async () => { await cy.get('button').click(); }). What is wrong with this?
06
Core Concept
Selectors — Finding Elements
Cypress finds elements using CSS selectors via cy.get(), and by text content via cy.contains(). Unlike Playwright which recommends ARIA roles, Cypress officially recommends using data-* attributes (like data-testid, data-cy) as the most stable selector strategy.

Cypress selector strategies — best to worst:

⭐ Best: data-testid attribute ⭐ Best: data-cy attribute ✅ Good: cy.contains() by text ⚠️ Avoid: CSS class (.btn-primary) 🔴 Avoid: id (#submit) if dynamic
⭐ Recommended: data-testid attributes
Add a data-testid (or data-cy) attribute to HTML elements specifically for testing. These never change due to styling or refactoring — they exist only for tests. Ask developers to add them to important elements.
Recommended — data-testid selectors
// HTML: <input data-testid="email-input" />
cy.get('[data-testid="email-input"]').type('[email protected]');

// HTML: <button data-cy="login-btn">Login</button>
cy.get('[data-cy="login-btn"]').click();

// HTML: <div data-testid="error-msg">Invalid credentials</div>
cy.get('[data-testid="error-msg"]').should('be.visible');
✅ cy.contains() — Find by Text Content
Finds elements by their visible text. Great for buttons, links, labels, and error messages where you know the exact text that should appear.
cy.contains() examples
// Click a button with text "Submit"
cy.contains('Submit').click();

// Assert error message is visible
cy.contains('Invalid credentials').should('be.visible');

// Find a specific element type containing text
cy.contains('button', 'Login').click();  // only find <button> with text "Login"

// Using regex for partial/case-insensitive match
cy.contains(/welcome/i).should('be.visible');
⚠️ cy.get() with CSS selectors — Use with caution
CSS selectors work but can break easily when classes or IDs change. Use only when data-* attributes are not available.
CSS selectors — fallback options
// By stable ID (ok if ID never changes)
cy.get('#submit-button').click();

// By input type (fairly stable)
cy.get('input[type="email"]').type('[email protected]');

// By CSS class — FRAGILE, avoid if possible
cy.get('.btn-primary').click();  // breaks if class name changes

// Chaining: find within a parent element
cy.get('form').find('input[name="email"]').type('[email protected]');
⚠️
Cypress official best practice: Avoid selecting by CSS class names, element tag names, or IDs that may change. Instead, add data-testid or data-cy attributes to elements you need to test. This keeps your tests independent from styling changes.
🧪 Quiz: According to Cypress best practices, which selector is the MOST stable and recommended?
07
Interactions
Actions — Interacting With the Page
Actions are Cypress commands that interact with page elements — clicking buttons, typing text, selecting dropdown options, checking checkboxes, and more. All actions are chained onto a cy.get() or cy.contains() command that first locates the element.
Common Cypress Actions
// ── NAVIGATION ──────────────────────────────────
cy.visit('/login');            // Navigate to a page (relative path)
cy.visit('https://example.com'); // Absolute URL
cy.go('back');                // Browser back button
cy.reload();                   // Refresh the page

// ── CLICK ───────────────────────────────────────
cy.get('[data-testid="login-btn"]').click();
cy.contains('Submit').click();
cy.get('[data-testid="menu"]').dblclick();  // double click
cy.get('[data-testid="item"]').rightclick(); // right click

// ── TYPE (into input fields) ────────────────────
cy.get('[data-testid="email"]').type('[email protected]');
cy.get('[data-testid="search"]').type('laptop{enter}'); // type & press Enter

// ── CLEAR ───────────────────────────────────────
cy.get('[data-testid="email"]').clear();
cy.get('[data-testid="email"]').clear().type('[email protected]'); // clear then retype

// ── SELECT DROPDOWN ─────────────────────────────
cy.get('select[name="country"]').select('India');     // by visible text
cy.get('select[name="country"]').select('IN');        // by option value

// ── CHECKBOX / RADIO ────────────────────────────
cy.get('[data-testid="terms"]').check();          // check a checkbox
cy.get('[data-testid="terms"]').uncheck();        // uncheck a checkbox
cy.get('input[value="male"]').check();           // check a radio button

// ── HOVER ───────────────────────────────────────
cy.get('[data-testid="account-menu"]').trigger('mouseover');

// ── SCROLL ──────────────────────────────────────
cy.get('[data-testid="lazy-section"]').scrollIntoView();

// ── SCREENSHOT ──────────────────────────────────
cy.screenshot('login-page'); // saves to cypress/screenshots/
📝 Special key presses inside type()
Inside .type(), you can use special key codes in curly braces:

{enter} — press Enter key
{tab} — press Tab key
{backspace} — press Backspace
{selectall} — select all text
{del} — press Delete
{esc} — press Escape

Example: cy.get('[data-testid="search"]').type('laptop{enter}')
🧪 Quiz: Which Cypress command is used to first clear an input field and then type new text into it?
08
Verification
Assertions — Verifying with should()
Assertions in Cypress are made using .should() chained onto any command. Cypress uses the Chai assertion library — so the assertion strings come from Chai (like 'be.visible', 'have.text', 'include'). Cypress automatically retries assertions until they pass or the timeout is reached.
Essential Cypress Assertions
// ── VISIBILITY ──────────────────────────────────
cy.get('[data-testid="welcome-msg"]').should('be.visible');
cy.get('[data-testid="error"]').should('not.be.visible');

// ── URL ─────────────────────────────────────────
cy.url().should('include', '/dashboard');
cy.url().should('eq', 'http://localhost:3000/dashboard');

// ── TEXT CONTENT ────────────────────────────────
cy.get('h1').should('have.text', 'Dashboard');            // exact match
cy.get('[data-testid="cart"]').should('include.text', '3'); // partial match
cy.get('[data-testid="msg"]').should('contain', 'Success');

// ── INPUT VALUE ─────────────────────────────────
cy.get('[data-testid="email"]').should('have.value', '[email protected]');

// ── CSS CLASS ───────────────────────────────────
cy.get('[data-testid="tab"]').should('have.class', 'active');
cy.get('[data-testid="btn"]').should('not.have.class', 'disabled');

// ── ELEMENT STATE ───────────────────────────────
cy.get('[data-testid="submit"]').should('be.disabled');
cy.get('[data-testid="submit"]').should('be.enabled');
cy.get('[data-testid="terms"]').should('be.checked');
cy.get('[data-testid="terms"]').should('not.be.checked');
cy.get('[data-testid="field"]').should('be.empty');

// ── COUNT ───────────────────────────────────────
cy.get('li.product-item').should('have.length', 10);

// ── EXISTENCE IN DOM ────────────────────────────
cy.get('[data-testid="modal"]').should('exist');
cy.get('[data-testid="modal"]').should('not.exist');

// ── CHAIN MULTIPLE ASSERTIONS with .and() ───────
cy.get('[data-testid="link"]')
  .should('be.visible')
  .and('have.attr', 'href')
  .and('include', '/profile');
ℹ️
'have.text' vs 'include.text' vs 'contain':
'have.text', 'Dashboard' — exact full text match. Element text must be exactly "Dashboard".
'include.text', '3' — partial text match. Element text just needs to contain "3".
'contain', 'Success' — also partial match. Identical to include.text for most use cases.

Use exact match when you know the full text; use partial match when the text may have surrounding content (e.g., "Cart (3 items)").
🧪 Quiz: After a successful login, you want to check that the URL contains "/dashboard". Which assertion is correct?
09
Test Lifecycle
Hooks — before(), after(), beforeEach(), afterEach()
Hooks are functions that run at specific points in the test lifecycle — before tests start, after they finish, or before/after every individual test. They help avoid code duplication and keep tests clean and DRY (Don't Repeat Yourself).
before()
Runs once before ALL tests in the describe block. Use for one-time setup — e.g. seeding test database, creating a test user.
beforeEach()
Runs before every test in the describe block. Most commonly used — e.g. navigate to the page, log in, reset state.
afterEach()
Runs after every test. Use for cleanup — e.g. logout, clear cookies/localStorage, reset form.
after()
Runs once after ALL tests in the describe block. For global teardown — e.g. delete test data from the database.
Hooks example — Login feature tests
describe('Login Feature', () => {

  before(() => {
    // Runs ONCE before all tests in this describe block
    cy.log('Setting up test data...');
    // e.g. create a test user via API
  });

  beforeEach(() => {
    // Runs before EACH test — navigate to login page
    cy.visit('/login');
    cy.clearCookies();  // ensure fresh state
  });

  it('valid login redirects to dashboard', () => {
    cy.get('[data-testid="email"]').type('[email protected]');
    cy.get('[data-testid="password"]').type('Test@123');
    cy.get('[data-testid="login-btn"]').click();
    cy.url().should('include', '/dashboard');
  });

  it('empty fields show validation errors', () => {
    cy.get('[data-testid="login-btn"]').click();
    cy.contains('Email is required').should('be.visible');
  });

  afterEach(() => {
    // Runs after EACH test — clear session data
    cy.clearLocalStorage();
  });

  after(() => {
    // Runs ONCE after all tests — cleanup
    cy.log('All login tests complete. Cleaning up...');
  });

});
⚠️
Important Cypress best practice: Use beforeEach() to set up state — NOT before(). State from a before() hook can "bleed" into tests in unexpected ways. Each test should set up its own fresh state via beforeEach() to keep tests independent and reliable.
🧪 Quiz: You have 5 tests and all of them need to visit '/login' before running. What is the cleanest way to handle this?
10
Advanced Feature
Network Intercept — cy.intercept()
cy.intercept() is one of Cypress's most powerful features. It lets you intercept any HTTP network request that your application makes, and either: spy on it (observe without changing), stub it (replace with a fake response), or modify it (change request/response data). This lets you test error scenarios without needing the backend to return errors.
🧠 Simple analogy: Imagine your app calls an API to get the list of products. Normally the API returns 20 products. With cy.intercept(), you can "intercept" that API call and replace the response with your own fake data — maybe 3 products, or an empty list, or an error. Your app gets the fake response and you can test how it handles different scenarios — all without touching the real server.
cy.intercept() — spy, stub, and modify
// ── SPY — observe a real request without changing it ──
cy.intercept('GET', '/api/products').as('getProducts');
cy.visit('/products');
cy.wait('@getProducts');  // wait for request to finish
cy.get('[data-testid="product-list"]').should('be.visible');

// ── STUB — replace real API with fake response ────────
cy.intercept('GET', '/api/products', {
  statusCode: 200,
  body: [
    { id: 1, name: 'Laptop', price: 50000 },
    { id: 2, name: 'Phone',  price: 30000 }
  ]
}).as('stubbedProducts');
cy.visit('/products');
cy.wait('@stubbedProducts');
cy.get('li.product').should('have.length', 2);

// ── STUB ERROR — test how app handles API failure ────
cy.intercept('GET', '/api/products', {
  statusCode: 500,
  body: { error: 'Internal Server Error' }
}).as('productError');
cy.visit('/products');
cy.wait('@productError');
cy.contains('Something went wrong').should('be.visible');

// ── INTERCEPT POST (login API) ───────────────────────
cy.intercept('POST', '/api/login').as('loginRequest');
cy.get('[data-testid="login-btn"]').click();
cy.wait('@loginRequest').its('response.statusCode').should('eq', 200);
.as('name')
Creates an alias for the intercepted route. Required to use cy.wait() with it. Call it with @name.
cy.wait('@alias')
Pauses the test until the intercepted request completes. Prevents tests from moving forward before the API responds.
statusCode
The HTTP status code to return in the fake response. Use 200 for success, 404 for not found, 500 for server error.
body
The fake response body. Can be a JSON object, array, or string. Your app receives this instead of the real API response.
💡
Why use cy.intercept() for testing?
1. Test error handling without breaking the real API
2. Make tests faster — no real network calls
3. Test edge cases that are hard to trigger in real APIs (500 errors, empty responses, slow responses)
4. Keep tests deterministic — same fake data every run, no dependency on external services
🧪 Quiz: You want to test how your app displays an error message when the products API returns a 500 error. Which approach should you use?
11
Execution
Running Tests & Viewing Reports
Cypress provides two ways to run tests: the interactive Test Runner (visual, with browser) and headless mode (terminal, no visible browser). After each headless run, Cypress generates screenshots and videos automatically.

Essential CLI commands:

Open Test Runner
npx cypress open — interactive GUI, see browser live, Time Travel, Command Log
Run all tests
npx cypress run — headless, all spec files, generates video + screenshots
Run specific file
npx cypress run --spec "cypress/e2e/login.cy.js"
Run on Chrome
npx cypress run --browser chrome
Run on Firefox
npx cypress run --browser firefox
Headed (visible browser)
npx cypress run --headed — runs in terminal but shows browser window
No video
npx cypress run --config video=false — skip video recording for faster runs
🎮 Cypress Interactive Test Runner — what you see:
When you run npx cypress open, the Test Runner has two panels:

Left panel — Command Log: Every Cypress command listed in order — cy.visit, cy.get, cy.click, .should, etc. Click any command to see the Time Travel snapshot of the page at that moment.

Right panel — App Preview: The actual browser rendering your application as the test runs live. You can see every click, every form fill, every page navigation happening in real time.

🕰️ Time Travel: Hover over any command in the Command Log → the app preview jumps back to show exactly what the page looked like at that step. This is Cypress's most powerful debugging feature.
📁 Output files after npx cypress run:
cypress/screenshots/ — PNG screenshots captured automatically when a test fails. File name includes the test name and timestamp.

cypress/videos/ — MP4 video recording of the entire test run for each spec file. Watch the video to see exactly what happened step by step during a CI failure.

These files are generated by default. To disable: set video: false and screenshotOnRunFailure: false in cypress.config.js.
💡
Mochawesome — prettier HTML reports: By default Cypress generates basic terminal output. For a detailed HTML report, install the mochawesome reporter: npm install mochawesome --save-dev, then add reporter config in cypress.config.js. The HTML report shows pass/fail per test, duration, error messages, and screenshots in a clean visual format.
🧪 Quiz: Your tests are failing in CI but passing locally. You want to see exactly what happened step by step in CI. Which Cypress output should you check?
12
Professional Skills
Cypress Best Practices

These are official Cypress recommendations and industry-standard practices followed by professional QA automation engineers. Apply them consistently to build a stable, maintainable test suite.

  1. 1
    Use data-testid attributes as selectors — avoid CSS classes and IDs Ask developers to add data-testid attributes to all testable elements. These never break due to styling changes. Never use .btn-primary or #submit as selectors if they change with redesigns.
  2. 2
    Never use cy.wait(number) — it is an anti-pattern cy.wait(2000) is a hardcoded sleep. It makes tests slow when things are fast and still fails when they are slow. Instead use cy.wait('@alias') with route aliases, or rely on Cypress auto-waiting via assertions.
  3. 3
    Tests must be independent — no test should depend on another test's state Each test must set up and tear down its own state. Tests that only pass when run in a specific order are fragile and misleading. Use beforeEach() for fresh setup before every test.
  4. 4
    Use beforeEach() for setup — not before() State from before() can persist between tests in unintended ways. beforeEach() guarantees every test starts clean. This is Cypress's official recommendation.
  5. 5
    Write descriptive test names in it() Name tests like sentences: 'should show error message when email field is empty'. When a test fails, the name tells you exactly what broke without reading the code. Avoid names like 'test1' or 'login test'.
  6. 6
    Use cy.intercept() to control network requests in tests Don't rely on a live backend for all test scenarios. Use cy.intercept() to stub error responses (404, 500), test loading states, and keep tests fast and reliable without hitting real APIs.
  7. 7
    Use fixtures for test data — not hardcoded values Store test data (credentials, form data, API mock responses) in JSON files inside cypress/fixtures/. Load with cy.fixture('users.json'). This separates data from test logic and makes data changes easy.
  8. 8
    Create custom commands for repeated actions If you write the same 5-line login flow in every test, extract it to a custom command in cypress/support/commands.js: Cypress.Commands.add('login', (email, password) => {...}). Then every test can use cy.login('[email protected]', 'pass').
  9. 9
    Test both positive and negative paths For every form or feature, write tests for valid input (happy path) AND invalid input (error handling). Tests that only check the happy path miss the most common bugs in validation logic.
  10. 10
    Configure retries in CI to handle flakiness Set retries: { runMode: 2 } in cypress.config.js so that tests that fail due to minor flakiness are retried twice in CI before marking as failed. This reduces false failures without hiding real bugs.
🎯
Interview-ready answer — "Walk me through how you would automate a login test in Cypress:"

"I start by creating a file cypress/e2e/login.cy.js. I use describe() to group all login tests and beforeEach() to navigate to the login page before each test. I use cy.get('[data-testid]') selectors for stability. I write one positive test — valid credentials redirect to dashboard — verified with cy.url().should('include', '/dashboard'). I write negative tests for wrong password, empty fields, and locked account. For the wrong password test I use cy.intercept() to stub a 401 response and verify the error message appears. I use cy.screenshot() for any tricky assertions. I run the suite with npx cypress run in CI and check the video on any failure."
🧪 Final Quiz: Your login flow (visit page, type email, type password, click login) is repeated in 8 different test files. What is the best way to handle this duplication in Cypress?

Ready to Master Cypress in Real Projects?

STAD Solution's QA Automation course covers Cypress end-to-end with hands-on practice, CI/CD integration, real app testing, and 100% placement support.

Explore Courses at STAD Solution →