Cypress E2E Testing Tutorial for QA Automation Engineers

⚡ Cypress Tutorial
Cypress Introduction
Last Updated: April 2026  |  TechWorld Labs

Cypress is a next-generation front-end testing tool built for the modern web. It runs directly in the browser and makes end-to-end testing fast, reliable, and easy to debug.

Tutorial
Programs
Interview Q&A

What is Cypress?

Cypress is a modern, JavaScript-based end-to-end (E2E) testing framework designed from the ground up to address the pain points that plague traditional automation tools like Selenium. Unlike Selenium, which runs as a separate process outside the browser and communicates via HTTP protocols, Cypress runs directly inside your browser in the same execution loop as your application code.

This architectural difference is fundamental and game-changing. When Cypress executes a command like cy.click(), it's not sending a remote HTTP request to an external driver that then simulates a click. Instead, it's running directly in your application's JavaScript context, giving it complete access to your DOM, your JavaScript objects, and your network layer. This level of access eliminates the timing issues, race conditions, and reliability problems that plague Selenium-based tests.

Why Cypress Matters for Modern QA Teams

The testing landscape has changed dramatically. Modern web applications are increasingly JavaScript-heavy, with complex state management, real-time updates, and asynchronous operations. Traditional testing tools struggle with this reality because they rely on external communication and don't understand the internal state of your application. Teams spend more time fixing brittle, flaky tests than actually testing features.

Cypress solves this by giving you direct access to your application's internals. When a test fails, you don't just see a screenshot—you can step back through your test execution using time-travel debugging and see exactly what happened at each step. Network requests are under your control via cy.intercept(), which lets you stub responses and test edge cases without hitting real APIs. This results in tests that are faster, more reliable, and easier to debug.

Fast Execution
Runs inside the browser — no network overhead. Tests complete in seconds instead of minutes.
🔍
Time-Travel Debugging
Step back through your test execution to see exactly what happened at each step.
📸
Automatic Screenshots & Video
Auto-captures on failure for easy debugging and root cause analysis.
🕸️
Network Control
Stub, spy on, and intercept network requests without hitting real APIs.

Who Should Use Cypress?

Cypress is ideal for: QA engineers and developers testing modern web applications built with React, Vue, Angular, or vanilla JavaScript. It's particularly powerful for teams that want reliable, maintainable test suites without the debugging headaches of traditional tools.

When to consider Selenium instead: If you need to test multiple browsers simultaneously (Cypress currently supports Chrome, Firefox, Edge, and Electron), or if you're testing legacy applications with minimal JavaScript, Selenium might be more suitable. Cypress also cannot test non-web applications (mobile apps, desktop apps), so explore Appium or other tools for those scenarios.

Cypress vs Selenium

FeatureCypressSelenium
LanguageJavaScript / TypeScriptJava, Python, C#, JS…
SetupVery easy (npm install)Needs WebDriver setup
SpeedFast (in-browser)Moderate (out-of-browser)
DebuggingExcellent (time-travel)Good (logs/screenshots)
API TestingBuilt-in cy.request()Not built-in
Browser SupportChrome, Firefox, Edge, ElectronAll major browsers
💡
Key fact: Cypress uses Mocha as its test runner and Chai for assertions out of the box — zero config needed.

Basic Cypress Test Structure

JAVASCRIPT — cypress/e2e/sample.cy.js
describe('TechWorld Labs Login Test', () => {

  beforeEach(() => {
    cy.visit('https://example.com/login')
  })

  it('should login with valid credentials', () => {
    cy.get('#email').type('[email protected]')
    cy.get('#password').type('securePass123')
    cy.get('button[type="submit"]').click()
    cy.url().should('include', '/dashboard')
    cy.contains('Welcome').should('be.visible')
  })

  it('should show error for wrong password', () => {
    cy.get('#password').type('wrongpass')
    cy.get('button[type="submit"]').click()
    cy.get('.error-msg').should('contain', 'Invalid credentials')
  })
})

Top Interview Questions — Cypress

Q1

What is Cypress and how does it differ from Selenium?

Cypress runs inside the browser in the same JS loop as your app. Selenium uses a WebDriver that communicates over HTTP from outside the browser.

Q2

What is cy.intercept() used for?

cy.intercept() stubs, spies on, or mocks network requests — making tests deterministic and fast without hitting real APIs.

Q3

Explain the Cypress retry mechanism.

Cypress automatically retries commands like cy.get() and assertions until they pass or the timeout (default 4s) expires.

Q4

What is the difference between cy.get() and cy.find()?

cy.get() queries the entire DOM. cy.find() queries within a previously yielded element — it's a scoped child selector.

🛠️
⚡ Cypress Tutorial
Installation & Setup
April 2026

Get Cypress installed and running in minutes with Node.js and npm.

Prerequisites

Before installing Cypress, ensure you have the following tools set up on your machine. The good news is that most developers already have these installed.

🟩
Node.js 16+
Download from nodejs.org — Cypress requires Node.js to run on your machine.
📦
npm or yarn
Bundled automatically when you install Node.js — used to manage dependencies.
💻
VS Code (Recommended)
Optional but recommended. Install the Cypress VS Code extension for better IntelliSense.

To check if Node.js is installed, open your terminal and run node -v and npm -v. You should see version numbers (e.g., v16.13.0) for both commands.

Step-by-Step Installation & Setup

Setting up Cypress is remarkably straightforward compared to Selenium. The installation process takes just a few minutes, and you'll be ready to write your first test immediately.

1

Create project folder

Open your terminal (Command Prompt, PowerShell, or Bash) and create a new directory for your test project. This will contain all your test files, configuration, and dependencies.

2

Initialize npm

Navigate into your project folder and run npm init -y to automatically create a package.json file. This file tracks your project dependencies and npm scripts.

3

Install Cypress

Run npm install cypress --save-dev to install Cypress as a development dependency. This downloads the entire Cypress application (~1-2 GB) to your node_modules folder. The first installation may take a few minutes.

4

Open Cypress for the first time

Run npx cypress open to launch the Cypress Launchpad. This interactive GUI lets you choose between E2E and Component testing, select a browser, and create your first test file.

