skip to content

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