Cypress selects elements with cy.get (CSS selectors) and cy.contains (by text). The recommended best practice is to target dedicated test attributes like data-cy or data-testid rather than brittle CSS/XPath.
cy.get('.btn-primary') works but couples tests to styling that changes often; cy.contains('Submit') couples to copy that changes with translations. Cypress recommends adding data-cy (or data-test/data-testid) attributes purely for testing, e.g. <button data-cy='submit'>, and selecting cy.get('[data-cy=submit]'). This isolates tests from CSS/content churn and makes intent explicit. You can chain to scope selectors (cy.get('[data-cy=form]').find('input')), and .within() to scope commands to a container. Avoid overly-specific selectors and auto-generated class hashes.
A design refactor renames every Bootstrap class; tests using cy.get('.btn.btn-lg.btn-primary') all break, while tests using cy.get('[data-cy=checkout]') keep passing because the test attribute was untouched.
Your team's Cypress tests break every sprint after CSS changes. What's the fix and why is it more stable?