Cypress has become the dominant end-to-end testing tool at Indian product companies, replacing Selenium for web application testing at companies like Razorpay, Freshworks, and Zoho. QA engineers and frontend engineers are expected to know Cypress selectors, network interception, fixtures, and CI integration.
Cypress Architecture and Core Commands
Cypress has a unique architecture compared to Selenium, which changes how you think about E2E testing.
Cypress vs Selenium:
- Cypress runs inside the browser (in the same JavaScript execution context as the application); Selenium controls the browser from outside via a WebDriver protocol
- Benefits of running inside the browser: direct DOM access, no network latency between test runner and browser, ability to stub network calls, automatic waiting
- No explicit waits: Cypress automatically retries commands until the element is found or the timeout expires; never use cy.wait(2000) for element readiness
- Commands are asynchronous under the hood but written synchronously — they queue up and execute in order
Core commands:
- cy.visit('/dashboard') — navigate to a URL (relative to baseUrl in cypress.config.js)
- cy.get('#submit-btn') — get an element by CSS selector
- cy.get('[data-testid="login-form"]') — get by data-testid attribute (preferred; resilient to style changes)
- cy.contains('Submit') — get an element containing the given text
- cy.get('form').find('input[name="email"]') — find a child element within a parent
- Chainable: cy.get('ul').children().first().click()
Interactions:
- .click() — click an element
- .type('hello@example.com') — type text into an input (clears nothing; use .clear() first if needed)
- .clear() — clear an input field
- .check() / .uncheck() — check or uncheck a checkbox or radio button
- .select('Option Value') — select from a <select> dropdown
- .trigger('change') — trigger a DOM event
- .focus() / .blur() — focus or blur an element
- .scrollIntoView() — scroll an element into the viewport before interacting
Assertions:
- .should('be.visible') — assert the element is visible
- .should('have.text', 'Expected text') — assert exact text content
- .should('contain', 'partial text') — assert partial text
- .should('have.value', 'input value') — assert input value
- .should('have.class', 'active') — assert CSS class
- .should('be.disabled') — assert an element is disabled
- cy.url().should('include', '/dashboard') — assert the URL contains a path
- cy.title().should('eq', 'Page Title') — assert the page title
cy.intercept, Fixtures, and Custom Commands
Network interception is one of Cypress's most powerful features and is heavily tested in QA engineer interviews.
cy.intercept for network stubbing:
- cy.intercept('GET', '/api/users', { fixture: 'users.json' }) — stub a GET request and respond with a fixture file
- cy.intercept('POST', '/api/login', { statusCode: 200, body: { token: 'test-token' } }) — stub a POST request with an inline response
- cy.intercept('GET', '/api/data').as('getData') — alias an intercept for waiting: cy.wait('@getData')
- cy.wait('@getData') — wait for a specific network request to complete before proceeding (better than arbitrary waits)
- cy.wait('@getData').its('response.statusCode').should('eq', 200) — assert on the network response
- cy.intercept can also spy (no stubbing): cy.intercept('POST', '/api/submit').as('submitForm') — let the real request through but alias it for assertions
- Request body assertions: cy.wait('@submitForm').its('request.body').should('deep.equal', { name: 'Alice', age: 30 })
- Intercept with a route matcher: cy.intercept({ method: 'GET', url: '/api/users/**' }) — use wildcards and patterns
Fixtures:
- Fixture files are JSON (or other format) files in the cypress/fixtures/ folder
- Load a fixture: cy.fixture('users.json').then((users) => { / use users data / })
- Use in intercept: cy.intercept('GET', '/api/users', { fixture: 'users.json' })
- Fixtures make tests deterministic — the same test data every run regardless of the server state
Custom commands:
- Add custom commands to cypress/support/commands.ts (or .js)
- Cypress.Commands.add('login', (email, password) => { cy.visit('/login'); cy.get('[name="email"]').type(email); cy.get('[name="password"]').type(password); cy.get('[type="submit"]').click(); })
- Call in tests: cy.login('user@example.com', 'password123')
- Cypress.Commands.add('getByTestId', (id) => cy.get('[data-testid="' + id + '"]')) — shorthand for data-testid selectors
- Custom commands appear in the Cypress Command Log for easy debugging
Test organisation:
- describe / context group tests logically; it defines individual specs
- before() / after() run once per describe block; beforeEach() / afterEach() run before/after each test
- cy.session() (Cypress 9+): cache and restore session state (cookies, localStorage) across tests to avoid re-logging in for every test
CI Integration, Parallelisation, and Best Practices
Running Cypress in CI and managing test reliability are key skills for QA engineer and senior frontend engineer roles in India.
CI integration: 1. Headless mode: cypress run (vs cypress open for interactive mode); runs tests without a visible browser window 2. GitHub Actions: use the cypress-io/github-action to install, cache, and run Cypress; it handles browser installation and caching automatically 3. Example GitHub Actions step: - uses: cypress-io/github-action@v6 with: start: npm start wait-on: 'http://localhost:3000' browser: chrome 4. wait-on: the action waits for the app to be ready before running tests 5. Docker: use the cypress/included Docker image for a consistent browser environment in CI
Parallelisation with Cypress Cloud:
- Cypress Cloud (formerly Cypress Dashboard) orchestrates parallel test runs
- Add --record --parallel to the cypress run command; tests are distributed across multiple CI machines automatically
- Cypress Cloud tracks flaky tests across runs and flags them
Artifacts on failure:
- Cypress automatically captures screenshots on test failure (cypress/screenshots/)
- Video recording of every run (cypress/videos/) — useful for debugging CI failures
- Upload artifacts in GitHub Actions: use actions/upload-artifact to preserve screenshots and videos
Best practices and flaky test prevention:
- Use data-testid attributes for selectors — resilient to CSS class changes and text changes
- Never use arbitrary waits (cy.wait(2000)); use cy.intercept aliases and cy.wait('@alias') instead
- Use cy.intercept to stub non-deterministic APIs; tests should not depend on external service availability
- Avoid testing third-party UIs (payment forms, OAuth windows); stub them at the network level
- Keep tests independent: each test should set up its own state; do not rely on another test having run first
- Use cy.session() to cache login state and avoid repeating login in every test
- Page Object Model (POM): encapsulate page-specific selectors and actions in a class; import and reuse across tests
- Retry-ability: Cypress retries most commands and assertions automatically; only use .should() for assertions (not .then() with expect()) to get automatic retry behaviour
Frequently asked questions
Explore more