Q25 of 48 · Cypress

Explain the difference between cy.wrap and cy.then.

CypressMidcypresswrapthencommand-queuemid

Short answer

Short answer: `cy.wrap(value)` puts a JavaScript value into the Cypress chain so you can run Cypress commands on it. `cy.then(fn)` pulls the *current subject* out of the chain so you can run plain JS on it. They're inverses: wrap goes JS → Cypress, then goes Cypress → JS.

Detail

Cypress chains operate on a "subject" — cy.get('.foo') yields a jQuery-wrapped element as the subject, and the next command (.click(), .should(...)) operates on that subject. The two boundaries between Cypress's queue and plain JavaScript are wrap and then.

cy.wrap(value) takes any value (an element, a promise, a primitive, an object) and yields it as the chain subject. Useful when:

  • You have a value from outside the chain and want to assert on it: cy.wrap({ id: 1 }).should('have.property', 'id', 1).
  • You're inside a .then and want to return to the chain: cy.then(($el) => { ... return cy.wrap($el); }).
  • You're working with a promise from a non-Cypress source: cy.wrap(somePromise).then(...).

cy.then(fn) is the opposite — it gives you the current subject as a JS value inside a callback:

cy.get('h1').then(($el) => {
  const text = $el.text();
  expect(text).to.include('Welcome');  // standalone Chai
  cy.wrap(text).should('eq', 'Welcome'); // back into the chain
});

The crucial difference: code inside .then doesn't auto-retry. Querying the DOM inside .then snapshots the value once; if the DOM updates, your snapshot is stale. So prefer chained .should for retry-sensitive checks, and reserve .then for situations where you genuinely need the value as plain JS.

// EXAMPLE

// cy.wrap — bring JS into the chain
const expectedUser = { id: 'u1', email: 'alice@x.com' };
cy.wrap(expectedUser).should('have.property', 'email', 'alice@x.com');

// cy.then — pull subject out as JS
cy.get('[data-test=cart-row]').then(($rows) => {
  expect($rows).to.have.length(3);
  // Plain JS — no retry
  const ids = [...$rows].map((el) => el.dataset.id);
  expect(ids).to.deep.equal(['p1', 'p2', 'p3']);
});

// Combined — read text, transform, assert via chain
cy.get('[data-test=total]')
  .invoke('text')                    // string
  .then((s) => parseFloat(s.replace('£', '')))
  .should('be.greaterThan', 0);

// WHAT INTERVIEWERS LOOK FOR

Knowing they're inverses (JS ↔ chain), and the consequence: code in `.then` doesn't retry, so use chained `.should` for state that may change.

// COMMON PITFALL

Putting assertions inside `.then` and getting flake when the DOM updates after the snapshot — `.should` would have retried.