Cypress Best Practices

Good habits keep a Cypress test suite reliable and easy to maintain as a project grows. This final topic gathers practical recommendations covered throughout this course into one place.

Prefer Stable Selectors

Selectors based on data-testid attributes survive design changes better than class names. A visual redesign often changes classes and layout but rarely touches dedicated test attributes.

cy.get('[data-testid="checkout-button"]')

A Simple Way to Picture It

Think of a good selector as a house address rather than a description of the house's paint color. Paint color changes over time, but the address usually stays fixed. Stable selectors work the same way, staying reliable even after visual updates.

Keep Tests Independent

Each test should run successfully on its own, without depending on a previous test's leftover state. Independent tests can run in any order without breaking, which also makes parallel execution possible.

Independent Tests Diagram

Bad Pattern
  Test 1 creates data -> Test 2 depends on that data -> Fragile chain

Good Pattern
  Test 1 sets up its own data -> Test 2 sets up its own data -> Both run safely alone

Avoid Fixed Wait Times

Fixed waits using cy.wait with a number slow down tests and hide real timing issues. Condition-based waiting through assertions and network aliases produces faster and more reliable results.

Reset State Before Each Test

Use hooks like beforeEach to reset the application into a known state, such as visiting the starting page or clearing stored data. This practice avoids one test's leftover state from silently affecting the next test.

beforeEach(() => {
  cy.visit('/')
  cy.clearCookies()
})

Use Custom Commands for Repeated Steps

Bundling repeated actions like login into a custom command reduces duplication across many test files. Updating one command fixes every test using it, saving significant maintenance time.

Write Clear Test Descriptions

A test name should describe expected behavior in plain language, such as "shows an error for an invalid email." Clear names let team members understand a failure's meaning without reading the underlying code first.

Test Behavior, Not Implementation Details

Focus assertions on what a user actually sees and experiences, rather than internal technical details. Checking that a success message appears matters more than checking an internal variable's exact value.

Balance API and UI Testing

Use API requests to set up data quickly instead of clicking through multiple forms just to reach a starting state. Reserve full UI testing for the specific behavior actually being verified in that test.

cy.request('POST', '/api/products', { name: 'Test Item', price: 10 })
cy.visit('/shop')

Keep an Eye on Test Suite Speed

A slow test suite discourages developers from running tests frequently during development. Regularly review long-running tests and look for unnecessary waits or repeated setup steps that could be optimized.

Review Failures Promptly

A failing test left unresolved for a long time often gets ignored entirely by the team, defeating its purpose. Treat every failure as a signal worth investigating quickly, whether it points to a real bug or a flaky test needing a fix.

Summary of Practices

Stable Selectors  -> Fewer breaks after design changes
Independent Tests -> Safer parallel execution
Condition Waits    -> Faster and more reliable runs
Custom Commands    -> Less duplication across files
Clear Names        -> Easier debugging for the whole team

Key Points

  • Stable selectors like data-testid attributes resist design changes.
  • Independent tests avoid depending on leftover state from earlier tests.
  • Condition-based waiting beats fixed wait times for speed and reliability.
  • Custom commands reduce duplication for frequently repeated actions.
  • Clear test names and prompt failure reviews keep a test suite healthy.

Leave a Comment

Your email address will not be published. Required fields are marked *