skip to content

Finding Elements

How a Cypress test says which element it means: the commands that search the page, the helpers that narrow the result, and the failure that follows when the node you named is gone.

on this pageshow

explore

questions

20

In Cypress, how do you find an element by its `data-cy` test attribute?

level: juniorimportance: must knowfreq 82%

answer

  1. Cypress adds no test-id command
  2. Plain CSS, matched jQuery-style
  3. Square brackets around the attribute
  4. Prefix, suffix and substring operators exist
  5. Quote values that are not identifiers

basics

~20 s

Pass 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 s

Cypress 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 lines
javascript
cy.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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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
open as a page

In Cypress, why does a re-render detach the element your chain already holds?

level: juniorimportance: must knowfreq 80%

basics

~20 s

A Cypress chain carries a concrete DOM node, not the selector that found it. A re-render throws that node away and mounts a fresh one, so the node your chain still holds is no longer in the document.

open as a page

In Cypress, what does `cy.get('.room-card')` yield when the page renders 12 room cards?

level: juniorimportance: must knowfreq 78%

basics

~20 s

One jQuery-wrapped collection holding all 12 room-card elements, not a single DOM node and not an array. Its length is 12, and the whole set becomes the subject the next command in the chain receives.

open as a page

In Cypress, what does `.within()` change about the `cy.get()` calls inside its callback?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Inside a .within() callback Cypress re-roots its document-level element queries at the subject, so cy.get() and cy.contains() search only that element's descendants and cy.root() yields it. The subject must be exactly one element.

open as a page

In Cypress, how do `.find()`, `.filter()` and `.children()` differ on a set of elements?

level: middleimportance: must knowfreq 58%

basics

~10 s

Cypress's .find() searches descendants of every element at any depth and .children() only direct children, while .filter() keeps the elements already in the set that match and .not() keeps those that do not.

open as a page

In Cypress, what does setting `selectorPriority` via `Cypress.ElementSelector` control?

level: middleimportance: should knowfreq 44%

basics

~20 s

It sets the order of attributes Cypress prefers when it writes a selector for you, in Cypress Studio, the Selector Playground and cy.prompt(). It never changes what cy.get() matches, and it replaces Cypress's default order rather than extending it.

open as a page

In a Cypress spec, what does `@testing-library/cypress` add to `cy`?

level: middleimportance: should knowfreq 58%

basics

~20 s

It registers Testing Library's asynchronous find queries as Cypress commands - cy.findByRole, cy.findByLabelText, cy.findByTestId and their All variants - so a spec can locate elements by role and accessible name. It is a third-party add-on, imported once in the support file.

open as a page

In Cypress, how does `cy.contains()` choose which element to yield, and what does a selector argument change?

level: middleimportance: should knowfreq 58%

basics

~20 s

It yields one element: the first deepest match, promoted to an enclosing input[type=submit], button, a or label when there is one. Passing a selector as the first argument filters the candidates and switches that promotion off.

open as a page

Why does a Cypress command chained after a `.within()` block run against the parent?

level: middleimportance: should knowfreq 44%

basics

~10 s

.within() always yields the subject it was given, never anything the callback found. Cypress discards the callback's return value, including a cy.wrap() inside it, so the next command acts on the container element.

open as a page

In Cypress, why is a jQuery element captured in .then() unsafe after an action?

level: seniorimportance: should knowfreq 47%

basics

~20 s

The callback runs once and is never retried, so it captured a snapshot of one node. Once an action re-renders that node away, the snapshot still answers reads with stale values, letting an assertion pass against a row that left the page.

open as a page

In Cypress, what does a detached failure that says 'while this command was executing' point at?

level: seniorimportance: should knowfreq 50%

basics

~20 s

It points at the application's timing, not at your chain. Cypress re-ran the query while waiting for the element to become actionable and got nothing attached back, so the element genuinely vanished between being found and being acted on.

open as a page

In Cypress, a room row is aliased with `.as('firstRoom')` and then re-renders. What does `cy.get('@firstRoom')` yield?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The freshly rendered row. By default Cypress stores the chain of queries that produced the subject rather than the element itself, so reading the alias re-runs those queries against the current DOM instead of returning a stale reference.

open as a page

In a Cypress test, when is `Cypress.$` safe to use, and why is it empty right after `cy.visit()`?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Cypress.$ is the bundled jQuery function and it queries the page synchronously, the instant its line runs. cy.visit() above it has only been queued, so it reads the previous page. Use it inside a callback, or from DevTools while debugging.

open as a page

In a Cypress `.within()` block, why can `cy.get()` time out on a visible element?

level: seniorimportance: should knowfreq 36%

basics

~20 s

A .within() scope restricts queries to the scoped element's DOM descendants, not to what looks nearby on screen. A date picker or modal rendered into a container at the end of body sits outside it, so the query never matches.

open as a page

How far should a Cypress project's `selectorPriority` list constrain generated selectors?

level: principalimportance: should knowfreq 26%

basics

~20 s

Far enough that generated code reaches for your test attribute first, not so far that Cypress has nothing left to fall back on. A one-entry list forces Cypress to improvise; leaving class and nth-child in lets fragile selectors into the repo.

open as a page

How far should a Cypress suite go to absorb detached-node failures the app causes?

level: principalimportance: should knowfreq 32%

basics

~20 s

Absorb the ordinary case by re-querying, which is normal Cypress hygiene. Escalate when a region is replaced so constantly that no query can win, because that churn costs real users their focus and scroll position too.

open as a page

In Cypress, what does Cypress.dom.isDetached($el) return, and what can it not do?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

It returns a boolean, the exact negation of Cypress.dom.isAttached: true unless every node you pass is connected to a document with a live window. It is a snapshot read that never waits, never re-queries and never repairs anything.

open as a page

In Cypress, what do `cy.title()`, `cy.document()` and `cy.window()` yield to the next command?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

cy.title() yields the document.title string, cy.document() yields the application's window.document, and cy.window() yields its window object. All three read the app under test, and none of them yields a DOM element you can chain element commands onto.

open as a page

In Cypress 16, what happens to a support file calling `Cypress.SelectorPlayground`?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Every method on it throws. Cypress.SelectorPlayground.defaults() reports that it was renamed to Cypress.ElementSelector.defaults(), and getSelector() reports that it was removed. The rename landed in Cypress 15.0.0, and because the call sits in the support file, every spec fails.

open as a page

In Cypress, how do you click a button that lives inside a component's open shadow root?

level: seniorimportance: nice to knowfreq 32%

basics

~10 s

Chain .shadow() off the host element and keep traversing: cy.get('room-card-widget').shadow().find('.book-btn').click(). Alternatively pass { includeShadowDom: true } to that one query, or set the includeShadowDom config value, which defaults to false.

open as a page