Q34 of 48 · Cypress
How does Cypress handle asynchronous code under the hood?
Short answer
Short answer: Cypress maintains an internal command queue. Each `cy.*` call appends to the queue synchronously, returning a chainable. After the test function returns, the runner drains the queue — executing each command, awaiting its async work, snapshotting state, then moving to the next.
Detail
Reading Cypress's source helps demystify the model. Internally, the framework holds a per-test command queue. Each command is a { name, args, fn } record. cy.get doesn't query the DOM at call time; it pushes a command onto the queue and returns a Chainable proxy.
When the test callback finishes synchronously, Cypress runs the queue:
- Pop the first command.
- Invoke its function with the previous subject as input.
- The function returns a value or a promise.
- If a promise, Cypress awaits it (with the appropriate timeout).
- Pass the result as the subject to the next command.
- Snapshot the DOM if the command is "snapshottable" (most are).
This explains several otherwise-puzzling behaviours:
Retry-ability is implemented in the query layer. cy.get and assertions both internally retry the underlying jQuery query until it succeeds or the timeout fires. The chain itself doesn't loop — the individual command does.
.then exits the queue because its callback runs synchronously with the resolved subject; the callback's return value becomes the new subject. If you return a Cypress chain from inside .then, Cypress re-enters the queue.
Promises don't compose with the queue — await fetch(...) from inside a test runs on a different timeline than the Cypress queue. The fix is cy.wrap(fetchPromise), which adds the promise to the queue.
async test functions break things because Cypress sees the test return a promise and either ignores it (the test continues) or treats it as a command to await (depending on version). The recommended pattern is a synchronous test body that just enqueues commands.
The senior signal: knowing the queue is internal and the test code is just declarative. You don't write async tests; you build a command list that Cypress executes.
// WHAT INTERVIEWERS LOOK FOR
// COMMON PITFALL
// Related questions