In Cypress, what do `cy.title()`, `cy.document()` and `cy.window()` yield to the next command?
answer
- One of the three gives you a string
- They read the app, not the runner
- Ask what each hands the next command
- Element commands need element subjects
- TypeScript users extend one window interface
basics
~20 scy.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.
solid answer
~40 sThese are Cypress's page-level queries. `cy.title()` yields `document.title` as a plain **string**; `cy.document()` yields the application's `window.document`; `cy.window()` yields its `window`, typed as `AUTWindow` so TypeScript users can extend `Cypress.ApplicationWindow` for properties the app sets. All three read the application under test, not the Cypress runner's own page. Because none of them yields a DOM element, element commands reject the subject: `cy.title().contains('Seaview')` fails with *`cy.contains()` failed because it requires a DOM element. The subject received was: ...*. Assert on the value instead — `cy.title().should('include', 'Seaview')` — or open a callback with `.then((win) => ...)` to read what the app put on `window`. `cy.document()` is the usual way to reach the real document when a check has no element to hang on.
go deeper
Recall which of the three gives you a string and which give you objects. Asserting on the page title is the everyday use and the one worth having at your fingertips.
Explain that these read the application under test rather than the runner, and why an element command rejects a string subject. That distinction is the real point of the question.
Show judgement about when a page-level query is the right tool at all - a check with no element to anchor on - and name the async trap of reading a window value into an outer variable.
Weigh how much a suite should know about the application's internal window state, and say where that coupling costs more than it saves once the app is refactored.
## Three page-level queries, three kinds of subject `cy.get()` and `cy.contains()` find elements. Cypress also ships three queries that hand you the page itself, and the thing to be clear about is what each one yields, because that decides what you are allowed to chain onto it. | query | yields | typical use on a booking app | |---|---|---| | `cy.title()` | the `document.title` **string** | asserting the browser tab reads *Seaview Hotel - Rooms* | | `cy.document()` | the application's `window.document` object | reading `contentType`, or reaching the real document when no element fits | | `cy.window()` | the application's `window` object | reading state the app published, e.g. `win.bookingState` | All three read the **application under test**, not the Cypress runner's own page. That distinction matters more than it sounds: the runner renders your app in a frame alongside its own UI, and these queries deliberately target the app. `cy.window()` yields an `AUTWindow`, typed as `Window & typeof globalThis & Cypress.ApplicationWindow`. In TypeScript you extend the `Cypress.ApplicationWindow` interface to declare properties your app sets, and the yielded window then type-checks: ```ts declare namespace Cypress { interface ApplicationWindow { bookingState: { nights: number } } } ``` ## Why an element command refuses a string subject The mistake this leaf exists to prevent is chaining an element command onto `cy.title()`: ```js cy.title().contains('Seaview') // fails ``` `.contains()` needs a DOM element to search inside. It receives a string, and Cypress reports: ``` cy.contains() failed because it requires a DOM element. The subject received was: > `Seaview Hotel - Rooms` ``` The fix depends on what you meant: - **Asserting on the tab title** — assert on the string: `cy.title().should('include', 'Seaview')`. - **Finding that text on the page** — you wanted `cy.contains('Seaview')`, which starts from the document and yields an element. The same rule covers `cy.getCookies().contains('_key')` and any other chain that hands a non-element subject to an element command. Ask what the previous query yielded, and the error stops being mysterious. ## Reading what the app put on `window` Booking flows frequently stash state the UI does not render — a session id, the selected nights, an analytics payload. `cy.window()` is how a test reaches it: ```js cy.window().then((win) => { expect(win.bookingState.nights).to.equal(3) }) ``` Two habits keep this honest: 1. **Read it inside the callback.** Assigning `win.bookingState` to an outer variable and using it on the next line gives you `undefined`, because the callback has not run yet — the command was only queued. This is the same async trap that catches people with `.then()` generally. 2. **Prefer what the user can see.** Every assertion against `window` couples your suite to the app's internals, so it survives a refactor only as long as that property does. It is a good tool for state that has no visible representation and a poor substitute for asserting on the room list the guest actually sees. `cy.document()` fills the gap where you need the real document and no element makes sense — checking `contentType`, or handing the document to a helper. ## The app's page, not the runner's It is worth being explicit about what "currently active" means. A Cypress test executes inside the browser alongside the application, and the spec file also has a `window` and a `document` of its own — the runner's. Writing `document.title` in a spec does **not** give you the hotel app's title; it gives you whatever page the test code itself is running in. That is the whole reason these three commands exist. `cy.title()`, `cy.document()` and `cy.window()` resolve against the application under test, and they resolve at the moment the command runs, so after a `cy.visit('/rooms')` earlier in the chain they see the room list rather than the page before it. Reaching for the bare global instead is a quiet way to write an assertion that passes for a reason that has nothing to do with your app. ## When these come up - The title assertion is the everyday one, and it is the reason `cy.title()` exists as a shortcut rather than making you write `cy.document().then((d) => d.title)`. - `cy.window()` shows up in tests that verify something the UI hides, and in setup where a test has to observe rather than drive. - `cy.document()` is the rarest of the three in day-to-day specs. None of the three is a differentiator on its own. What an interviewer is really checking with this question is whether you can say what a command **yields** — the habit that explains most confusing Cypress failures, including the one above.
- In Cypress, why does `cy.title().contains('Seaview')` fail?`cy.title()` yields a string and `.contains()` requires a DOM element subject, so Cypress throws *cy.contains() failed because it requires a DOM element*, printing the string it received. Use `cy.title().should('include', 'Seaview')` to assert on the title, or `cy.contains('Seaview')` if you actually meant to find that text on the page.
- How do you read a value the hotel app sets on `window` from a Cypress test?Open a callback: `cy.window().then((win) => { expect(win.bookingState.nights).to.equal(3) })`. In TypeScript, declare the property on `Cypress.ApplicationWindow` so the yielded `AUTWindow` type recognises it. Remember the callback runs when the command reaches the front of the queue, so a variable assigned inside it is not readable on the line below.
saying these in an interview costs you the question
- Thinks cy.window returns the Cypress runner's own window
- Chains .contains or .click onto cy.title
- Expects cy.title to yield the title element
- Reads a window value into a variable and uses it immediately