0
0
Cypresstesting~15 mins

should() with chainers in Cypress - Deep Dive

Choose your learning style9 modes available
Overview - should() with chainers
What is it?
In Cypress, the should() command is used to make assertions about the state of elements or values during testing. It allows you to check if something meets a condition, like being visible or having specific text. Chainers are the conditions or checks you attach to should(), such as 'be.visible' or 'contain'. Using should() with chainers lets you write clear, readable tests that verify your app behaves as expected.
Why it matters
Without should() and its chainers, tests would be harder to write and understand because you would need separate commands to check conditions. This would make tests longer and less clear, increasing the chance of mistakes. Using should() with chainers makes tests concise and expressive, helping catch bugs early and ensuring your app works well for users.
Where it fits
Before learning should() with chainers, you should understand basic Cypress commands like get() to select elements. After mastering should() with chainers, you can learn about more advanced assertions, custom commands, and how to handle asynchronous behavior in tests.
Mental Model
Core Idea
should() with chainers lets you attach clear, readable checks directly to elements or values to confirm they meet expected conditions during tests.
Think of it like...
It's like checking items on a shopping list right after picking them up in the store; you confirm each item meets your needs before moving on.
Element selection ──> should() ──> Chainer(s) (e.g., 'be.visible', 'contain')

Flow:
┌───────────────┐
│ Select Element│
└──────┬────────┘
       │
       ▼
┌───────────────┐
│   should()    │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│  Chainer(s)   │
│ (Assertions)  │
└───────────────┘
Build-Up - 6 Steps
1
FoundationBasic use of should() in Cypress
🤔
Concept: Introduces the should() command as a way to assert conditions on elements.
In Cypress, after selecting an element with cy.get(), you can use should() to check if it meets a condition. For example, cy.get('button').should('be.visible') checks if the button is visible on the page.
Result
The test passes if the button is visible; otherwise, it fails and shows an error.
Understanding should() as a direct way to check element states makes tests easier to write and read.
2
FoundationCommon chainers used with should()
🤔
Concept: Shows typical chainers like 'be.visible', 'contain', and 'have.text' used with should().
Chainers are strings that describe the condition to check. Examples: - 'be.visible' checks if element is visible - 'contain' checks if element contains specific text - 'have.text' checks exact text Example: cy.get('h1').should('contain', 'Welcome')
Result
The test confirms the element contains the expected text or state.
Knowing common chainers helps quickly write meaningful assertions without extra code.
3
IntermediateUsing multiple chainers with should()
🤔Before reading on: do you think you can pass multiple conditions in one should() call or do you need separate should() calls? Commit to your answer.
Concept: Explains how to combine multiple chainers in one should() call using 'and' chaining.
You can chain multiple assertions by chaining should() calls or using .and(). For example: cy.get('input').should('be.visible').and('have.value', 'test') This checks the input is visible and has the value 'test'.
Result
Both conditions must be true for the test to pass; if either fails, the test fails.
Combining chainers keeps tests concise and readable, avoiding repetitive code.
4
IntermediateUsing callback functions inside should()
🤔Before reading on: do you think should() can accept a function to write custom assertions, or only string chainers? Commit to your answer.
Concept: Shows that should() can take a callback function for custom, flexible assertions.
Instead of string chainers, you can pass a function to should() that receives the element(s) and lets you write any assertion. Example: cy.get('ul li').should(($items) => { expect($items).to.have.length(3) }) This checks the list has exactly 3 items.
Result
The test passes if the function's assertions succeed; otherwise, it fails.
Using functions inside should() allows complex checks beyond built-in chainers.
5
AdvancedHandling asynchronous retries with should()
🤔Before reading on: do you think should() retries assertions automatically until they pass or fails immediately? Commit to your answer.
Concept: Explains that should() automatically retries assertions until they pass or timeout, handling dynamic page changes.
Cypress retries should() assertions for a default timeout (usually 4 seconds). This means if an element is not visible immediately but appears shortly after, should('be.visible') waits and retries until success or timeout. Example: cy.get('.loading').should('not.exist') waits until loading disappears.
Result
Tests become more stable and less flaky because should() waits for conditions.
Understanding automatic retries prevents confusion about test failures on slow-loading elements.
6
ExpertCustom chainers and extending should()
🤔Before reading on: do you think you can add your own chainers to should() or only use built-in ones? Commit to your answer.
Concept: Shows how to create custom chainers to extend should() for project-specific checks.
Cypress allows adding custom commands that can be used with should(). For example, you can add a command 'beEnabled' that checks if a button is enabled: Cypress.Commands.add('beEnabled', { prevSubject: true }, (subject) => { expect(subject).not.to.be.disabled }) Then use: cy.get('button').should('beEnabled')
Result
Tests become more expressive and tailored to your app's needs.
Extending should() with custom chainers improves test clarity and reuse in large projects.
Under the Hood
When Cypress runs should(), it queries the DOM for the element(s) and evaluates the chained assertion(s). It automatically retries the assertion within a timeout period, polling the DOM repeatedly until the condition passes or the timeout expires. This retry mechanism handles asynchronous changes in the app, like animations or data loading. Internally, Cypress uses promises and event loops to manage these retries without blocking the test flow.
Why designed this way?
Cypress was designed to handle modern web apps that change dynamically. Traditional tests fail if elements are not immediately ready. The retry mechanism in should() was created to reduce flaky tests and make assertions more reliable without extra waits or manual retries. This design trades off some complexity in implementation for a much smoother developer experience.
┌───────────────┐
│ cy.get()      │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ should() call │
└──────┬────────┘
       │
       ▼