TERMINAL
# Create project
mkdir my-cypress-project && cd my-cypress-project

# Init npm
npm init -y

# Install Cypress
npm install cypress --save-dev

# Open Cypress GUI
npx cypress open

# Run headlessly (CI)
npx cypress run
Success! After running npx cypress open, the Launchpad appears. Choose E2E Testing to begin.
🧪
⚡ Cypress Tutorial
Your First Test
April 2026

Write and run your very first Cypress test — from scratch to a green check.

Writing Your First Test

Cypress tests are written in JavaScript using a familiar syntax borrowed from Mocha and Chai testing frameworks. This means if you're already comfortable with JavaScript, you'll pick up Cypress testing almost immediately—no new language to learn.

By convention, test files live in the cypress/e2e/ directory and must end with .cy.js or .cy.ts (if using TypeScript). The .cy extension tells Cypress to treat this file as a test file. When you run npx cypress run, Cypress automatically discovers all files matching this pattern and executes them.

Tests are organized using two core functions: describe() for grouping related tests (test suites), and it() for individual test cases. This hierarchical structure makes it easy to organize large test suites and understand what each test is checking.

JAVASCRIPT — cypress/e2e/first-test.cy.js
describe('My First Cypress Test', () => {
  it('Visits TechWorld Labs homepage', () => {
    cy.visit('https://thetechworldlabs.com')
    cy.title().should('include', 'TechWorld Labs')
    cy.get('nav').should('be.visible')
    cy.contains('Tutorials').click()
    cy.url().should('include', '/tutorials')
  })
})

Test Anatomy

1

describe()

Groups related test cases. Acts as the test suite — can be nested.

2

it()

Defines one test case. Each it() tests a single specific behaviour.

3

cy.visit()

Navigates the browser to a URL — almost always the first command.

4

.should()

Asserts expected state. Cypress retries until it passes or times out.

📁
⚡ Cypress Tutorial
Folder Structure
April 2026

Understand how Cypress organises your test files, fixtures, commands, and configuration.

FOLDER STRUCTURE
my-project/
├── cypress/
│   ├── e2e/          # Test files (.cy.js)
│   ├── fixtures/     # Test data (JSON files)
│   ├── support/
│   │   ├── commands.js   # Custom commands
│   │   └── e2e.js        # Global setup / imports
│   └── downloads/    # Files downloaded during tests
├── cypress.config.js # Configuration (baseUrl, timeouts…)
└── package.json
💡
cypress.config.js is where you set baseUrl, viewportWidth, defaultCommandTimeout, and environment variables.
🎯
⚡ Cypress Tutorial
Selectors & Queries
April 2026

Reliably find elements using cy.get(), cy.contains(), cy.find(), and best-practice selector strategies.

Querying Elements in Cypress

Selecting HTML elements is the foundation of any test. Cypress provides multiple ways to find elements, each suited to different scenarios. Understanding when to use each method will help you write stable, maintainable tests that don't break when your UI changes.

MethodBest Used ForExample
cy.get()CSS selectors, IDs, or data attributescy.get('#submit') or cy.get('[data-cy="btn"]')
cy.contains()Finding by visible text (useful for buttons, links)cy.contains('Login') or cy.contains('Save Changes')
cy.find()Finding child elements within a parentcy.get('table').find('tr')
cy.first()Selecting the first element from a listcy.get('li').first()
cy.eq(n)Selecting a specific element by indexcy.get('li').eq(2) (gets the 3rd item)

Why data-cy Attributes Matter

Beginners often make the mistake of using fragile selectors like class names or element types. For example, cy.get('.btn') might work today, but if your designer adds a new button and reorders the CSS, your test breaks even though the actual functionality hasn't changed.

The professional approach is to use dedicated data-cy attributes specifically for testing. These act as a contract between your test and your application: "As long as this element has data-cy="submit-btn", the test will work." This decouples your tests from implementation details like styling, making them more resilient to refactoring.

💡
Best practice: Always use data-cy="..." attributes for test selectors. They don't change when you refactor CSS or restructure your DOM, keeping your tests stable through iterations.
JAVASCRIPT
// Preferred — data-cy attribute
cy.get('[data-cy="login-btn"]').click()

// Chaining selectors
cy.get('.form-group')
  .find('input[type="email"]')
  .type('[email protected]')
⚡ Cypress Tutorial
Assertions
April 2026

Assertions validate expected outcomes. Cypress uses Chai assertions internally — with automatic retry until the condition passes.

AssertionChecks
.should('be.visible')Element is visible on page
.should('exist')Element exists in DOM
.should('have.text', '…')Exact text content
.should('contain', '…')Partial text match
.should('have.value', '…')Input field value
.should('have.class', '…')CSS class is present
.should('be.disabled')Element is disabled
.should('have.length', n)Number of matched elements
JAVASCRIPT — multiple assertions
cy.get('.alert')
  .should('be.visible')
  .and('have.class', 'alert-success')
  .and('contain', 'Saved successfully')
🖱️
⚡ Cypress Tutorial
Actions & Interactions
April 2026

Simulate every user gesture — clicks, keyboard input, dropdowns, drag-and-drop, hover, and more — using Cypress action commands.

Click Actions

CommandDescription
.click()Single left click
.dblclick()Double click
.rightclick()Right-click (context menu)
.click({ force: true })Click even if element is hidden or overlapped
.click({ multiple: true })Click all matched elements
.click('topRight')Click a specific position on the element

Keyboard & Input

JAVASCRIPT — type, clear, special keys
// Type text
cy.get('#search').type('cypress automation')

// Special key sequences
cy.get('#name').type('Hello{selectAll}{del}')
cy.get('input').type('{ctrl}a').type('{backspace}')
cy.get('form input').type('admin{enter}')   // submit on enter
cy.get('#email').type('{tab}')             // move focus to next field

// Clear and type slowly (like a real user)
cy.get('#amount').clear().type('99.99', { delay: 50 })

Forms: Checkbox, Radio & Select

JAVASCRIPT — form element interactions
// Checkbox
cy.get('#agree-terms').check()
cy.get('#newsletter').uncheck()
cy.get('#agree-terms').should('be.checked')

