Mastering CSS Selectors in Cypress

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.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.