Let me show you the bug that made me stop trusting position-based selectors.
We had a test that added the first product to the cart:
cy.get('.product-card:nth-child(1) button').click();
It worked for months. Then the marketing team added a "Featured" product to the top of the page. Our test still passed. It was adding the wrong product to the cart, and the test could not tell the difference.
That is the worst thing a test can do. Not fail. Pass while being wrong.
nth-child(1) means "the first one in the list". It describes where the element sits. Positions change whenever someone edits the page.
Instead, use a selector that describes what the element is:
cy.get('[data-testid="add-to-cart"]').first().click();
A test id is an attribute the developers add to the app on purpose, just for testing. It looks like this in the HTML:
<button data-testid="add-to-cart">Add to cart</button>
Nobody moves it or renames it by accident, because it exists for one reason.
You can also find things by the text a user reads:
cy.contains('button', 'Add to cart').click();
This is good for buttons and links. Just know that if someone changes the wording, your test will fail. Usually that is correct, because the wording is part of what users see.
What to avoid: long CSS paths like div > div:nth-child(2) > span, and class names from your styling framework like .MuiButton-root-2891. Those class names are generated and change between builds.
Remember this: if your selector would break when someone reorders the page, it is the wrong selector.
Reference: Best Practices: Selecting Elements.