// Radio button
cy.get('input[type="radio"][value="monthly"]').check()

// Native <select> dropdown
cy.get('select#country').select('Nepal')
cy.get('select#plan').select('pro')  // by value
cy.get('select#size').select(['S', 'M'])  // multi-select

Hover, Drag & Scroll

JAVASCRIPT — hover, drag-drop, scroll
// Hover (trigger mouseover event)
cy.get('.tooltip-trigger').trigger('mouseover')
cy.get('.tooltip').should('be.visible')

// Real hover (install cypress-real-events)
// import 'cypress-real-events' in e2e.js
cy.get('.dropdown-menu').realHover()

// Drag and drop using cypress-real-events
cy.get('[data-cy="drag-item"]').realMouseDown()
cy.get('[data-cy="drop-zone"]').realMouseMove(0, 0).realMouseUp()

// Scroll commands
cy.scrollTo('bottom')
cy.get('#footer').scrollIntoView()
cy.scrollTo(0, 500)
⚙️
⚡ Cypress Tutorial
Custom Commands
April 2026

Reduce code duplication by creating reusable commands in cypress/support/commands.js.

JAVASCRIPT — cypress/support/commands.js
// Define a reusable login command
Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login')
  cy.get('#email').type(email)
  cy.get('#password').type(password)
  cy.get('button[type="submit"]').click()
})

// Use it in any test file
cy.login('[email protected]', 'pass123')
🔗
⚡ Cypress Tutorial
Hooks (before / after)
April 2026

Use hooks to set up and tear down state around your tests — keeping each test isolated and clean.

HookWhen it runsCommon use
before()Once before all tests in describeOne-time DB seed, login
beforeEach()Before every it()Navigate to page, set state
afterEach()After every it()Clear cookies, take screenshot on fail
after()Once after all testsDB cleanup, logout
JAVASCRIPT
describe('Dashboard Tests', () => {
  before(() => { cy.login('[email protected]', 'pass') })
  beforeEach(() => { cy.visit('/dashboard') })
  afterEach(() => { cy.clearCookies() })
  after(() => { cy.request('POST', '/api/cleanup') })
})
⏱️
⚡ Cypress Tutorial
Waiting Strategies
April 2026

Master deterministic waiting — use network aliases, assertion retries, and timeout tuning instead of hardcoded sleeps.

Understanding Cypress Waiting & Retry Logic

One of Cypress's most powerful features is its built-in retry mechanism. Unlike Selenium, where you often write explicit waits like WebDriverWait(driver, 10).until(...), Cypress is intelligent about waiting. It understands that web applications are asynchronous and that elements might not be available immediately.

When you run a Cypress command like cy.get('.user-name'), Cypress doesn't just look for the element once and fail if it doesn't exist. Instead, it continuously retries until the element appears or the timeout (default 4 seconds) expires. This automatic retry mechanism eliminates the vast majority of flaky tests caused by timing issues.

However, not all waits are automatic. Some operations require explicit strategies: waiting for network requests to complete, waiting for data to load, or waiting for animations to finish. Cypress provides specific methods for each scenario, and choosing the right one is key to writing stable tests.

How Cypress Waits (Built-in Retry)

Cypress automatically retries most commands and assertions until they pass or the defaultCommandTimeout (4 seconds by default) expires. You rarely need explicit waits for DOM state changes.

StrategyWhen to useExample
Assertion retryWaiting for DOM statecy.get('.spinner').should('not.exist')
Network alias waitAfter a page action triggers an XHRcy.wait('@getUsers')
Timeout overrideSlow operations (file generation, reports)cy.get('.pdf-ready', { timeout: 15000 })
cy.wait(ms)Last resort — external delay you can't observecy.wait(1000)
JAVASCRIPT — waiting for network calls
// Define intercept alias before action
cy.intercept('POST', '/api/checkout').as('checkout')
cy.get('[data-cy="buy-btn"]').click()

// Wait until the checkout request completes
cy.wait('@checkout').then((interception) => {
  expect(interception.response.statusCode).to.eq(200)
})
cy.get('.order-confirmation').should('be.visible')
JAVASCRIPT — waiting for DOM transitions
// Wait for loading spinner to disappear
cy.get('.loading-overlay').should('not.exist')

// Wait for element to become enabled
cy.get('button[type="submit"]').should('not.be.disabled').click()

// Extend timeout for a specific command only
cy.get('[data-cy="report-table"]', { timeout: 20000 })
  .should('have.length.greaterThan', 0)

// Increase global timeout in cypress.config.js
// defaultCommandTimeout: 10000
💡
Golden rule: Never use cy.wait(ms) for elements or network calls — it makes tests slow and brittle. Reserve it only for true external delays (e.g. a third-party email service that takes 1–2 s to deliver).
📦
⚡ Cypress Tutorial
Fixtures & Test Data
April 2026

Store external test data in JSON fixture files and load them in tests — keeps data out of test logic.

JSON — cypress/fixtures/user.json
{
  "email":    "[email protected]",
  "password": "test1234",
  "role":     "admin"
}
JAVASCRIPT — using fixtures
cy.fixture('user').then((user) => {
  cy.get('#email').type(user.email)
  cy.get('#password').type(user.password)
  cy.get('button[type="submit"]').click()
})
🌐
⚡ Cypress Tutorial
API Testing with Cypress
April 2026

Use cy.request() to make HTTP calls and cy.intercept() to stub/spy on network traffic directly in your E2E tests.

JAVASCRIPT — cy.request()
cy.request({
  method: 'POST',
  url:    'https://api.example.com/users',
  body:   { name: 'Suraj', role: 'QA' }
}).then((response) => {
  expect(response.status).to.eq(201)
  expect(response.body.name).to.eq('Suraj')
})
JAVASCRIPT — cy.intercept()
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts')
cy.visit('/products')
cy.wait('@getProducts')
cy.get('.product-card').should('have.length', 3)
🏗️
⚡ Cypress Tutorial
Page Object Model (POM)
April 2026

POM separates page interactions from test logic for maintainability. Each page/component gets its own class with selectors and action methods.

