As an expert Cypress tester with over 10 years of experience across real devices and browsers, I often get asked – what are the best practices for CSS selectors in Cypress?
CSS selectors allow you to reliably target elements in the DOM for asserting, interacting with and validating UI components. Understanding how to create maintainable, reusable yet flexible selector patterns is critical to writing robust Cypress tests.
In this comprehensive guide, you’ll learn:
- Selector basics and syntax
- Best practices for crafting predictable and reliable selectors
- Examples for targeting elements, validating style and checking state
- Tips for handling dynamic data and reducing fragility
- Advanced techniques like selector playgrounds
- Integrating selector helper libraries like Testing Library
- Comparing CSS selectors vs XPath
- Cross browser and device tips
And much more! Let’s get started.
Selector Basics
Before jumping into Cypress specifics, let’s quickly recap CSS selector fundamentals. Below are some common types available:
ID Selector
Targets element by id
attribute:
#login-button
Class Selector
Targets elements by class
attribute:
.error-message
Type Selector
Targets DOM elements by node name:
input
Attribute Selectors
Target elements with specific attributes/values:
[disabled]
[type=‘checkbox‘]
There are also combinators that allow chaining selectors to drill down and target nested elements:
header .nav ul > li a
And much more! For a complete selector reference see here.
Now let’s look at using CSS selectors specifically within Cypress for testing.
Best Practices
Based on testing many real world applications over the years, here are my top tips for maintainable selectors in Cypress:
Prefer ID/data attributes
IDs offer unique specificity so try targeting them first before less precise selectors:
// Good
cy.get(‘#submitButton‘)
// Avoid
cy.get(‘button[type="submit"]‘)
Use classnames sparingly
Classnames are reused so can lead to fragile/flaky selectors:
// Good
cy.get(‘.modal.login-modal‘)
// Avoid
cy.get(‘.modal‘)
Combine ID + classname
For added precision and flexibility:
cy.get(‘#loginForm.horizontal-layout‘)
This follows a pattern of targeting a more specific ID paired with a classname that may be reused across the app.
Prefer immediate children over deep nesting
Overly nested selectors are more prone to breakage when markup changes:
// Good
cy.get(‘.user-profile > .name‘)
// Avoid
cy.get(‘.user-profile .meta .fullname .name‘)
Use data attributes
Data attributes allow targeting elements without relying on presentation classes:
<button data-test="submit">Submit</button>
cy.get(‘[data-test="submit"]‘)
This reduces brittleness since CSS can change without impacting tests. I recommend using the prefix data-test
or data-cy
for selector hooks.
Create reusable custom commands
Wrap common selectors in helper commands to keep tests clean:
Cypress.Commands.add(‘login‘, () => {
cy.get(‘#loginEmail‘).type(‘[email protected])
cy.get(‘#loginPassword‘).type(‘password123‘)
cy.get(‘#loginButton‘).click()
})
cy.login() // Reusable login command
There are many more tips but these form a solid foundation for stable selectors in Cypress.
Examples
Let’s look at some real world examples using CSS selectors with Cypress across different test cases:
Targeting Elements
Selectors allow interacting with elements on the page:
cy.get(‘.action-button‘).click()
cy.get(‘#email‘).type(‘[email protected]‘)
Validating Style
Check element styling using asserted CSS properties:
cy.get(‘.error‘).should(‘have.css‘, ‘color‘, ‘rgb(255, 0, 0)‘)
State Testing
Target selectors to make state assertions:
// Element is visible
cy.get(‘.modal‘).should(‘be.visible‘)
// Element contains text
cy.get(‘h1‘).should(‘contain‘, ‘Dashboard‘)
// Element is checked
cy.get(‘#termsCheckbox‘).should(‘be.checked‘)
There are many more examples across different test scenarios – I encourage you to check the references at the end for more.
Now let’s move on to some more advanced tips and tricks!
Dynamic Data
Dealing with dynamic data is one of the top challenges with CSS selectors. Let’s take an e-commerce site as an example – product listings will have dynamically generated IDs.
Instead of:
cy.get(‘#product-342938‘) // Bad!
A better option is to leverage data attributes as static anchor points combined with contextual selectors:
cy.get(‘[data-test="product"‘)
.contains(‘My Cool Product‘) // Validate name
.should(‘have.class‘, ‘instock‘) // Check stock status
This keeps tests robust even when underlying IDs change across runs.
Some other good strategies for dynamic data:
- Generating ID lists before test runs
- Using regex selectors
- Creating helper functions that search elements
- Abstracting common selectors
The key is building resilience into scripts early on.
Reducing Selector Fragility
Selector brittleness leading to test flakiness is a constant pain point. Some top tips to minimize:
Abstract environments
Use tools like BrowserStack to test across different OS, devices and browsers. Fix selectors that break early.
Analyze usage
Enable Cypress instrumentation to understand where selectors are used across tests. Then optimize ones with high usage first.
Use selector playground
SELECTORPLAYGROUND allows interactively experimenting with selectors which is invaluable for test design.
Add retries
Automatically retrying failed tests with different selectors helps manage flakiness.
Create identifier maps
Maps abstract UI elements to concrete selectors, offering insulation when markup changes.
While no test suite is bulletproof, following Cypress best practices goes a long way in taming CSS selector chaos!
Selector Libraries
Writing reliable custom selectors from scratch can be tricky. Helper libraries like Cypress Testing Library (CTL) provide an excellent foundation:
Simplifies Guidelines
CTL encapsulates many of the best practices we covered like preferring data attributes and avoiding nesting. Makes it easier for teams.
Readable Selectors
Selectors clearly express purpose and content of elements rather than implementation details.
Automatic Retries
CTL automatically retries failed locator searches helping manage dynamic data.
Page Object Pattern
CTL recommends separating selectors from test code following page object patterns for reusability.
Works With Cypress
The Cypress flavor has full Cypress integration enabling you to continue using existing workflows.
Here is an example CTL selector:
// Login page object
export default class LoginPage {
emailInput() {
return cy.findByTestId(‘email‘)
}
passwordInput() {
return cy.findByTestId(‘password‘)
}
submitButton() {
return cy.findByRole(‘button‘, { name: /submit/i })
}
}
Much cleaner! While CTL is great, also consider tools like Applitools, Percy and Screenster for visual testing and selector generation.
CSS Selectors vs XPath
While CSS locators have good support in Cypress, sometimes testers ask about using XPath selectors as an alternative. Some key differences:
Readability
CSS selectors follow a terser, more friendly syntax that is quicker to interpret. XPath has steeper learning curve.
Maintenance
Refactoring CSS selectors tends to be simpler compared to long winding XPath strings spread across tests.
Targeting
XPath offers the ability to transverse complicated DOM structures and target obscured elements. CSS can occasionally have issues here.
Performance
Retrieving elements via short, simple CSS locators puts less load on the browser compared to complex XPath searching.
So in summary — CSS selectors work great for the majority of use cases thanks to better usability. But also leverage XPath where helpful using tools like Cypress GUI Automation which adds support.
Cross Browser Strategies
While selector strategies we’ve covered also apply across environments, properly testing in different browsers/devices deserves special attention:
- Audit selectors in multiple browsers early while updating tests
- Use emulators and remote testing services like BrowserStack to access wide range of platforms
- Analyze CSS usage using Chrome DevTools coverage across environments
- Follow progressive enhancement principles – layer on fixes for specific browsers
- Use libraries like Modernizr.js that handle cross browser deviations
- Parameterize configurations to easily switch domains, ports etc across runs
Investing in multi-platform verification goes a long way for selector resilience.
Okay, we’ve covered a ton of ground on unlocking the full potential of CSS selectors in Cypress! Let’s quickly recap key learnings:
- Match reliability and uniqueness by combining ID, classname + data attributes
- Minimize brittleness through stateful targeting, custom commands and helper libraries
- Tackle dynamic data with contextual selectors and test retries
- Compare pros and cons of CSS selector and XPath approaches
- Build cross browser confidence through expanded test coverage
And much more! For next steps, I recommend reviewing the Cypress Best Practices guide, integrating tools like BrowserStack and experimenting with selectors hands-on using Selector Playground.
Happy testing! Let me know if you have any other selector questions.