┌─────────────────────────────┐
│ Assertion evaluation loop    │
│ ┌─────────────────────────┐ │
│ │ Check condition on DOM   │ │
│ │ If fail, wait & retry    │ │
│ └─────────────────────────┘ │
└──────────────┬──────────────┘
               │
               ▼
       ┌───────────────┐
       │ Pass or Fail  │
       └───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does should() fail immediately if the condition is not met on first check? Commit to yes or no.
Common Belief:should() fails immediately if the condition is not true at the moment of checking.
Tap to reveal reality
Reality:should() retries the assertion multiple times within a timeout before failing, allowing for dynamic page changes.
Why it matters:Believing it fails immediately leads to adding unnecessary waits or flaky tests that fail on slow-loading elements.
Quick: Can you pass multiple conditions as separate arguments inside one should() call? Commit to yes or no.
Common Belief:You can pass multiple conditions as separate arguments inside a single should() call, like should('be.visible', 'contain', 'text').
Tap to reveal reality
Reality:should() accepts one condition per call; to check multiple conditions, chain should() or use .and() between them.
Why it matters:Misusing arguments causes tests to ignore some conditions or throw errors, leading to incomplete checks.
Quick: Does should() only accept string chainers or can it accept functions? Commit to your answer.
Common Belief:should() only accepts string chainers describing built-in assertions.
Tap to reveal reality
Reality:should() can accept a callback function to write custom assertions on elements, offering more flexibility.
Why it matters:Not knowing this limits test expressiveness and forces workarounds for complex checks.
Quick: Can you add your own custom chainers to should()? Commit to yes or no.
Common Belief:You cannot add custom chainers; you must use only built-in ones.
Tap to reveal reality
Reality:Cypress supports adding custom chainers via commands or plugins to extend should() functionality.
Why it matters:Ignoring this limits test clarity and reuse in large or specialized projects.
Expert Zone
1
should() retries all chained assertions together, not individually, so a failure in any chainer triggers a retry of the whole chain.
2
Using functions inside should() bypasses Cypress's automatic retry for that assertion, so you must handle retries manually if needed.
3
Custom chainers can integrate with Cypress's retry mechanism if implemented correctly, but poorly designed ones can cause flaky tests.
When NOT to use
Avoid using should() with complex functions for very large DOM sets as it can slow tests; instead, use more targeted selectors or separate assertions. For non-DOM values or API responses, prefer expect() assertions outside should().
Production Patterns
In real projects, should() with chainers is used extensively for UI state checks, form validations, and dynamic content verification. Teams often create custom chainers for repeated app-specific checks like 'isLoggedIn' or 'hasErrorMessage' to keep tests clean and maintainable.
Connections
Fluent Interfaces
should() with chainers uses fluent interface design to chain assertions smoothly.
Understanding fluent interfaces helps grasp how chaining multiple checks in should() creates readable, expressive test code.
Retry Logic in Networking
should()'s automatic retry mechanism is similar to retrying network requests until success or timeout.
Knowing retry patterns in networking clarifies why Cypress retries assertions, improving test reliability on dynamic pages.
Quality Control in Manufacturing
Both involve checking items repeatedly to ensure they meet standards before acceptance.
Seeing should() as a quality inspector that keeps checking until the product is ready helps understand its retry and assertion role.
Common Pitfalls
#1Failing to chain multiple assertions properly.
Wrong approach:cy.get('input').should('be.visible', 'have.value', 'test')
Correct approach:cy.get('input').should('be.visible').and('have.value', 'test')
Root cause:Misunderstanding that should() accepts only one condition per call leads to incorrect argument usage.
#2Expecting should() to fail immediately without retries.
Wrong approach:cy.get('.loading').should('not.exist') // expecting instant failure if loading exists
Correct approach:cy.get('.loading').should('not.exist') // relies on automatic retries until loading disappears
Root cause:Not knowing should() retries assertions causes confusion about test timing and flaky failures.
#3Using functions inside should() without handling retries.
Wrong approach:cy.get('ul li').should(($items) => { expect($items).to.have.length(3) }) // no retry logic
Correct approach:cy.get('ul li').should('have.length', 3) // uses built-in retry mechanism
Root cause:Using custom functions disables automatic retries, which can cause flaky tests if the DOM changes asynchronously.
Key Takeaways
should() with chainers is a powerful way to write clear, concise assertions directly on elements or values in Cypress tests.
It automatically retries assertions until they pass or timeout, making tests more reliable on dynamic web pages.
Multiple conditions can be chained using .and() to keep tests readable and avoid repetition.
should() accepts both string chainers for common checks and callback functions for custom assertions, offering flexibility.
Advanced users can extend should() with custom chainers to tailor tests to specific application needs.