## TL;DR

Prefer `data-testid` (or `data-cy`) attributes over CSS classes and structure. Test IDs are a contract between app and tests; classes and DOM position change for styling reasons and break tests.

## Error

```text
(Not an error; a convention decision. The cost of getting it wrong: every CSS refactor breaks tests.)
```

## Steps

1. Add `data-cy` attributes to interactive elements in the app. Expected: stable hooks the tests own.
2. Select with them: `cy.get('[data-cy=submit]')`. Expected: readable, stable selectors.
3. Reserve CSS for layout-structural targets with no better hook. Expected: the minority of selectors.
4. Never select by dynamic classes (CSS modules hashes) or `:nth-child` position. Expected: the brittle patterns banned.
5. Lint for banned patterns in code review. Expected: conventions hold.

## When to use

- Writing Cypress selector conventions.
- Choosing attributes for a new app.

## When not to use

- Playwright (prefer role locators there).
- Third-party widgets without test IDs (CSS with comments).

## Tool compatibility

- Cypress 10 through 14; `data-cy` convention.

## Variant phrasings

### data-cy vs css selectors

The core comparison; test IDs win.

### Cypress stable selectors

The goal; dedicated attributes are the mechanism.

## Why it happens

CSS serves styling; tests need stability. One attribute serving both masters breaks constantly. Dedicated test attributes decouple them.

## Edge cases

- `data-cy` attributes ship to production; that is fine and common.
- Get the frontend team to own adding them; it is a testability feature.
- For text content, `cy.contains()` is sometimes better than any attribute.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_BZw0PEDizqhFiR_SN2N5Nw
