What can a Cypress test do to the app under test that an out-of-process runner cannot?
answer
- What the test can touch without asking
- The app's globals are already in scope
- Commands yielding window and document
- Replacing the application's own functions and timers
- The bundled jQuery, and when it runs
basics
~20 sIt can touch the application's live objects directly. cy.window() yields the app's real window, cy.stub() replaces its own functions, and cy.clock() takes over the page's timers -- no expression is shipped anywhere and no result is serialized back.
solid answer
~40 sBecause the spec runs inside the page, the application's objects are simply in scope. `cy.window()` yields the app's own `window`, so `.its()` and `.invoke()` read a store or call a method on it; `cy.document()` does the same for the document. `cy.stub()` and `cy.spy()` replace or wrap the application's own functions to force a branch such as an empty list or a failed request. `cy.clock()` overrides the page's `setTimeout`, `setInterval` and `Date`, and `cy.tick()` then advances time so a poll fires immediately. `Cypress.$` is the bundled jQuery and reads the DOM synchronously -- but it is not a queued command, so it runs on its own line rather than after the `cy` commands above it. A runner outside the browser reaches the same things only by shipping an expression in and serializing a result back.
code
javascript · 14 linesdescribe('shipment console pilot', () => {
it('renders a delivery exception without waiting for the poll', () => {
cy.clock()
cy.visit('/shipments')
cy.window()
.its('shipmentConsole.feed')
.invoke('push', { id: 'SH-4471', status: 'EXCEPTION' })
cy.tick(30000)
cy.contains('[data-testid="shipment-row"]', 'SH-4471')
.should('have.attr', 'data-status', 'EXCEPTION')
})
})go deeper
Know that a Cypress test can read the page's window and document directly, and be able to name at least one command that does it.
Walk through cy.window(), .its(), .invoke(), cy.stub() and cy.clock(), and say what each one replaces in a test that would otherwise have to click through the interface or wait for real time.
Explain what native access does not cover, where a spec still has to leave the browser, and show judgement about which reach-ins are worth the coupling they create.
Set the team's expectation: this leverage is real, and it also lets a suite drift away from testing the product. That trade needs a stated position, not a habit.
## The privilege, stated precisely Because the spec executes inside the page, every object the application creates is an object the test already holds. There is nothing to fetch and nothing to decode, which turns a family of otherwise awkward test problems into ordinary JavaScript. The commands that expose it are few and worth knowing by name: - **`cy.window()`** yields the application's own `window`. Chain `.its('key')` to read what the app hung there and `.invoke('method', arg)` to call it. - **`cy.document()`** yields the page's `document` the same way. - **`cy.stub()` and `cy.spy()`** replace or wrap a function the application owns, so a branch can be forced -- an empty result, a rejected promise, a third-party widget's callback -- without arranging the real conditions that would produce it. - **`cy.clock()`** overrides the page's `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` and `Date`; **`cy.tick(ms)`** then advances that clock synchronously, so a thirty-second poll fires at once. - **`Cypress.$`** is the jQuery that Cypress bundles, querying the application's DOM synchronously at the moment the line runs. An out-of-process runner is not shut out of all of this, but it reaches it by shipping an expression into the page and getting a serialized result back. Anything not serializable -- a function, a class instance, a live node -- has to be flattened first, and the round trip is per interaction. In Cypress there is no round trip, because there is no boundary to cross. ## What this changes in a real spec Consider a team running a two-week Cypress pilot against a shipment tracking console while deciding whether to migrate the suite. Three scenarios that were previously expensive collapse: 1. **A delivery exception arriving over a poll.** Instead of waiting out the poll interval, the pilot spec calls `cy.clock()` before `cy.visit()`, pushes the record into the console's own feed with `cy.window().its(...).invoke(...)`, then calls `cy.tick()` and asserts on the rendered row. 2. **The empty state.** Rather than emptying a shared database, the spec stubs the function the console calls for its shipment list and forces it to return nothing. 3. **A third-party map widget.** Instead of driving an unautomatable canvas, the spec calls the widget's own method to move the viewport, then asserts on what the console renders around it. Each one used to need either a fixture pipeline or a sleep. Neither is needed once the test is a co-tenant of the page. ## The trap: synchronous is not the same as ordered `Cypress.$` runs on the line where it appears. It is not one of the queued `cy` commands, so it does not wait for the ones written above it. This is the classic first-week bug in a pilot: - Written directly after `cy.visit()`, `Cypress.$('...')` queries the page that was there **before** the navigation and returns an empty result. - Moved inside a `.then()` callback, or applied to an element a `cy` command already yielded, it reads the DOM the test actually means. `cy.window()`, `cy.document()` and the rest are queued commands and do not have this problem. ## What native access does not buy Being inside the page is not a general escape hatch, and interviewers often probe exactly here: | It does not give you | Why, and what to use instead | | --- | --- | | Server-side modules | The spec is browser JavaScript, so it cannot open a database connection or import a Node module. Send that work to `cy.task()`, or make an HTTP call with `cy.request()` | | Native OS input | Events are dispatched in the browser, not at the operating-system level | | A second origin | Sharing the page's realm means sharing its origin boundary; another origin needs `cy.origin()` | | A second browser | Native access is access to *this* page, not to another driven browser | ## Using the leverage without hollowing out the test The same power that removes the boundary can quietly remove the coverage. The workable habit is to **reach in while arranging and assert through the interface a user has**: seed the console's feed by calling into it, then assert that the row appears on screen with the right status, not that the feed array has the right length. A spec written the other way round passes because the setup ran, which is a fact you already knew. The assertion chainers in that second form -- `have.attr`, `be.visible` -- come from the Chai and Chai-jQuery libraries Cypress bundles, so nothing about the habit costs extra tooling.
- Where does a Cypress test still have to leave the browser?Anywhere the page itself cannot go. A spec is browser JavaScript, so it cannot open a database connection or import a server-side module. That work goes to `cy.task()`, which runs in the Node process behind the browser, or to `cy.request()`, which makes an HTTP call outside the application's own code.
- Why does cy.clock() start the application's clock at the Unix epoch?`cy.clock()` swaps the page's time functions for controllable versions, and with no argument the fake clock starts at timestamp 0 -- so `new Date()` in the application reads January 1st 1970. Pass a timestamp, as `cy.clock(now)`, whenever the application renders or branches on the current date. Call it before `cy.visit()` so timers registered on page load are covered too.
saying these in an interview costs you the question
- Thinks cy.window() returns a serialized copy of window
- Expects Cypress.$ to wait for queued cy commands
- Says native access means importing server-side modules
- Cannot name anything beyond the DOM a test reaches
- Believes cy.clock() makes real elapsed time pass faster