
If you’ve ever tried testing a web app, you know how tricky it can be to grab the exact element you want. That’s where Cypress selectors come in. They help you pinpoint buttons, inputs, links, or any part of the page so your tests run smoothly without breaking.
However, selectors do more than just find elements. With the right ones, you can check if buttons are clickable, inputs accept text, or styles appear as expected. You can combine selectors to target complex layouts, use attributes to grab dynamic elements, and even test styles directly. This makes writing tests more precise and ensures your app behaves exactly as intended.
In this article, I will show how to use Cypress selectors effectively for testing web apps.
Cypress selectors are how you locate elements on a web page so your tests can interact with them. Cypress primarily uses CSS selectors, which means you can target elements using IDs, classes, attributes, or their position in the DOM. Without selectors, Cypress wouldn’t know which button to click, which input to type into, or which element’s style to check.
For example, #login-button targets a button with that ID, while .form-input selects all elements with that class. You can also use attribute selectors like [type="submit"] to pick specific buttons or inputs. This ensures Cypress interacts with the exact element you intend, even when the page has multiple similar elements.
Cypress selectors also let you check styles or element states. Using .error-message makes it easy to verify whether an error appears after a failed login, while [disabled] ensures buttons are correctly disabled when required. This makes tests precise and reduces failures caused by hitting the wrong element.
When testing a web app in Cypress, the first thing you need is a reliable way to locate elements. CSS selectors are the foundation for this. Using the right selector ensures Cypress interacts with exactly the element you want, even if the page has multiple similar elements.
Here are the basics you should start with:
1. ID Selector (#id)
Use this when an element has a unique ID on the page. This is the most precise and fastest way to select a single element because IDs should only appear once in the DOM. Ideal for buttons, forms, or any element that has a unique identifier.
Example in Cypress:
cy.get('#submit-btn').click();
// clicks the button with ID 'submit-btn'
2. Class Selector (.class)
Use this when you want to select elements sharing a common style or behavior. Classes are often used for input fields, buttons, or repeated components. If multiple elements share the same class, Cypress will target all of them, so sometimes you’ll need to combine it with other selectors.
Example in Cypress:
cy.get('.form-input').type('hello');
// types 'hello' into all input fields with class 'form-input'
3. Type Selector (element)
Use this when you want to select elements by their HTML tag, like buttons, links, or inputs. This works best when the element type is unique within the container or section you’re testing. It’s a simple way to grab elements without relying on IDs or classes.
Example in Cypress:
cy.get('button').contains('Login').click();
// clicks the button element containing text 'Login'
Attribute selectors let you target elements based on their HTML attributes. They are extremely useful when elements don’t have unique IDs or classes, or when you want more precise targeting. Here are the main types:
1. Has Attribute Selector ([attr])
Use this when you just need to check for the presence of an attribute, regardless of its value. This is helpful for verifying elements that have dynamic attributes or optional properties.
Example in Cypress:
cy.get('[data-active]') // selects all elements that have the 'data-active' attribute
2. Exact Attribute Selector ([attr="value"])
Use this when you want to select elements with an attribute set to a specific value. This ensures you target exactly the element you intend.
Example in Cypress:
cy.get('[data-active="true"]') // selects elements where 'data-active' is exactly 'true'
3. Begins With Selector ([attr^="value"])
Use this when an attribute starts with a specific value. This is helpful for dynamic IDs or attributes that follow a predictable prefix pattern.
Example in Cypress:
cy.get('[data-color^="r"]') // selects elements whose 'data-color' starts with 'r'
4. Ends With Selector ([attr$="value"])
Use this when an attribute ends with a specific value. This is useful for elements where the suffix of an attribute determines its role or state.
Example in Cypress:
cy.get('[data-color$="r"]') // selects elements whose 'data-color' ends with 'r'
5. Substring Selector ([attr*="value"])
Use this when an attribute contains a specific substring anywhere. This is flexible for dynamic attributes or partial matches.
Example in Cypress:
cy.get('[data-color*="e"]') // selects elements where 'data-color' contains the letter 'e'
Combination selectors allow you to combine selectors or use relationships between elements. They are essential for targeting elements within complex or nested layouts.
1. Descendant Selector (ancestor descendant)
Use this to select elements that are nested anywhere inside a parent element. This is useful when you want to interact with elements inside a container.
Example in Cypress:
cy.get('.container p') // selects all p tags inside elements with class 'container'
2. Child Selector (parent > child)
Use this to select direct children of a parent element. This avoids accidentally selecting deeper nested elements.
Example in Cypress:
cy.get('.container > p') // selects only p tags directly under '.container'
3. General Sibling Selector (element1 ~ element2)
Use this to select all sibling elements that come after a specific element. This is helpful when you need to target elements that follow a certain node.
Example in Cypress:
cy.get('.box ~ p') // selects all p tags that come after '.box'
4. Adjacent Sibling Selector (element1 + element2)
Use this to select the element immediately following another element. This is precise for cases where only the next sibling matters.
Example in Cypress:
cy.get('.box + p') // selects the p tag directly after '.box'
5. Or Selector (selector1, selector2)
Use this to select elements that match at least one of multiple selectors. This is handy when multiple elements could fulfill the same role.
Example in Cypress:
cy.get('.square, #rect') // selects elements with class 'square' or ID 'rect'
6. And Selector (selector1.selector2)
Use this to select elements that match all specified selectors at once, such as multiple classes or a combination of type and class.
Example in Cypress:
cy.get('div.black') // selects div elements that also have the class 'black'
Locating elements correctly is the first step in writing reliable Cypress tests. Follow these steps to find elements efficiently:
Step 1: Inspect the Element in the Browser
Open your web app in the browser, right-click the element you want to test, and choose Inspect. This opens the DevTools, showing the element’s HTML, classes, IDs, and attributes.
Example:
// Suppose the element has <button id="submit-btn" class="btn primary">
cy.get('#submit-btn').click();
Step 2: Decide the Most Reliable Selector
Check if the element has a unique ID—this is usually the best option. If not, look for a class, attribute, or combination that uniquely identifies the element.
Example using class:
cy.get('.btn.primary').click();
// clicks the button with both 'btn' and 'primary' classes
Step 3: Test Using Attributes for Dynamic Elements
If IDs or classes are dynamic, use attribute selectors like data-cy, type, or name to target the element reliably.
Example:
cy.get('[data-cy="login-button"]').click();
// clicks the login button using a custom data attribute
Step 4: Use Combination Selectors for Nested Elements
For elements inside a container or deeply nested, combine selectors or use child/descendant selectors to pinpoint the element.
Example:
cy.get('#login-form > .btn').click();
// clicks the button directly inside the login form
Step 5: Validate the Selector
Before writing full test logic, check if the selector selects the correct element using Cypress commands like .should('exist') or .should('be.visible').
Example:
cy.get('[data-cy="submit"]').should('be.visible');
// ensures the element is visible
Following these steps ensures your Cypress tests target the correct elements reliably, reducing flaky tests and saving time debugging.
After you’ve located an element with a CSS selector, you often want to verify its visual appearance, visibility, or state. Cypress allows you to check CSS properties directly, making it easy to ensure the UI behaves as expected. Follow these steps:
Step 1: Select the Element Using a CSS Selector
Use the selectors you already identified to target the element. This ensures you’re testing the correct element before checking its styles.
Example:
cy.get('#submit-btn') // selects the button with ID 'submit-btn'
Step 2: Check Visibility
Ensure the element is visible on the page. This is a basic but essential style check to verify that the element is rendered correctly.
Example:
cy.get('#submit-btn').should('be.visible');
// confirms the button is visible
Step 3: Check Specific CSS Properties
Use .should('have.css', property, value) to verify exact styles like color, font size, or background.
Example:
// Check the button’s background color
cy.get('#submit-btn').should('have.css', 'background-color', 'rgb(0, 123, 255)');
// Check font size
cy.get('#submit-btn').should('have.css', 'font-size', '16px');
Step 4: Check Classes or State Changes
Sometimes elements change style based on state (e.g., hover, active, error). Verify this by checking classes or attribute changes.
Example:
// Verify error class appears on invalid input
cy.get('.form-input').type('wrong').blur();
cy.get('.form-input').should('have.class', 'input-error');
Step 5: Combine Selectors for Nested or Dynamic Styles
If the element is inside a container or dynamically generated, combine selectors to target it precisely before checking styles.
Example:
cy.get('#login-form > .btn').should('have.css', 'color', 'rgb(255, 255, 255)');
// checks text color of the button inside login form
Step 6: Use Assertions for Multiple Properties
You can check multiple CSS properties in sequence to ensure full style verification.
Example:
cy.get('#submit-btn')
.should('have.css', 'background-color', 'rgb(0, 123, 255)')
.and('have.css', 'font-size', '16px')
.and('be.visible');
Once you’ve verified styles and states using CSS selectors, it’s important to remember that visual appearance and behavior can vary across browsers and devices. A button that looks correct in one browser may render differently in another, or styles may break on smaller screens.
To ensure your tests are truly reliable, it’s helpful to run them in real-world conditions. Tools like BrowserStack allow you to execute Cypress tests across multiple browsers, screen sizes, and operating systems, giving you confidence that your selectors and styles work consistently for all users.
Sometimes, a selector that looks correct in the browser fails in Cypress. Understanding why can save you hours of debugging. Follow these steps to troubleshoot:
Step 1: Check If the Element Exists at the Time of the Test
Cypress runs tests faster than the page may render. If the element isn’t present yet, the selector will fail. Use cy.wait() or .should('exist') to verify.
Example:
cy.get('#submit-btn').should('exist');
// ensures the button exists before clicking
Step 2: Verify the Selector Is Correct
Inspect the element in the browser and make sure the ID, class, or attribute matches. Remember, classes and IDs may change dynamically.
Example:
cy.get('.btn.primary').click();
// confirms the element has the expected classes
Step 3: Avoid Selecting Hidden or Detached Elements
Elements not visible or removed from the DOM cannot be interacted with by default. Use .should('be.visible') to debug or .click({ force: true }) if necessary.
Example:
cy.get('#hidden-btn').should('be.visible');
// checks visibility before interaction
Step 4: Use More Specific Selectors When Needed
If multiple elements match your selector, Cypress may target the wrong one. Combine selectors to make them unique.
Example:
cy.get('#login-form .btn[data-cy="submit"]').click();
// precisely targets the submit button inside login form
Even experienced testers sometimes run into issues with CSS selectors. Knowing the common mistakes can save you from flaky tests, hard-to-find bugs, and wasted debugging time. Here are the key pitfalls to watch out for:
CSS selectors are the foundation of reliable Cypress tests. Choosing the right selector whether an ID, class, attribute, or combination lets you locate elements accurately, interact with them, and verify their styles or state. Avoiding common mistakes like dynamic IDs, overly broad selectors, or ignoring element visibility makes your tests more robust, maintainable, and less prone to errors.
Ensuring your selectors work in real-world scenarios requires testing across multiple browsers and devices. Tools like BrowserStack allows you to validate your web app in real environments, across different browsers, screen sizes, and operating systems. This makes your tests more reliable and ensures your application behaves as expected for all users.
Get visual proof, steps to reproduce and technical logs with one click
Try Bird on your next bug - you’ll love it
“Game changer”
Julie, Head of QA
Try Bird later, from your desktop