skip to content

Scoping and Traversal

Working from a subject you already hold: scoping a block with within, walking to descendants, parents and siblings, filtering a set down, and crossing into an open shadow root.

on this pageshow

explore

questions

5

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

level: juniorimportance: must knowfreq 70%

answer

  1. Where does a query start looking?
  2. A callback that moves the root
  3. One element in, same element out
  4. What does cy.root() report in there?
  5. cy.request() and aliases ignore it

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.

solid answer

~40 s

`.within(callback)` records the current subject as the scope for the length of the callback and re-roots Cypress's document-level element queries at it: `cy.get('.price')` matches only descendants of that element, `cy.contains('Book')` matches only text inside it, and `cy.root()` — normally `<html>` — yields the scoped element, so `cy.root().submit()` submits the scoped form. Nothing else moves: `cy.visit()`, `cy.request()`, `cy.window()`, `cy.focused()` and `cy.task()` never searched the page, and `cy.get('@alias')` is an alias lookup rather than a page search. The subject must be exactly one element — calling `.within()` on a multi-element `cy.get()` result throws "can only be called on a single element", so narrow with `.first()` or `.eq(n)` first, or loop with `.each(($card) => cy.wrap($card).within(...))`.

code

javascript · 14 lines
javascript
cy.get('.room-card').first().within(() => {
  cy.contains('Deluxe King')
  cy.get('.price').should('be.visible')
  cy.get('.book-btn').click()
  cy.root().should('have.class', 'room-card')
})

// one scope per card, using .each() + cy.wrap() to satisfy
// the single-element rule
cy.get('.room-card').each(($card) => {
  cy.wrap($card).within(() => {
    cy.get('.price')
  })
})

go deeper

for a junior

Be ready to write a .within() block from memory and say which commands inside it now search a smaller part of the page.

for a middle

Explain the mechanics: the subject is stored as the scope while the callback enqueues commands, cy.root() reports it, and exactly one element is required.

for a senior

Show judgement about when a scope earns its callback — several queries against one container — versus a single .find() reach, and name what scoping cannot reach.

for a principal

Own the convention: where scoped blocks belong in a suite's helpers, and what it costs a reader when every test wraps a single query in a block.

## What a Cypress element query is rooted at Every Cypress element query starts from a **root node**. By default `cy.get()` matches against the whole document of the application under test, and `cy.contains()` starts at `<body>`. On a hotel booking page that renders one `.room-card` per room, `cy.get('.book-btn')` therefore matches every card's button at once, and the next action command has more than one element to act on. `.within(callback)` changes that root. It takes the subject it is chained off, records it as the **scope** for as long as the callback's commands are being enqueued, and hands the document-level queries that new root: ```javascript cy.get('.room-card').first().within(() => { cy.get('.book-btn').click() // only this card's button cy.contains('Free cancellation') // only text inside this card }) ``` ## Exactly what is re-rooted, and what is not - **`cy.get(selector)`** searches only descendants of the scoped element. - **`cy.contains(text)`** matches only text inside the scoped element, instead of starting at `<body>`. - **`cy.root()`** yields the scoped element rather than `<html>`, which is how you act on the scope itself — `cy.root().submit()` submits the scoped guest form. - **`cy.get('@alias')`** is *not* scoped. An alias resolves a stored subject rather than searching the page, so it yields the same thing inside the block as outside it. - **Anything that never searched the page** — `cy.visit()`, `cy.request()`, `cy.window()`, `cy.focused()`, `cy.task()`, `cy.intercept()` — is untouched. That last group is what surprises people. `.within()` is a DOM-scoping construct, not a sandbox: it does not isolate network stubs, clock control, storage or the window object. Only the queries that would otherwise walk the document are affected. ## The single-element rule `.within()` requires a subject of exactly one element. Given more, Cypress refuses to guess and throws: > `.within()` can only be called on a single element. Your subject contained 12 elements. Narrow > down your subject to a single element (using `.first()`, for example) before calling `.within()`. Three ways to satisfy that: 1. **Narrow positionally** — `cy.get('.room-card').first().within(...)`, or `.eq(2)`, or `.last()`. 2. **Narrow by content** — `cy.contains('.room-card', 'Deluxe King').within(...)` picks the one card you actually mean, which survives reordering of the room list. 3. **Iterate** — `cy.get('.room-card').each(($card) => { cy.wrap($card).within(() => { ... }) })`. `.each()` hands the callback a raw jQuery element; `cy.wrap()` puts it back into the command chain so `.within()` has a Cypress subject to scope to. ## Nested scopes `.within()` blocks nest. Entering an inner block saves the outer scope and replaces it; when the inner block's commands are done the previous scope is restored, so a query written after the inner block but still inside the outer one is scoped to the outer element again. In practice that means: - Scoping a room card, then a fare row inside it, behaves the way the markup reads. - Leaving a block never "leaks" the inner root outward. - Two nested blocks on the same element still restore correctly, because the runner tracks the chain that produced each scope, not just the element. ## `.within()` versus chaining `.find()` Both narrow where a query looks. They differ in shape, not in power. | | `.within(cb)` | `.find(selector)` | |---|---|---| | written as | a callback block | one link in a chain | | queries per root | many | one | | what you hold afterwards | the scoped element | the element you found | | best for | a card or form you query repeatedly | a single reach into one descendant | For one lookup, `cy.get('.guest-form').find('#email').type('[email protected]')` is shorter and leaves you holding the input. For five queries against the same guest form, `.within()` states the root once instead of five times. Neither is more "correct"; a block of one query is just a longer way to write `.find()`. ## Reading a scoped block in the Command Log The runner makes the scope visible: the block appears as a `within` entry and every command the callback enqueued is indented beneath it. That is the quickest confirmation that a query really did run scoped, and the quickest way to spot a command that was written after the closing parenthesis and therefore ran against the whole document again. If a query you expected to be scoped appears at the outer level, the parenthesis is in the wrong place. ## Two consequences worth recognising early - A scoped query **cannot see an element rendered outside the scope**, even one plainly visible on screen — a popup mounted at the end of `<body>` is not a descendant of the form. - The subject after the block is **still the container**. `.within()` yields what it was given, so a command written after the closing parenthesis acts on the card or form, not on anything the callback found. Neither is a bug, and neither produces an error that says "scope". Both are the same fact seen from two sides: the block changes where queries look and nothing else about the chain.

  • Does a Cypress `.within()` block scope `cy.request()` or `cy.intercept()` as well?
    No. `.within()` only re-roots the queries that would otherwise search the document — `cy.get()`, `cy.contains()` and `cy.root()`. `cy.request()`, `cy.intercept()`, `cy.window()`, `cy.task()` and `cy.visit()` never looked at the page in the first place, so they behave identically inside and outside the block. It scopes the DOM, not the test.
  • How do you run the same Cypress `.within()` block over every room card on the page?
    Chain `.each()` and re-wrap each element: `cy.get('.room-card').each(($card) => { cy.wrap($card).within(() => { /* queries */ }) })`. `.each()` yields a raw jQuery element to the callback, and `cy.wrap()` turns it back into a Cypress subject so `.within()` sees exactly one element, which is what it requires.

It is like giving a search a folder instead of the whole drive: the same query text, a smaller place to look, and files outside the folder simply do not exist for it.

saying these in an interview costs you the question

  • Thinks .within() isolates network stubs, timers or storage, not just DOM queries
  • Calls .within() on a whole multi-element result and expects it to loop
  • Believes cy.get('@alias') resolves relative to the .within() scope
  • Says .within() scopes cy.get() but not cy.contains()
  • Assumes the callback's return value becomes the chain's next subject
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

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

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