Why Page Object Model Matters

Imagine you're testing a login page with 50 different test cases. The login form has an email input, password input, and a submit button. Your tests reference these selectors repeatedly:

cy.get('#email'), cy.get('#password'), cy.get('button[type="submit"]')

Now, your developer refactors the form and changes the ID from #email to #user-email. Suddenly, all 50 tests break. You have to go through and update the selector in every single test. This is a maintenance nightmare.

The Page Object Model solves this by centralizing all selectors and actions for a page into a single class. You have one place to update when the UI changes. Your tests become cleaner, more readable, and dramatically easier to maintain.

JAVASCRIPT — cypress/topics/LoginPage.js
class LoginPage {
  visit()             { cy.visit('/login') }
  enterEmail(email)   { cy.get('#email').type(email) }
  enterPassword(pass) { cy.get('#password').type(pass) }
  submit()            { cy.get('[type="submit"]').click() }

  login(email, pass) {
    this.visit()
    this.enterEmail(email)
    this.enterPassword(pass)
    this.submit()
  }
}
export default new LoginPage()
JAVASCRIPT — test file using POM
import loginPage from '../topics/LoginPage'

it('logs in successfully', () => {
  loginPage.login('[email protected]', 'pass123')
  cy.url().should('include', '/dashboard')
})
🕸️
⚡ Cypress Tutorial
Network Interception
April 2026

cy.intercept() lets you spy on, stub, or modify any network request made by the app — the most powerful tool for controlling test state.

Spy vs Stub vs Alias

PatternWhat it doesExample
SpyObserve a request, let it pass throughcy.intercept('GET', '/api/users').as('getUsers')
StubReturn fake response, block real networkcy.intercept('GET', '/api/users', { fixture: 'users.json' })
ModifyIntercept and alter the live responsecy.intercept('GET', '/api/users', (req) => { req.reply(res => { ... }) })
JAVASCRIPT — spy and wait
// Spy on a request and wait for it to complete
cy.intercept('GET', '/api/products').as('getProducts')
cy.visit('/products')
cy.wait('@getProducts').its('response.statusCode').should('eq', 200)
cy.get('.product-list').should('be.visible')
JAVASCRIPT — stub with fixture
// Return fixture data instead of hitting real API
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('stubProducts')
cy.visit('/products')
cy.wait('@stubProducts')
cy.get('.product-card').should('have.length', 3)
JAVASCRIPT — intercept with route matcher
// Match with wildcards and query params
cy.intercept({ method: 'GET', url: '/api/users/**' }, (req) => {
  req.reply({ statusCode: 200, body: [{ id: 1, name: 'Test User' }] })
}).as('getUser')

// Assert on request body for POST
cy.intercept('POST', '/api/login').as('loginRequest')
cy.wait('@loginRequest').its('request.body').should('deep.include', { email: 'user@test.com' })
💡
Pro tip: Use cy.wait('@alias') instead of cy.wait(2000). Waiting for a named request is deterministic — it finishes as soon as the response arrives.
⚙️
⚡ Cypress Tutorial
Environment Variables & Config
April 2026

Control test behaviour across environments using cypress.config.js and environment variables — no hard-coded URLs or credentials.

cypress.config.js — Key Options

OptionDefaultPurpose
baseUrlnullPrefix for cy.visit() and cy.request()
defaultCommandTimeout4000msHow long Cypress retries commands
viewportWidth / Height1280 × 720Default browser window size
specPatterncypress/e2e/**/*.cy.{js,jsx,ts,tsx}Glob for test files
retries0Auto-retry failing tests
videofalseRecord video during cypress run
screenshotOnRunFailuretrueAuto-screenshot on failure
JAVASCRIPT — cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'https://staging.example.com',
    defaultCommandTimeout: 6000,
    viewportWidth: 1440,
    viewportHeight: 900,
    retries: { runMode: 2, openMode: 0 },
    video: false,
    env: {
      apiUrl: 'https://api.staging.example.com',
      adminEmail: 'admin@example.com'
    }
  }
})
JAVASCRIPT — reading env vars in tests
// Read from cypress.config.js env block
const apiUrl = Cypress.env('apiUrl')

// Override via CLI (e.g. for different environments)
// npx cypress run --env apiUrl=https://prod.example.com

// Or via .env file with cypress-dotenv plugin
cy.request(`${apiUrl}/users`).then((res) => {
  expect(res.status).to.eq(200)
})
💡
Never commit secrets. Use CYPRESS_* OS environment variables (e.g. CYPRESS_API_KEY=abc123) — Cypress automatically picks them up as Cypress.env('API_KEY').
🔷
⚡ Cypress Tutorial
TypeScript Support
April 2026

Write type-safe Cypress tests with full IntelliSense for cy commands — essential for large teams and enterprise projects.

Setup TypeScript in Cypress

1

Install TypeScript

npm install typescript --save-dev

2

Create tsconfig.json

Configure TypeScript for the cypress/ folder.

3

Rename files

Change .cy.js.cy.ts and commands.jscommands.ts

4

Add type definitions

Extend Cypress.Chainable for custom commands.

JSON — cypress/tsconfig.json
{
  "compilerOptions": {
    "target":    "ES6",
    "lib":       ["ES6", "DOM"],
    "types":     ["cypress", "node"],
    "strict":    false,
    "esModuleInterop": true
  },
  "include": ["**/*.ts"]
}
TYPESCRIPT — typing custom commands
// cypress/support/commands.ts
Cypress.Commands.add('login', (email: string, password: string) => {
  cy.request({ method: 'POST', url: '/api/login', body: { email, password } })
    .then((res) => { cy.setCookie('auth_token', res.body.token) })
})

// cypress/support/index.d.ts — type declaration
declare namespace Cypress {
  interface Chainable {
    login(email: string, password: string): Chainable<void>
  }
}
TYPESCRIPT — test file (.cy.ts)
import loginPage from '../topics/LoginPage'

describe('Login', () => {
  it('logs in via API then visits dashboard', () => {
    cy.login('admin@test.com', 'pass123')
    cy.visit('/dashboard')
    cy.get('[data-cy="welcome-msg"]').should('be.visible')
  })
})
🔐
⚡ Cypress Tutorial
Authentication Patterns
April 2026

