In Cypress, how do you find an element by its `data-cy` test attribute?
answer
- Cypress adds no test-id command
- Plain CSS, matched jQuery-style
- Square brackets around the attribute
- Prefix, suffix and substring operators exist
- Quote values that are not identifiers
basics
~20 sPass a CSS attribute selector to cy.get(), as in cy.get('[data-cy="room-card"]'). Cypress ships no dedicated test-id command: the attribute is ordinary markup and the selector is ordinary CSS, matched the way jQuery's $() matches, with data-cy carrying no special status.
solid answer
~40 sCypress has no `cy.getByTestId()`. You target a test attribute with an ordinary CSS attribute selector passed to `cy.get()` — `cy.get('[data-cy="room-card"]')` for the room list, `cy.get('[data-cy="check-in-date"]')` for the date picker. Because `cy.get()` queries the DOM the way jQuery's `$()` does, every CSS attribute operator is available: `[data-cy^="room-"]` for a prefix, `[data-test*="rate"]` for a substring, `[data-testid$="-total"]` for a suffix. Quotes around the value are optional only when it is a valid CSS identifier, so `[data-cy=room-card]` parses but `[data-cy="deluxe king"]` and any value starting with a digit must be quoted. Nothing in Cypress privileges `data-cy` over `data-test` or `data-testid` at query time — all three are just attributes. `cy.get()` then retries until the selector matches or its timeout expires, and it yields every match rather than the first.
code
javascript · 14 linescy.visit('/rooms')
// Ordinary CSS attribute selectors - Cypress adds no test-id command
cy.get('[data-cy="room-card"]').should('have.length', 12)
cy.get('[data-cy="check-in-date"]').type('2026-11-14')
cy.get('button[data-test="book-room"]').click()
// The value is not a valid CSS identifier, so it has to be quoted
cy.get('[data-cy="deluxe king"]').should('be.visible')
// Prefix, substring and suffix operators all work
cy.get('[data-cy^="room-"]')
cy.get('[data-test*="rate"]')
cy.get('[data-testid$="-total"]')go deeper
Be ready to write the selector on the spot. Interviewers ask you to click a room card by its data-cy attribute and want cy.get('[data-cy="room-card"]').click() without hesitation, not a hunt for a dedicated command.
Explain that cy.get() queries the DOM the way jQuery does, so CSS attribute operators such as ^=, $= and *= all work, and say exactly when an attribute value has to be quoted.
Be ready to say what a test-attribute selector costs when the attribute is not unique across a rendered list, and how you would confirm from a failing run what the selector actually matched.
Own whether the suite's selectors are hand-written or generated, because that decides whether Cypress's selectorPriority setting has any effect on them at all.
## The selector is CSS, not a Cypress feature `cy.get()` takes a selector string and queries the DOM the way jQuery's `$()` does. A **test attribute** — `data-cy`, `data-test`, `data-testid`, `data-qa` — is ordinary HTML, and `[data-cy="room-card"]` is an ordinary CSS **attribute selector**. Cypress adds no `getByTestId`-style command, and it gives no attribute name special standing at query time. On a hotel booking page the room list, the date picker and the guest form are all reached the same way: ```javascript cy.get('[data-cy="room-card"]') cy.get('[data-cy="check-in-date"]') cy.get('[data-cy="guest-form"]') ``` That is the whole mechanism. Everything below is CSS detail plus a few consequences specific to the Cypress runner. ## The operators an attribute selector gives you Because the string is CSS, every attribute operator the browser supports works inside `cy.get()`: | Selector | Matches a `data-cy` value that… | Example match | | --- | --- | --- | | `[data-cy="room-card"]` | equals `room-card` exactly | `room-card` | | `[data-cy^="room-"]` | begins with `room-` | `room-101`, `room-deluxe` | | `[data-cy$="-total"]` | ends with `-total` | `nightly-total`, `tax-total` | | `[data-cy*="rate"]` | contains `rate` anywhere | `rate-row`, `nightly-rate` | | `[data-cy~="deluxe"]` | contains `deluxe` as a space-separated word | `room deluxe` | | `[data-cy]` | exists at all, whatever its value | any element carrying it | You can compound the attribute with a tag or a class — `cy.get('button[data-cy="book-room"]')` — which narrows the match at the cost of coupling the selector back to the markup the attribute was added to insulate it from. Cypress's own documentation shows the same operators in use, for instance `cy.get('a[href*="questions"]')` and `cy.get('[id^=local-]')`. ## When the value needs quotes An unquoted attribute value has to be a valid CSS identifier. That covers most test ids, which is why `[data-cy=room-card]` parses fine. It stops covering them the moment the value contains anything unusual: - `[data-cy=room-card]` — fine; hyphens are legal inside a CSS identifier. - `[data-cy="deluxe king"]` — **must** be quoted; a space ends the identifier and the rest is a parse error. - `[data-cy="2-adults"]` — **must** be quoted; a CSS identifier cannot start with a digit. - `[data-cy="rate.nightly"]` — **must** be quoted; an unquoted `.` starts a class selector. Quoting always works, so the simplest rule is to quote every value and never think about it again. When the value is genuinely dynamic, build the string in JavaScript with a template literal rather than concatenating an unquoted fragment. ## The three attribute names are interchangeable to `cy.get()` `data-cy`, `data-test` and `data-testid` are three conventions for one idea, and at query time Cypress cannot tell them apart — all three are attributes and all three are matched by the same CSS. They differ only in provenance: `data-cy` is what Cypress's own documentation uses in its examples, `data-testid` is what Testing Library reads by default, `data-test` is what Cypress's real-world example application uses. Which one a project standardises on is a project decision, and the query syntax does not change either way. There is one place inside Cypress where the names are **not** equal, and it is not `cy.get()`. `Cypress.ElementSelector.defaults({ selectorPriority })` orders the attributes Cypress prefers when *it* generates a selector for you in Cypress Studio or the Selector Playground, and its default order starts `data-cy`, `data-test`, `data-testid`, `data-qa`. That list never touches a selector you wrote yourself. ## What the runner adds on top Two Cypress behaviours ride along with the plain CSS: 1. **`cy.get()` retries.** It re-queries the document until the selector matches or the command times out, so a room card that arrives after a fetch does not need an explicit wait around it. 2. **It yields every match.** `cy.get('[data-cy="room-card"]')` on a page of twelve rooms yields all twelve rather than the first, which matters as soon as the next command in the chain is an action. ## Where it goes wrong - **Reaching for a command that does not exist.** `getByTestId()` is Testing Library's name and `findElement()` is Selenium WebDriver's; neither is a Cypress command, and both throw. - **Assuming `data-cy` is privileged.** Renaming your attribute to `data-cy` changes nothing about how `cy.get()` behaves. - **Forgetting the value is unique per element, not per component.** If every room card in the list carries `data-cy="room-card"`, the selector is correct and still ambiguous for an action. - **Losing the quotes on an interpolated value.** A room name dropped into an unquoted selector produces a CSS parse error, and the message points at the selector rather than at the data that broke it.
- In Cypress, how do you match every room card whose `data-cy` value starts with `room-`?Use the CSS prefix operator inside `cy.get()`: `cy.get('[data-cy^="room-"]')`. `^=` matches values beginning with the string, `$=` matches values ending with it, and `*=` matches values containing it anywhere. All three are standard CSS attribute operators, and `cy.get()` accepts them because it queries the DOM the way jQuery's `$()` does rather than through a Cypress-specific matcher.
- In a Cypress test, can you combine a tag or class with a `data-cy` attribute selector?Yes. `cy.get()` takes any CSS selector string, so `cy.get('button[data-cy="book-room"]')` matches only buttons carrying that attribute, and `cy.get('.rate-row[data-testid="nightly-rate"]')` combines a class with an attribute. Compounding narrows the match but reintroduces the coupling the test attribute was added to remove: the selector now breaks if the element changes from a `button` to an `a`. Reach for it only when the attribute alone is genuinely not unique.
saying these in an interview costs you the question
- Claims Cypress has a built-in cy.getByTestId() command
- Thinks data-cy is special to cy.get() and data-test is not
- Never quotes attribute values, then blames Cypress for the parse error
- Believes selectorPriority changes which elements cy.get() matches