Cypress: first steps β
Cypress runs the tests inside the browser, next to the application (its architecture explains its strengths and limits): immediate visual feedback, excellent debugging and an API that reads itself. It's one of the most widespread E2E tools, and the one I've worked with the longest.
Install and start β
npm install -D cypress
npx cypress open # the interactive runner: pick a browser and watch tests run
npx cypress run # headless mode: the pipeline oneThe structure it generates:
cypress/
βββ e2e/ # the tests: *.cy.ts
βββ fixtures/ # test data in JSON
βββ support/
βββ commands.ts # custom commands
βββ e2e.ts # global hooks
cypress.config.ts # configuration (baseUrl, timeouts, retriesβ¦)The first thing you always configure: the baseUrl in cypress.config.ts, so tests do cy.visit('/catalog') instead of absolute URLs.
The first test β
describe('Product catalog', () => {
it('shows the 1 Gbps fiber detail', () => {
cy.visit('/catalog');
cy.contains('Fiber 1 Gbps').click();
cy.url().should('include', '/products/fiber-1gbps');
cy.get('[data-cy=monthly-price]').should('be.visible');
});
});Two things already visible here: commands chain, and assertions go through should.
The essential commands β
| Command | What it does |
|---|---|
cy.visit('/path') | Navigate (relative to baseUrl) |
cy.get('[data-cy=β¦]') | Select elements |
cy.contains('text') | Select by visible text |
.click() / .type('β¦') / .select('β¦') | Interact |
.should('be.visible') / .should('contain', 'β¦') | Assert (with automatic retry) |
cy.intercept(β¦) | Spy on or stub network requests |
The idea you must internalize: retry-ability β
Cypress automatically retries cy.get and should until they pass or the timeout expires (4 s by default). That's why you almost never need manual waits: cy.get('[data-cy=order-status]').should('contain', 'Active') will wait for the status to arrive on its own.
The flip side: commands are not normal promises. You don't await cy.get(...) or store its result in a variable; you chain, or use .then(). It's the number one source of confusion when starting out.
Selectors with judgment β
The recommended selector is a dedicated attribute (data-cy), which doesn't change when styles or copy change:
<button data-cy="confirm-order">Confirm order</button>cy.get('[data-cy=confirm-order]').click();CSS-class or structural selectors (div > ul li:nth-child(3)) are what turn a suite into a house of cards.
Common mistakes β
- Fixed
cy.wait(3000). If you must wait, wait for something concrete: an assertion or a network alias (covered in patterns). - Treating commands like normal sequential code: mixing variables and
cy.*without.then()produces tests that "pass" while testing nothing. - Tests that depend on each other (test B uses what test A created): every test prepares its own state.
- Ignoring the interactive mode.
cypress openwith time-travel over each command is the best E2E debugging tool there is; use it before fighting logs.
Key idea
Cypress's learning curve is short because the API reads itself, but the mental model (chaining + automatic retry) is different from a normal script. Internalize that first and the tool plays on your side.