Both .should() and expect() ultimately use the same Chai assertion library underneath, but they behave very differently in a Cypress test. This lesson compares them directly with practical guidance on choosing between them.
Same Assertions, Different Retry Behavior
.should('have.text', 'Done') and expect($el.text()).to.equal('Done') can express the exact same check, but .should() is chained onto the Cypress command queue and retried automatically, while expect() runs immediately, once, at the moment its line executes inside a callback.
This difference is the entire reason .should() is generally preferred: it tolerates the natural asynchronous timing of a real application, while expect() requires the value to already be correct the instant it runs.
// Preferred: retried automatically until it passes
cy.get('[data-cy="status"]').should('have.text', 'Done');
// Works, but must already be correct when .then() fires
cy.get('[data-cy="status"]').then(($el) => {
expect($el.text()).to.equal('Done');
});
If the status text updates a moment after the element appears, the .should() version tolerates that; the expect() version may fail depending on timing.
Both .should() and expect() support the full range of Chai chainers, equal, include, have.length, and more.
.should('be.true') and expect(x).to.be.true express the same check in each style.
Chai-jQuery adds DOM-specific chainers like have.text, be.visible, and have.class, usable with either style.
Sinon-Chai adds spy/stub chainers like have.been.called, most useful with .should() on a spy alias.
should vs expect Cheat Sheet
A direct comparison to help decide which style fits a given check.
Scenario
Recommended Style
Checking element visibility/text/class
.should()
Checking a value after custom computation
expect() inside .then()
Multiple related checks on the same element
.should() chained with .and()
Asserting on a spy or stub call count
.should('have.been.calledOnce')
Complex conditional logic before asserting
.then() + expect()
Chaining Multiple Assertions with .and()
When multiple checks apply to the same element, .and() (an alias for .should()) keeps them readable in a single chain, and each one is still retried as part of the same automatic retry cycle.
expect() is appropriate once you have already reached a stable point, typically inside a .then() after a .should() has confirmed a precondition, and need to perform custom computation or multiple related checks on a plain JavaScript value rather than a DOM element directly.
The .should() guarantees at least one item exists before the .then() computes and asserts on a derived value.
Common Mistakes
Defaulting to expect() for every assertion out of habit, losing retry-ability without a good reason.
Not realizing .and() is simply an alias for .should(), and using it inconsistently for readability only.
Writing complex multi-step logic directly inside a .should(callback) when a .then() + expect() would be clearer.
Asserting inside a .then() without a preceding .should() guaranteeing the precondition needed for that logic to be valid.
Key Takeaways
.should() and expect() use the same underlying Chai assertions but differ in retry behavior.
.should()/.and() retry automatically and should be the default choice.
expect() runs once and is best reserved for custom computed values inside .then().
Combining a .should() precondition with a follow-up .then() + expect() is a common, reliable pattern.
Pro Tip
Use .should() to guarantee a precondition (like "at least one item exists") before dropping into .then() for custom computation, this keeps the retry-sensitive part of the check automatic while still allowing complex logic where it's genuinely needed.
You now know when to use should versus expect. Next, focus specifically on DOM Assertions for elements and their state.