100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Testing

Your First Cypress Test

Write, run, and understand a complete Cypress spec file from scratch, covering describe/it blocks, commands, and assertions.

FoundationsBeginner9 min readJul 10, 2026
Analogies

Anatomy of a Cypress Spec File

A Cypress spec file is a plain JavaScript or TypeScript file, conventionally named with a .cy.js or .cy.ts suffix and placed in cypress/e2e/, that uses Mocha's describe and it functions to organize tests into suites and individual test cases. Inside an it block, you chain Cypress commands like cy.visit(), cy.get(), and cy.click() together with Chai-based assertions such as should() or expect(), and because nearly every cy command returns a chainable subject, you can build a readable pipeline that mirrors how a real user would interact with the page.

🏏

Cricket analogy: A describe/it structure is like a tour itinerary grouped by series (describe) and broken into individual Test matches (it), each with its own scorecard, all nested under one overarching tour.

javascript
// cypress/e2e/login.cy.js
describe('Login page', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('shows an error for invalid credentials', () => {
    cy.get('[data-cy="email-input"]').type('nobody@example.com');
    cy.get('[data-cy="password-input"]').type('wrongpassword{enter}');

    cy.get('[data-cy="error-message"]')
      .should('be.visible')
      .and('contain.text', 'Invalid email or password');
  });

  it('redirects to the dashboard on successful login', () => {
    cy.get('[data-cy="email-input"]').type('user@example.com');
    cy.get('[data-cy="password-input"]').type('correcthorsebatterystaple{enter}');

    cy.url().should('include', '/dashboard');
    cy.get('[data-cy="welcome-message"]').should('contain.text', 'Welcome back');
  });
});

Commands, Chaining, and Assertions

Commands like cy.get() are asynchronous under the hood but written in a synchronous-looking style; Cypress internally queues each command and only executes the next one once the previous has resolved, retried automatically if a chained assertion after it fails. The should() command is the idiomatic way to assert because it re-runs the preceding query until the assertion passes or times out, whereas wrapping a value in a raw Chai expect() only checks it once at the moment it's called, so should() is generally safer for anything involving asynchronous UI updates like a loading spinner disappearing.

🏏

Cricket analogy: The should() command's automatic re-checking is like a third umpire re-examining a run-out replay repeatedly from different camera angles until reaching a confident verdict, rather than glancing at one blurry frame and deciding immediately.

Fixtures and beforeEach Hooks

Repeated setup, like navigating to a page or logging in, belongs in a beforeEach hook so every it block starts from a known, consistent state without duplicating code; Mocha runs beforeEach before every single test in the enclosing describe block, and afterEach or after hooks are available for teardown when needed. Static test data, such as a mock list of products, is stored as JSON in cypress/fixtures/ and loaded into a test with cy.fixture('products.json'), which is commonly combined with cy.intercept() to stub an API response with realistic, repeatable payloads instead of depending on a live backend's actual data.

🏏

Cricket analogy: A beforeEach hook is like the standard pre-innings ritual of rolling the pitch and setting the field before every single over, ensuring conditions start identically each time regardless of what happened in the previous over.

cy.fixture('products.json') combined with cy.intercept('GET', '/api/products', { fixture: 'products.json' }) is one of the most common Cypress patterns: it stubs the network call entirely, so tests run fast, deterministically, and without depending on backend state or seed data.

Avoid chaining unrelated assertions off the same cy.get() call after an action that changes the DOM, like a click that triggers a re-render; Cypress may hold a stale reference to the old element. Re-query with cy.get() after actions that mutate the page instead of reusing a saved alias from before the change.

  • Spec files use Mocha's describe/it structure, conventionally named *.cy.js or *.cy.ts, in cypress/e2e/.
  • Cypress commands queue asynchronously but read like synchronous code, resolving one at a time.
  • should() is preferred over raw Chai expect() because it retries the preceding query until it passes or times out.
  • beforeEach hooks run setup like navigation or login before every test in a describe block, avoiding duplication.
  • Fixtures store static JSON test data in cypress/fixtures/, loaded via cy.fixture().
  • Combining cy.fixture() with cy.intercept() stubs API responses for fast, deterministic tests independent of a live backend.
  • Re-query the DOM after actions that trigger re-renders instead of reusing a stale saved reference.

Practice what you learned

Was this page helpful?

Topics covered

#Testing#CypressStudyNotes#TestingQA#YourFirstCypressTest#Cypress#Test#Anatomy#Spec#StudyNotes#SkillVeris