Cypress Best Practices for Test Writing: The Official Style Guide

Write Cypress tests using accessibility-first selectors, stub network calls with cy.intercept, and centralize UI strings via i18n files to create maintainable, deterministic test suites.

The cypress-io/cypress repository maintains rigorous standards for test writing in its official Testing Strategy and Style Guide. Following these Cypress best practices for test writing ensures your end-to-end, component, and integration tests remain readable, accessible, and resilient against UI changes.

Adopt a Test-Driven Workflow

The Cypress team recommends a strict test-driven development approach. Write a failing test before implementing the feature, then iterate until the test passes. According to the guides/testing-strategy-and-styleguide.md, this applies across all test types: unit tests for isolated functions, integration tests for feature-level logic with mocked dependencies, component tests for UI pieces, and end-to-end (E2E) tests for full-stack flows.

Choose the Appropriate Test Type

Selecting the correct test type maximizes confidence while minimizing execution time.

  • Unit tests validate isolated functions and business logic without UI dependencies.
  • Integration tests verify feature-level behavior using mocked external services.
  • Component tests, utilizing helpers from packages/app/src/mountFragment.ts, mount individual UI fragments and mock GraphQL data.
  • E2E tests exercise complete user flows through the entire application stack.

Prioritize Accessibility-First Selectors

Avoid fragile CSS selectors that break when styling changes. Instead, locate elements by their accessible names as implemented in the Cypress testing strategy.

Use cy.contains() with specific element types or findByLabelText for form fields:

// ✅ Prefer an accessible selector
cy.contains('button', 'Log In').click()

// ✅ Use a label-based selector for a form field
cy.findByLabelText('Email address').type('user@example.com')

When accessibility attributes cannot expressionally target an element, augment with data-cy attributes. This pattern appears throughout the style guide for non-interactive elements:

// ✅ Combine data-cy with a label for a non-interactive element
cy.get('[data-cy="success-toast"]')
  .should('be.visible')
  .and('contain.text', 'Operation successful')

Centralize Reusable Strings

Hard-coding UI text in tests creates maintenance burdens during localization. The Cypress style guide recommends pulling interface text into i18n JSON files (such as en-US.json) and importing those constants in both the application and test suites. This ensures consistency across the codebase and simplifies updates when text changes.

Optimize Assertions and Visibility Checks

Keep assertions close to their target elements to minimize brittleness. The guides/testing-strategy-and-styleguide.md specifically warns against deep DOM traversals that couple tests to implementation details.

Verify visibility only when the element is not being interacted with. Implicit assertions during interactions (like .click() or .type()) automatically check for visibility and enabled state, making explicit visibility checks redundant in those contexts.

Leverage Cypress Testing Library Judiciously

While Cypress Testing Library provides semantic queries like findByRole and findByLabelText, the official guide recommends using cy.contains for most cases. Reserve findByRole and findByLabelText for scenarios where they provide clear accessibility value, keeping tests concise and readable.

Handle Network Requests and Eliminate Flakiness

Guard against flaky tests by using deterministic selectors and avoiding timing-related hacks. Never use arbitrary cy.wait() calls with millisecond values.

Instead, stub network requests using cy.intercept, implemented in packages/driver/src/cy/intercept.ts, to control external dependencies:

// ✅ Stub a network request in an E2E test
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
cy.get('[data-cy="user-list"]').should('contain', 'John Doe')

This pattern ensures tests wait for specific network conditions rather than arbitrary timeouts, creating reliable, deterministic test execution.

Implement Visual Regression Testing

Avoid asserting against hard-coded CSS values, which break when design systems evolve. The Cypress testing strategy recommends using Percy or similar visual snapshot tools to detect unintended UI changes. This approach validates visual appearance without coupling tests to specific style implementations.

Summary

  • Write tests first using a test-driven workflow across unit, integration, component, and E2E layers.
  • Select elements by accessible names using cy.contains or findByLabelText, falling back to data-cy attributes when necessary.
  • Centralize strings in i18n files to maintain consistency between application and test code.
  • Stub network calls with cy.intercept to eliminate flakiness and external dependencies.
  • Use visual snapshots rather than CSS assertions to validate UI appearance.

Frequently Asked Questions

How do I select elements in Cypress without creating brittle tests?

Prefer accessibility-first selectors such as cy.contains('button', 'Submit') or cy.findByLabelText('Email') over CSS classes or IDs. When accessibility markup is insufficient, use data-cy attributes, which remain stable during styling changes. This approach, documented in guides/testing-strategy-and-styleguide.md, decouples your tests from implementation details.

What is the difference between component tests and E2E tests in Cypress?

Component tests mount individual UI fragments in isolation using utilities like packages/app/src/mountFragment.ts, mocking GraphQL data and dependencies. E2E tests navigate the actual application URL and exercise the full stack including real routing and backend integration. Use component tests for rapid UI feedback and E2E tests for critical user flows.

How should I handle waiting for network requests in Cypress?

Never use arbitrary cy.wait() with millisecond values. Instead, use cy.intercept() to stub the route and assign an alias, then wait for that specific alias using cy.wait('@alias'). This pattern, implemented in packages/driver/src/cy/intercept.ts, ensures tests wait precisely for the expected network call rather than timing delays.

Where should I store reusable test strings in a Cypress test suite?

Store UI text in your application's i18n JSON files (such as en-US.json) and import those constants into your test files. This practice, recommended in the testing style guide, ensures that when application text changes, tests automatically reference the updated values without manual synchronization.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →