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.
// 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
1. What testing framework provides the describe and it functions used in Cypress spec files?
2. Why is should() generally preferred over a raw Chai expect() for UI assertions?
3. What is the purpose of a beforeEach hook in a Cypress spec?
4. Where are static JSON test data files conventionally stored in a Cypress project?
5. What common pattern combines cy.fixture() with cy.intercept()?
Was this page helpful?
You May Also Like
The Cypress Test Runner
A tour of the interactive Cypress Test Runner: the command log, DOM snapshots, selector playground, and debugging tools that make writing tests fast.
Installing Cypress and Project Setup
How to add Cypress to a JavaScript project, understand the folder structure it scaffolds, and configure it for your app.
Cypress Architecture: Why It's Different
A deeper look at the two-process architecture behind Cypress, how the Node server and browser driver work together, and the trade-offs that result.