Skip to content

Cypress Commands

Cypress commands look like simple, synchronous function calls, but they behave very differently under the hood. This lesson explains the command queue, how commands resolve, and why that matters for writing correct tests.

Commands Are Enqueued, Not Executed Immediately

When you write cy.get(...), Cypress does not run it on that line. Instead, it adds the command to an internal queue and returns immediately. The entire chain of commands in a test is built up first, then Cypress works through the queue asynchronously, one command at a time.

This is why you cannot use a plain if statement based on a command's result on the very next line, the result does not exist yet when that line executes; it only exists once Cypress actually runs that queued command.

cy.get('[data-cy="title"]'); // enqueued, not run yet
cy.log('this line runs immediately, before the get above executes');

Counterintuitively, cy.log() here can appear to run "before" cy.get() resolves, because enqueuing and executing are separate steps.

Command Categories

Queries:   cy.get(), cy.find(), cy.contains()
Actions:   .click(), .type(), .check()
Assertions: .should(), .and()
Other:     cy.visit(), cy.request(), cy.intercept()
  • Query commands find elements and are automatically retried when chained with an assertion.
  • Action commands simulate real user interaction and wait for the target element to be actionable.
  • Assertion commands verify a condition and retry until it passes or the timeout is reached.
  • Some commands (like cy.request()) are not retried, since they represent a single, direct action.

Command Behavior Reference

How different categories of commands behave when queued and executed.

Command Type Example Retried Automatically?
Query cy.get(), cy.find() Yes, when followed by an assertion
Action .click(), .type() Waits for actionability, not the action itself
Assertion .should() Yes, until it passes or times out
Network cy.request() No, runs once
Utility cy.log(), cy.wrap() No

Visualizing the Command Queue

It helps to picture an entire it() block as building a to-do list before any of it actually runs. Every cy. call and chained command adds one item; only after the synchronous JavaScript in the test function finishes does Cypress start working through that list.

it('demonstrates the queue', () => {
  cy.visit('/');          // item 1
  cy.get('h1');           // item 2
  cy.log('queued third'); // item 3
  // The list is now built. Cypress executes items 1, 2, 3 in order.
});

Commands Vs. Plain jQuery Methods

Cypress commands are intentionally similar in spirit to jQuery, cy.get() behaves like $(), but with retry-ability layered on top. If you need the raw jQuery-wrapped element for something Cypress doesn't provide directly, .then() gives you access to it.

cy.get('[data-cy="price"]').then(($el) => {
  const raw = $el.text(); // plain jQuery/DOM access here
  expect(Number(raw.replace('$', ''))).to.be.greaterThan(0);
});

Use .then() sparingly, for logic Cypress's built-in commands and assertions genuinely cannot express.

Common Mistakes

  • Expecting cy.log() or plain console.log() placed after a command to reflect that command's already-resolved state.
  • Mixing native async/await promise patterns with the Cypress command queue in ways that break execution order.
  • Reaching for .then() for logic that a built-in Cypress assertion could express more simply.
  • Assuming a command "fails" the instant it's called, rather than after Cypress finishes retrying it.

Key Takeaways

  • Cypress commands are enqueued when called and executed later, in order, not immediately.
  • Query and assertion commands are automatically retried; some other commands run exactly once.
  • .then() gives access to a command's resolved, jQuery-wrapped subject when needed.
  • Thinking of a test as building a queue first, then executing it, resolves most "why doesn't this work" confusion.

Pro Tip

If a piece of logic in your test feels awkward to express with .then(), check whether a built-in assertion or a custom command could express the same intent more declaratively, retry-ability is usually lost the moment you drop into a .then() callback.