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.