Handle login correctly in Cypress — avoid repeating the UI login flow in every test using cy.session(), API login, and token injection.

The Authentication Problem in E2E Testing

Almost every real-world application requires authentication. The challenge is that if you're testing feature X (e.g., "user can create a post"), you don't want to spend 5-10 seconds logging in via the UI before testing that feature. With 100 tests, repeating login in each test means 500 seconds just spent on authentication — that's over 8 minutes of wasted test execution time.

The solution is to handle authentication efficiently: log in once per test session (or once per test), cache the authentication state, and reuse it. Cypress provides three powerful patterns for this, each suited to different scenarios. Choosing the right pattern can cut your test execution time in half.

Pattern 1 — Programmatic API Login (Fastest)

This pattern logs in directly via your API without touching the UI at all. You make an API call to /api/login, receive a token, and set it in localStorage or as a cookie. This is the fastest approach (completes in milliseconds) and is ideal when your API is reliable and stable.

JAVASCRIPT — login via cy.request()
// Skip the UI entirely — hit the login API directly
Cypress.Commands.add('loginByApi', (email, password) => {
  cy.request('POST', '/api/auth/login', { email, password })
    .then((resp) => {
      window.localStorage.setItem('authToken', resp.body.token)
    })
})

// In test:
beforeEach(() => {
  cy.loginByApi('user@test.com', 'pass123')
  cy.visit('/dashboard')
})

Pattern 2 — cy.session() (Cache & Reuse)

JAVASCRIPT — cy.session() caches login state
// cy.session() runs the setup only once per session key
// Subsequent calls restore cookies/storage from cache
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.visit('/login')
    cy.get('#email').type(email)
    cy.get('#password').type(password)
    cy.get('[type="submit"]').click()
    cy.url().should('include', '/dashboard')
  }, {
    validate() {
      cy.request('/api/profile').its('status').should('eq', 200)
    }
  })
})

Pattern 3 — JWT / Token Injection

JAVASCRIPT — set Authorization header for all requests
// Intercept every request and inject a Bearer token
cy.intercept('**', (req) => {
  req.headers['Authorization'] = `Bearer ${Cypress.env('JWT_TOKEN')}`
})

// Or set it on localStorage before visiting
cy.window().then((win) => {
  win.localStorage.setItem('token', Cypress.env('JWT_TOKEN'))
})
cy.visit('/app')
💡
Best practice ranking: cy.session() > API login > UI login. Never repeat UI login in every test — it's the #1 cause of slow Cypress suites.
📋
⚡ Cypress Tutorial
Data-Driven Testing
April 2026

Run the same test logic across multiple datasets using fixture files, forEach loops, and cy.each() — reduce duplication and increase coverage.

Parameterized Tests with forEach

JAVASCRIPT — test multiple scenarios from an array
const loginScenarios = [
  { email: 'admin@test.com',  password: 'pass1', expectedUrl: '/admin' },
  { email: 'user@test.com',   password: 'pass2', expectedUrl: '/dashboard' },
  { email: 'viewer@test.com', password: 'pass3', expectedUrl: '/read-only' }
]

loginScenarios.forEach(({ email, password, expectedUrl }) => {
  it(`redirects ${email} to ${expectedUrl}`, () => {
    cy.visit('/login')
    cy.get('#email').type(email)
    cy.get('#password').type(password)
    cy.get('[type="submit"]').click()
    cy.url().should('include', expectedUrl)
  })
})

Fixture-Driven Data Tests

JSON — cypress/fixtures/products.json
[
  { "name": "Laptop",  "price": 1200, "category": "Electronics" },
  { "name": "Desk",    "price": 350,  "category": "Furniture" },
  { "name": "Headset", "price": 89,   "category": "Electronics" }
]
JAVASCRIPT — iterate over fixture data
cy.fixture('products').then((products) => {
  products.forEach((product) => {
    cy.request('POST', '/api/products', product)
      .its('status').should('eq', 201)
  })
})

// cy.each() — iterate over DOM elements
cy.get('.product-card').each(($card) => {
  cy.wrap($card).find('.price').should('be.visible')
  cy.wrap($card).find('.add-to-cart').should('not.be.disabled')
})
💡
Pro tip: Use context() or describe() inside forEach to group related dataset scenarios together in the test report.
🪟
⚡ Cypress Tutorial
Alerts, iframes & Windows
April 2026

Handle browser dialogs, embedded iframes, and new browser tabs — some of the trickiest automation scenarios.

Browser Alerts & Confirms

JAVASCRIPT — window:alert / window:confirm
// Cypress auto-accepts alert() — no action needed
// To assert the alert text:
cy.on('window:alert', (text) => {
  expect(text).to.eq('Item deleted!')
})
cy.get('[data-cy="delete-btn"]').click()

// Override confirm() to return false (cancel)
cy.on('window:confirm', () => false)
cy.get('[data-cy="delete-btn"]').click()
cy.get('.item-row').should('still.exist')

iframes

JAVASCRIPT — interacting inside an iframe
// Install: npm install cypress-iframe --save-dev
// In e2e.js: import 'cypress-iframe'

cy.frameLoaded('#payment-iframe')
cy.iframe('#payment-iframe')
  .find('input[name="cardNumber"]')
  .type('4111111111111111')

// Without plugin — using cy.wrap on contentDocument
cy.get('iframe').its('0.contentDocument.body')
  .should('not.be.empty')
  .then(cy.wrap)
  .find('button[type="submit"]')
  .click()

New Tabs & Windows

JAVASCRIPT — intercepting new window opens
// Cypress runs in one tab — remove target="_blank" to test in same window
cy.get('a[target="_blank"]')
  .invoke('removeAttr', 'target')
  .click()
cy.url().should('include', '/new-page')

// Or stub window.open and assert URL
cy.window().then((win) => {
  cy.stub(win, 'open').as('windowOpen')
})
cy.get('.external-link').click()
cy.get('@windowOpen').should('be.calledWithMatch', 'https://docs.example.com')
🍪
⚡ Cypress Tutorial
Cookies & Storage
April 2026

