On this page14 sections

Cypress + TypeScript E2E Automation

A production-structured Cypress + TypeScript project with custom commands, intercept-based network mocking, fixture files, and a GitHub Actions pipeline.

IntermediateE2EUI automationNetwork mockingComponent testingui automation
Setup: ~25 minLanguage: TypeScriptFramework: CypressPackage manager: npmBest for: Automation QA engineers moving from manual testing to Cypress

Overview

This project showcases a well-structured Cypress + TypeScript test suite against Sauce Demo (saucedemo.com), a static e-commerce practice site. It demonstrates the Cypress-idiomatic approach to test organisation: typed custom commands that wrap multi-step UI flows, fixture files for test data, and a Page Object-light structure for locator management. The CI pipeline validates every push using the Cypress GitHub Action and uploads Mochawesome HTML reports as artifacts.

Project goals

  • Build a custom command library that abstracts repetitive UI interactions (login, addToCart, checkout) into single-line calls
  • Demonstrate cy.intercept() usage by stubbing any third-party analytics or external scripts (the practice site is static and has no backend API to mock)
  • Organise fixture files for user accounts, product data, and order payloads to separate test data from test logic
  • Implement TypeScript strictly: type the custom commands in cypress/support/commands.ts and extend Cypress.Chainable
  • Run the full suite in parallel in CI and merge results into a single Mochawesome report
  • Pass TSC with strict mode — zero any, no implicit returns

Architecture

Custom Commands + Fixture-Driven Data

Tests call high-level custom commands that encapsulate multi-step UI sequences. Fixture files supply all test data, keeping test files focused on assertions. A small Page Object-light layer in cypress/pages/ owns the locators per page so test files stay focused on user-flow assertions.

cypress/e2e/Test specs (.cy.ts); one file per feature or user journey
cypress/support/commands.ts (custom commands) and e2e.ts (global hooks)
cypress/fixtures/JSON fixture files: users.json, products.json, orders.json
cypress/pages/Optional Page Objects for complex multi-step flows
cypress.config.tsBase URL, specPattern, video/screenshot settings, env vars

Prerequisites

  • Node.js 18 or later
  • npm 9 or later
  • Git
  • Chrome (or any Chromium-based browser) for local headed runs

Folder structure

Project structure
Bash
cypress.config.ts                         # Root Cypress configuration: baseUrl, specPattern, viewportSize, env
cypress.env.json                          # Local-only secrets (gitignored); CI uses GitHub Actions env vars
cypress.env.json.example                  # Checked-in template showing required env var keys
cypress/e2e/                              # All test specs (.cy.ts) — one file per feature
cypress/e2e/auth/login.cy.ts              # Login: happy path, wrong password, lockout, session persistence
cypress/e2e/shop/product-listing.cy.ts    # Sort and filter on the product catalogue
cypress/e2e/shop/checkout.cy.ts           # Full UI checkout flow: add to cart, fill info, complete order
cypress/support/commands.ts               # Custom commands: cy.login(), cy.addToCart(), cy.fillCheckout()
cypress/support/e2e.ts                    # Global hooks (beforeEach screenshot reset) and command import
cypress/fixtures/users.json               # Test user accounts (standard, locked, admin)
cypress/fixtures/products.json            # Product payloads for intercept stubs
cypress/fixtures/orders.json              # Expected order confirmation data for assertion
tsconfig.json                             # TypeScript config — extends the base, adds Cypress type references
.github/workflows/cypress.yml             # CI workflow: install, run, upload artifacts

Setup & run

Installation

  1. 1.Clone the repository: git clone <repo-url> && cd cypress-typescript
  2. 2.Install dependencies: npm install
  3. 3.Copy environment config: cp cypress.env.json.example cypress.env.json
  4. 4.Set baseUrl and credentials in cypress.env.json
  5. 5.Verify Cypress opens correctly: npx cypress open

Commands

Open Cypress Test Runner (interactive)

npx cypress open

Opens the GUI test runner — choose E2E Testing, pick a browser, then click a spec

Run all tests headlessly (CI mode)

npx cypress run

Runs all specs headlessly in Electron; generates video and screenshots on failure

Run tests in Chrome headlessly

npx cypress run --browser chrome

Run a single spec file

