How to Write Good Test Selectors
3 Tips for Improving your Automation Selectors
Over the years I’ve written a lot of automation tests - some good, some flakey. Choosing the right selector is key to building non-flakey tests.
Here are my top tips:
Tip 1 — Choose the right selector
Here’s a list of selectors in order of preference:
1. Custom attribute
Always use custom attributes that are specifically added for the purpose of automation. These are isolated and unlikely to change, resulting in non-flakey tests. Data-cy in Cypress is a great example of this.
cy.get('[data=cy="submit"]')
EDIT: 1.5 Accessibility-focused selectors
Zachary mentioned in the comments about a shift towards accessibility-focused selectors which better simulate user interactions with the page. More details here.
const input = screen.getByLabelText('Username');
2. ID
IDs are good as they are at least unique. However they are open to change and could break your tests if a developer changes the ID without realising it’s linked to a test.
cy.get('#submit')
3. CSS
CSS selectors can change frequently, but they can work if you use good selectors (see Tip 2 below). Cypress doesn’t recommend using these so it might be better to speak to your developer about adding a custom attribute.
cy.get('.submit')
4. XPath
Never use XPath. These are extremely flakey and if anything in the DOM changes, your test will break:
cy.get('/html/body/div[1]/div/form/span')
Tip 2 — Use better CSS selectors
Often I see people working their way down the DOM tree using CSS selectors. This may work today, but is highly likely to break down the line. But there are still some ways to make them less flakey:
1. Finding elements within elements
Do some research on how CSS selectors work. W3School has some great resources.
A common mistake is writing a CSS selector like this:
cy.get('.tableStyle > .rowStyle > .divStyle > .buttonStyle')
Yes, it helps you select the specific button within a table. But if anything between the table and the button changes (the row or div), your test will fail.
Instead try find the button anywhere within the table like this:
cy.get('.tableStyle .buttonStyle')
Much less flakey.
Understanding how to find elements within elements using selectors is key:

2. Use selectors to filter out elements
Use selectors like :disabled
, :nth-child(n)
or :checked
to filter down and find the specific elements you need.

This can be much cleaner to find the element(s) you’re looking for, rather than looping through elements in code.
Tip 3 — Work with developers
It sounds simple, but working with developers is key to writing better selectors and automation.
If you use a tool like Cypress, which is JavaScript based, why not make use of the JavaScript experts around you to help improve your code.
If you can’t find an element, rather than struggling and using a flakey selector, work with you developer to get a custom or better attribute added.
You’ll thank yourself later!