Read, write, and clear browser cookies, localStorage, and sessionStorage — essential for authentication and state management testing.

Cookie Commands

CommandPurpose
cy.getCookie('name')Get a single cookie
cy.getCookies()Get all cookies
cy.setCookie('name', 'value')Set a cookie before test
cy.clearCookie('name')Remove one cookie
cy.clearCookies()Remove all cookies
JAVASCRIPT — pre-set auth cookie to skip login
beforeEach(() => {
  // Get a real token via API, then set the cookie
  cy.request('POST', '/api/login', {
    email: 'user@test.com', password: 'pass'
  }).then((res) => {
    cy.setCookie('auth_token', res.body.token)
  })
  cy.visit('/dashboard') // already authenticated
})

localStorage & sessionStorage

JAVASCRIPT — localStorage via cy.window()
// Set localStorage before visiting
cy.window().then((win) => {
  win.localStorage.setItem('theme', 'dark')
  win.localStorage.setItem('lang', 'en')
})

// Assert a value
cy.window().its('localStorage')
  .invoke('getItem', 'theme')
  .should('eq', 'dark')

// Clear all storage between tests
cy.clearLocalStorage()
cy.clearAllSessionStorage()
💡
Session isolation: In Cypress 12+, testIsolation: true (default) clears cookies, localStorage, and sessionStorage before every test automatically.
📐
⚡ Cypress Tutorial
Viewport & Screenshots
April 2026

Test responsive layouts by switching viewports and capture screenshots for debugging and visual verification.

Viewport Testing

JAVASCRIPT — responsive testing
// Preset sizes: 'iphone-6', 'ipad-2', 'samsung-s10', etc.
cy.viewport('iphone-6')
cy.visit('/')
cy.get('.hamburger-menu').should('be.visible')
cy.get('.desktop-nav').should('not.be.visible')

// Custom width × height
cy.viewport(1920, 1080)
cy.get('.desktop-nav').should('be.visible')

// Test multiple viewports in a loop
['iphone-6', 'ipad-2', [1280, 720]].forEach((size) => {
  it(`renders on ${size}`, () => {
    cy.viewport(...(Array.isArray(size) ? size : [size]))
    cy.visit('/')
    cy.get('main').should('be.visible')
  })
})

Screenshots

JAVASCRIPT — manual screenshots
// Full page screenshot
cy.screenshot('checkout-step-2')

// Element-scoped screenshot
cy.get('.invoice-table').screenshot('invoice')

// Screenshot on every test failure (add to afterEach)
afterEach(function () {
  if (this.currentTest.state === 'failed') {
    cy.screenshot(`FAILED-${this.currentTest.title}`)
  }
})
💡
Screenshots are saved to cypress/screenshots/ and videos to cypress/videos/. Both are auto-uploaded in Cypress Cloud for remote debugging.
🌑
⚡ Cypress Tutorial
Shadow DOM & Dynamic Elements
April 2026

Interact with Web Components inside Shadow DOM and reliably handle dynamic elements like spinners, lazy-loaded content, and animations.

Shadow DOM with cy.shadow()

JAVASCRIPT — piercing the shadow root
// Access elements inside a shadow root
cy.get('my-custom-button')
  .shadow()
  .find('button')
  .click()

// Nested shadow roots
cy.get('payment-widget')
  .shadow()
  .find('card-input')
  .shadow()
  .find('input')
  .type('4111111111111111')

// Enable shadow DOM piercing globally (cypress.config.js)
// includeShadowDom: true  — then cy.get() pierces shadow roots automatically
cy.get('my-element input').should('be.visible')  // works with includeShadowDom: true

Dynamic Elements & Loading States

JAVASCRIPT — handling async content
// Wait for skeleton/loader to disappear first
cy.get('.skeleton-loader').should('not.exist')
cy.get('.data-table').should('be.visible')

// Lazy-loaded images — trigger scroll to load them
cy.get('[data-cy="product-grid"]').scrollIntoView()
cy.get('.product-img').should('have.attr', 'src').and('not.be.empty')

// Infinite scroll — load more items
cy.scrollTo('bottom')
cy.get('.item-card').should('have.length.greaterThan', 10)

// Retry on stale DOM — Cypress handles this automatically via .should()
cy.get('[data-cy="user-count"]')
  .should('not.have.text', 'Loading...')
  .invoke('text')
  .then(parseFloat)
  .should('be.greaterThan', 0)
💡
includeShadowDom: true in cypress.config.js is the cleanest approach for Web Component-heavy apps — it removes the need to chain .shadow() on every query.
📎
⚡ Cypress Tutorial
File Upload & Download
April 2026

Automate file input interactions with cy.selectFile() for uploads and cy.readFile() to verify downloaded content.

File Upload with cy.selectFile()

JAVASCRIPT — upload a real file
// Upload a file from cypress/fixtures/
cy.get('input[type="file"]')
  .selectFile('cypress/fixtures/resume.pdf')
cy.get('.upload-status').should('contain', 'Uploaded successfully')

// Drag-and-drop upload (drop zone)
cy.get('.drop-zone')
  .selectFile('cypress/fixtures/report.csv', { action: 'drag-drop' })

// Upload multiple files
cy.get('input[type="file"]')
  .selectFile(['cypress/fixtures/a.jpg', 'cypress/fixtures/b.jpg'])

File Download Verification

JAVASCRIPT — verify downloaded file
// Click the download button
cy.get('[data-cy="export-btn"]').click()

// File lands in cypress/downloads/
cy.readFile('cypress/downloads/report.csv')
  .should('contain', 'OrderID,Amount')

// Verify JSON download content
cy.readFile('cypress/downloads/users.json')
  .its('length')
  .should('be.greaterThan', 0)
🔌
⚡ Cypress Tutorial
cy.task() & Plugins
April 2026

Run Node.js code from inside your tests with cy.task() — seed databases, read files, send emails, or call any server-side API.

cy.task() — Bridge to Node.js