npx cypress run --spec 'cypress/e2e/checkout.cy.ts'

Run with environment variable override

npx cypress run --env baseUrl=https://staging.example.com

Generate and open Mochawesome HTML report

npm run report

Merges individual JSON results and opens the combined HTML report

Environment

VariableDescriptionExampleRequired
BASE_URLRoot URL of the application under testhttps://www.saucedemo.comYes
TEST_USER_USERNAMEUsername of the standard test accountstandard_userYes
TEST_PASSWORDPassword of the standard test accountsecret_sauceYes
CYPRESS_RECORD_KEYCypress Dashboard record key (optional — only needed if recording to Cypress Cloud)No

Test data strategy

  • JSON fixture files supply all user, product, and order data — tests never hardcode values inline
  • cy.intercept() is configured to stub any third-party analytics or external scripts so tests aren't slowed by them; the practice site itself is static and has no API to mock
  • cy.login() is a UI-level custom command that performs a fresh login per test — Sauce Demo's auth state lives in sessionStorage (per-test scope), so per-test login is the simplest correct approach
  • Unique email addresses for sign-up tests use `Date.now()` suffix to prevent conflicts in parallel runs
  • Fixture data is versioned alongside test code so tests and data always move together

Reporting

  • Cypress records video of every headless run; failing tests also generate a screenshot at the point of failure
  • Mochawesome reporter is configured to output per-spec JSON files; `npm run report` merges them into a single HTML report
  • GitHub Actions uploads the merged report and the videos directory as artifacts on every run
  • Test run summary (pass/fail counts, duration) is printed to stdout by the default `spec` reporter for quick CI feedback

CI/CD

  • .github/workflows/cypress.yml triggers on push to main and on pull_request events
  • uses: cypress-io/github-action@v6 handles install, browser setup, and run in one step
  • BASE_URL, TEST_USER_USERNAME, and TEST_USER_PASSWORD are injected as GitHub Actions secrets and exposed as env vars
  • Mochawesome reports and video artifacts are uploaded with actions/upload-artifact@v4
  • The workflow fails fast: if any spec fails, the workflow reports failure and does not proceed to deployment

Common issues

cy.get() returns detached or stale element after XHR completes

Cause: The DOM re-rendered after the intercept resolved, invalidating the chained reference

Fix: Re-query the element after the intercept wait: use `cy.wait('@alias').then(() => cy.get(selector))`

TypeScript error: Property 'login' does not exist on type Chainable

Cause: Custom command is defined in commands.ts but the type declaration is missing

Fix: Add a `declare namespace Cypress { interface Chainable { login(email: string, password: string): void } }` block to commands.ts

Intercept stub is not matched — real API is called instead

Cause: The intercept URL pattern does not match (query string, trailing slash, or protocol mismatch)

Fix: Use a glob pattern: `cy.intercept('POST', '**/api/checkout**', { fixture: 'orders' })` and inspect the network tab to confirm the URL

Tests pass locally but fail in CI with 'Timed out retrying after 4000ms'

Cause: CI environment is slower; default command timeout is too low for slower network/render

Fix: Increase `defaultCommandTimeout` in cypress.config.ts to 8000–10000ms for CI, or use `cy.get(selector, { timeout: 8000 })`

Best practices

  • Use `data-testid` attributes for locators that must survive visual redesigns; document them in a locator registry comment
  • Keep cy.intercept() stubs in the test file they support, not in global support hooks, so the test is self-documenting
  • Avoid cy.wait(number) for arbitrary delays; use cy.wait('@alias') with a named intercept instead
  • One spec file per user journey — avoid mega-spec files that test 10+ flows and take 5 minutes to run
  • Use fixture files for all test data; never hardcode user credentials or product IDs in test code
  • Run `npx tsc --noEmit` in CI before the Cypress step to catch type errors early

Next steps

  • Add component tests using Cypress Component Testing for React or Vue components that are difficult to test at the E2E level
  • Integrate with cypress-axe to run accessibility audits on each page during the test run
  • Set up Cypress Cloud to enable parallelisation across multiple CI machines and get historical analytics
  • Add API-only tests using cy.request() to cover the backend contract without launching a browser
  • Extend the custom command library to support roles (admin vs standard user) using session-scoped login