In a Cypress test, when is `Cypress.$` safe to use, and why is it empty right after `cy.visit()`?
answer
- Which lines run now, which run later
- It is not a cy command
- The spec body only builds a queue
- Bundled jQuery, exposed on the Cypress object
- Callbacks are the safe place for it
basics
~20 sCypress.$ 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.
solid answer
~40 s`Cypress.$` is the jQuery function Cypress bundles, exposed for **synchronous** access to the application's DOM. It is not a queued command: it runs the moment the JavaScript line executes, while a `cy.visit('/rooms')` above it has merely been added to the command queue and has not navigated yet. So `const $rooms = Cypress.$('.room-card')` on the next line queries the *previous* page and hands back an empty jQuery object. It also waits for nothing. The safe places for it are inside a `.then()` callback, where the preceding commands have already finished, and in the browser console while debugging a paused test. Reaching for it to decide what the test does next is where suites get into trouble — a synchronous existence check only means something if the DOM has already settled.
code
javascript · 10 linescy.visit('/rooms')
// runs now, before cy.visit() has navigated: empty collection
const $tooEarly = Cypress.$('.room-card')
expect($tooEarly).to.have.length(0)
cy.get('body').then(($body) => {
// runs after cy.visit() finished, so this reads the room list
expect($body.find('.room-card')).to.have.length(12)
})go deeper
Recall that Cypress bundles jQuery and exposes it as Cypress.$, and that it is not a cy command. That alone explains most of the surprise the first time it returns nothing.
Explain the command queue: cy.visit() is enqueued while Cypress.$ executes inline. Being able to say which lines run now and which run later is really what is being tested.
Bring an example where a synchronous read made a test lie - a branch taken on a page that had not finished rendering - and say what you replaced it with and how you found it.
Decide whether synchronous DOM reads belong in your suite at all, what review rule you apply to them, and what you offer engineers instead when they reach for one.
## `Cypress.$` is jQuery, not a command Cypress bundles jQuery and exposes the function itself as `Cypress.$`. Anything you can do with jQuery you can do with it, including jQuery's utility functions rather than just its element queries — `Cypress.$.each` and `Cypress.$.ajax` are jQuery's `jQuery.each` and `jQuery.ajax`. What it is **not** is a `cy` command. It never joins the command queue, it takes no `timeout`, and it does no waiting of any kind. There is a second, easily confused name. `cy.$$` is a wrapper around jQuery's element-querying half, so `cy.$$('.room-card')` works but `cy.$$.each([1, 2, 3], fn)` does not. Both read the application's DOM synchronously; neither is a queued command. | | queued | waits for anything | returns | |---|---|---|---| | `cy.get('.room-card')` | yes | yes | a Cypress chainer whose subject is a jQuery set | | `Cypress.$('.room-card')` | no | no | a jQuery object, immediately | | `cy.$$('.room-card')` | no | no | a jQuery object, immediately | ## Why it is empty right after `cy.visit()` A Cypress spec body runs top to bottom **once**, and what it does is build a queue. `cy` calls put work on that queue; plain JavaScript statements execute as the file is read. So in: ```js cy.visit('/rooms') const $rooms = Cypress.$('.room-card') ``` the order of events is: 1. `cy.visit('/rooms')` is **enqueued**. No navigation has happened. 2. `Cypress.$('.room-card')` executes **now**, against whatever page is currently loaded — the blank page a fresh test starts on, or the leftover page from the previous command. 3. `$rooms` is an empty jQuery object, `$rooms.length` is `0`. 4. Only after the spec body finishes does Cypress start draining the queue and actually navigate. Nothing about this is intermittent. It fails the same way every run, which is at least a mercy — the same misunderstanding applied to `let rooms; cy.get(...).then(($el) => { rooms = $el })` and a read on the next line produces the identical `undefined`. ## Where `Cypress.$` earns its place - **Debugging from DevTools.** Pause a test with `cy.pause()` and query the room list from the browser console to see what is genuinely rendered. This is the use the API exists for. - **Inside a `.then()` callback.** By the time a callback runs, the commands before it have finished, so a synchronous read is meaningful. Even then, working from the subject you were handed — `cy.get('body').then(($body) => $body.find('.room-card'))` — keeps the reasoning local. - **Non-DOM jQuery utilities.** jQuery's `each` over a fixture array, reached as `Cypress.$.each`, is legitimate and has nothing to do with the page at all. ## The trap it exists to enable The tempting use is conditional testing: *if a promo banner is showing on the room list, dismiss it, otherwise carry on.* ```js // fragile - the banner may simply not have rendered yet if (Cypress.$('.promo-banner').length) { cy.get('.promo-banner .dismiss').click() } ``` A synchronous read answers "is it there **at this instant**", and the honest answer during an app's render is often "not yet". The branch is decided before the page has settled, so the test takes a different path on a slow CI machine than it does on your laptop — and it is the branch that is wrong, not the click, which makes it miserable to diagnose. The DOM has to already be in a known, settled state for a synchronous existence check to mean anything, and if you can guarantee that, you usually did not need the branch. ## What to reach for instead Almost every reason people give for using `Cypress.$` has a queued equivalent that behaves the way they wanted in the first place: | what you wanted | the queued way | |---|---| | "read the room cards after navigating" | `cy.get('.room-card').then(($cards) => ...)` | | "grab the element into a variable" | keep working inside `.then()`, or alias it with `.as()` | | "check the promo banner is gone" | assert it, so the assertion decides when the page has settled | | "do jQuery work on a subject" | you already hold jQuery inside the callback - no global needed | The common thread is that a queued command decides *when* it looks, and a synchronous read decides that for you at the worst possible moment. If a piece of test code needs to know what the page looks like, the queue is where that knowledge is reliable. ## The sentence to say out loud "`Cypress.$` is the bundled jQuery function, it runs the instant the line runs rather than joining the command queue, so anything above it that is still queued has not happened yet." Everything else about the API — why it is empty after `cy.visit()`, why it is fine inside `.then()`, why branching on it is a flake generator — falls straight out of that one sentence.
- How does `cy.$$` differ from `Cypress.$` in a Cypress test?`Cypress.$` is the jQuery function itself, so jQuery's utility methods such as `jQuery.each` and `jQuery.grep` are available on it. `cy.$$` wraps only jQuery's element-querying half, so `cy.$$('.room-card')` works but `cy.$$.each([1, 2, 3], fn)` fails. Both read the application's DOM synchronously, and neither is a queued Cypress command.
- What is a legitimate use of `Cypress.$` in a hotel booking suite?Debugging: pause the test with `cy.pause()` and query the room list from the browser console to see what actually rendered. Inside a `.then()` callback it can also do jQuery work on a subject you already hold, such as reading `data-` attributes off a set of cards. What it should not do is decide branching behaviour on a page that is still rendering.
saying these in an interview costs you the question
- Thinks Cypress.$ waits for elements the way cy.get does
- Calls Cypress.$ right after cy.visit and trusts the result
- Uses Cypress.$ to branch on whether an element exists
- Confuses Cypress.$ with cy.$$ and calls jQuery.each on it