JAVASCRIPT — cypress.config.js (define task)
const { defineConfig } = require('cypress')
const db = require('./support/db')

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        // Seed a test user directly in the DB
        async seedUser(user) {
          await db.users.insert(user)
          return null
        },
        // Get latest OTP email from mailhog/inbox
        async getOTP(email) {
          const msg = await mailhog.getLatest(email)
          return msg.body.match(/\d{6}/)[0]
        }
      })
    }
  }
})
JAVASCRIPT — calling cy.task() in a test
cy.task('seedUser', { name: 'Jane', email: 'jane@test.com' })

cy.task('getOTP', 'jane@test.com').then((otp) => {
  cy.get('#otp-input').type(otp)
  cy.get('[data-cy="verify-btn"]').click()
})

Popular Cypress Plugins

PluginPurpose
@cypress/code-coverageInstrument and collect JS code coverage
cypress-iframeHelpers for interacting with iframes
cypress-axeAccessibility (a11y) testing with axe-core
cypress-real-eventsReal native browser events (hover, drag)
@shelex/cypress-allure-pluginAllure report integration
cypress-mochawesome-reporterHTML test reports
🔁
⚡ Cypress Tutorial
Retries & Flaky Tests
April 2026

Configure automatic test retries, understand test isolation, and use best practices to eliminate flakiness from your suite.

The Flaky Test Problem

Flaky tests are the bane of every QA team's existence. A flaky test passes sometimes and fails sometimes, without any code changes. It drives developers crazy because they can't reproduce the failure, making it almost impossible to fix. Common causes include hardcoded waits that aren't long enough on slow servers, race conditions in your test logic, or tests that depend on state from previous tests.

Cypress addresses flakiness at multiple levels. First, it has automatic retry logic built-in that eliminates many timing issues. Second, it offers test isolation that clears browser state between tests so tests don't interfere with each other. Third, it allows you to configure automatic retries at the global level (useful in CI) or per-test.

Retry Mechanisms in Cypress

Cypress provides two levels of retries: command-level retries (automatic retry of commands like cy.get() and assertions) and test-level retries (automatically re-running an entire failing test). Understanding the difference is crucial.

Configuring Retries

JAVASCRIPT — retry config in cypress.config.js
module.exports = defineConfig({
  e2e: {
    retries: {
      runMode:  2,  // retry 2× in CI (npx cypress run)
      openMode: 0   // no retries in interactive GUI
    }
  }
})
JAVASCRIPT — per-test retry override
it('submits the payment form', { retries: { runMode: 3 } }, () => {
  // This test will retry up to 3 times in CI
  cy.get('[data-cy="pay-btn"]').click()
  cy.get('.success-toast').should('be.visible')
})

Common Causes of Flakiness & Fixes

CauseBad PatternFix
Hardcoded waitscy.wait(3000)Use cy.wait('@alias') or assertion retry
Race conditionsChecking element before XHR finishesIntercept + wait for the network call
Shared stateTests rely on previous test's dataEnable testIsolation: true, seed data per-test
AnimationsClicking a moving element.should('not.have.class', 'animating') first
Unstable selectorscy.get('.btn:nth-child(3)')Use [data-cy="submit-btn"]
💡
Test isolation (Cypress 12+): testIsolation: true is on by default. Cypress clears all browser state between every it() — cookies, localStorage, and sessionStorage — keeping tests independent.
🧩
⚡ Cypress Tutorial
Component Testing
April 2026

Test UI components in complete isolation — mount a single React, Vue, or Angular component and assert its behaviour without spinning up the full app.

E2E Testing vs Component Testing

AspectE2E TestingComponent Testing
ScopeFull app through a browserSingle component, isolated
SpeedSlower (full page load)Very fast (no routing)
Setupcy.visit(url)cy.mount(<Component />)
Best forUser flows, integrationsUI logic, props, events

Setup for React

TERMINAL — configure component testing
# Run Cypress Launchpad and select Component Testing
npx cypress open

# Or configure manually
npm install @cypress/react vite --save-dev
JAVASCRIPT — cypress.config.js
module.exports = defineConfig({
  component: {
    devServer: { framework: 'react', bundler: 'vite' },
    specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}'
  }
})
JSX — src/components/Button.cy.jsx
import Button from './Button'

describe('Button component', () => {
  it('renders label and fires onClick', () => {
    const onClick = cy.stub().as('clickHandler')
    cy.mount(<Button label="Save" onClick={onClick} />)

    cy.get('button').should('have.text', 'Save')
    cy.get('button').click()
    cy.get('@clickHandler').should('have.been.calledOnce')
  })

  it('shows disabled state', () => {
    cy.mount(<Button label="Submit" disabled />)
    cy.get('button').should('be.disabled')
  })
})
⚡ Cypress Tutorial
Accessibility Testing
April 2026

Automatically detect WCAG violations in every test run using cypress-axe — catch missing labels, contrast issues, and ARIA errors before they ship.

Setup cypress-axe

TERMINAL
npm install cypress-axe axe-core --save-dev
JAVASCRIPT — cypress/support/e2e.js
import 'cypress-axe'

Running Accessibility Checks

JAVASCRIPT — cy.checkA11y()
it('has no accessibility violations on login page', () => {
  cy.visit('/login')
  cy.injectAxe()
  cy.checkA11y()  // fails if any WCAG violations found
})

// Check only a specific part of the page
cy.injectAxe()
cy.checkA11y('#main-form')

// Configure which rules to apply
cy.checkA11y(null, {
  runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] }
})

// Log violations without failing the test
cy.checkA11y(null, null, (violations) => {
  violations.forEach((v) => cy.task('log', `A11Y: ${v.id} — ${v.description}`))
}, true)

Common WCAG Violations Cypress-axe Catches

RuleWhat it checks
color-contrastText/background contrast ratio meets WCAG AA
image-altAll <img> have descriptive alt text
labelAll form inputs have associated <label>
button-nameButtons have accessible names
link-nameAnchor tags have descriptive text
aria-required-attrARIA roles have required attributes
📊
⚡ Cypress Tutorial
Test Reports
April 2026

Generate professional HTML and Allure reports from your Cypress runs — essential for stakeholder visibility and CI artifact publishing.

Mochawesome HTML Reports

TERMINAL — install cypress-mochawesome-reporter
npm install cypress-mochawesome-reporter --save-dev
JAVASCRIPT — cypress.config.js
module.exports = defineConfig({
  reporter: 'cypress-mochawesome-reporter',
  reporterOptions: {
    charts: true,
    reportPageTitle: 'My Test Report',
    embeddedScreenshots: true,
    inlineAssets: true
  },
  e2e: {
    setupNodeEvents(on, config) {
      require('cypress-mochawesome-reporter/plugin')(on)
    }
  }
})
JAVASCRIPT — cypress/support/e2e.js
import 'cypress-mochawesome-reporter/register'

Allure Reports

TERMINAL — install Allure plugin
npm install @shelex/cypress-allure-plugin allure-commandline --save-dev
JAVASCRIPT — register and generate
// cypress/support/e2e.js
import '@shelex/cypress-allure-plugin'

// Run tests and generate report:
// npx cypress run --env allure=true
// npx allure generate allure-results --clean -o allure-report
// npx allure open allure-report
💡
Cypress Cloud (formerly Dashboard) provides built-in test recording, parallelisation, flake detection, and analytics — sign up at cloud.cypress.io and add your CYPRESS_RECORD_KEY to CI.
🚀
⚡ Cypress Tutorial
CI/CD Integration
April 2026

Run Cypress tests automatically on every push using GitHub Actions, Jenkins, or any CI platform.

Continuous Testing: The Real Business Value

Running tests locally on your machine is great, but the real value comes from running tests automatically in CI/CD pipelines. Every time a developer pushes code or opens a pull request, tests run automatically. If tests fail, the pull request blocks and developers know immediately that something is broken. This catches bugs before they reach production.

Cypress is built with CI/CD in mind. It can run headlessly (without a visible browser window), record videos of failures, generate reports, and even run tests in parallel across multiple machines. This means you can have hundreds of tests that complete in just a few minutes instead of 30+ minutes.

Setting Up Cypress in GitHub Actions

GitHub Actions is a free CI/CD platform built into GitHub. If your project is already on GitHub, setting up Cypress testing is incredibly straightforward. The example below shows a basic configuration that runs your Cypress tests on every push and pull request.

YAML — .github/workflows/cypress.yml
name: Cypress E2E Tests
on: [push, pull_request]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: cypress-io/github-action@v6
        with:
          start: npm start
          wait-on: 'http://localhost:3000'
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
YAML — parallel execution across 3 machines
jobs:
  cypress-run:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        containers: [1, 2, 3]
    steps:
      - uses: actions/checkout@v3
      - uses: cypress-io/github-action@v6
        with:
          record: true
          parallel: true
          group: 'E2E Tests'
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
💡
Headless run: npx cypress run runs headlessly by default in CI. Add --browser chrome to target a specific browser, or --spec "cypress/e2e/auth/**" to run only a subset of tests.
❓ FAQ & Resources
Frequently Asked Questions
Common issues & solutions | TechWorld Labs

Find answers to common Cypress questions and learn best practices from experienced QA engineers.

Best Practices

🎯
Use Data Attributes
Select elements using data-cy instead of class/id selectors for stability.
⏱️
Avoid Hard Waits
Use cy.intercept() and cy.wait() for network requests instead of cy.wait(1000).
📦
Page Object Model
Organize selectors and actions into reusable page classes for maintainability.
🔐
Handle Authentication
Use cy.session() to cache login state and skip repetitive auth in tests.
📝
Keep Tests Isolated
Each test should be independent; avoid dependencies on other tests' state.
🚀
Run in CI/CD
Configure parallel execution and use GitHub Actions for automated test runs.

Common Pitfalls & Solutions

⚠️

Tests fail in CI but pass locally

Root cause: Missing cy.wait() for API calls or timing issues. Solution: Use cy.intercept() to stub API responses and ensure predictable test behavior across environments.

⚠️

Element not clickable error

Root cause: Element hidden behind modal, covered by overlay, or not yet visible. Solution: Use cy.get().should('be.visible') before clicking, or cy.get().click({ force: true }) as last resort.

⚠️

Flaky tests - random failures

Root cause: Race conditions, timing issues, or inadequate waits. Solution: Use cy.intercept() with wait(), avoid hard-coded waits, enable retry mechanism in cypress.config.js.

⚠️

Tests take too long to run

Root cause: Visiting full page load repeatedly. Solution: Use cy.visit() with onBeforeLoad to skip unnecessary assets, use cy.session() to skip login flow, or run tests in parallel.

Frequently Asked Questions

Q1

Can I use Cypress to test non-web applications?

No, Cypress only automates web browsers. For mobile apps, use Appium or Detox. For desktop apps, use UFT or TestComplete.

Q2

How do I handle file uploads in Cypress?

Use cy.selectFile() to select a file from your machine, or cy.get('input[type="file"]').selectFile('path/to/file') to upload.

Q3

Is Cypress suitable for testing APIs?

Yes! Use cy.request() to make API calls, cy.intercept() to stub responses, and assert on status codes and response body.

Q4

Can I run Cypress tests in parallel?

Yes, use the Cypress Dashboard with `npx cypress run --record --parallel` to run tests across multiple machines simultaneously.

Q5

How do I test Shadow DOM elements in Cypress?

Use cy.get() with .shadow() method (Cypress 13+), or pierce the shadow DOM with CSS combinators like `cy.get('host >>> element')`.

Q6

What's the difference between cy.get() and cy.contains()?

cy.get() selects by selector/attribute. cy.contains() finds elements by text content, useful when selectors are unstable.

Advanced Tips

💡
Custom Commands: Create reusable Cypress commands in support/commands.js for common workflows like login, form filling, or assertions. Use Cypress.Commands.add('login', (email, password) => { ... }).
Debugging: Use cy.debug() to pause execution, cy.pause() for manual stepping, or --headed --no-exit flags to keep browser open after tests.
⚠️
Performance: Monitor test execution time. If tests exceed 10-15 minutes, run in parallel. Use cy.session() to cache login, reducing test setup time by 30-50%.