0
0
Cypresstesting~15 mins

Multiple assertions chaining in Cypress - Deep Dive

Choose your learning style9 modes available
Overview - Multiple assertions chaining
What is it?
Multiple assertions chaining means checking several conditions one after another on the same element or value in a test. In Cypress, you can write these checks in a chain, making tests clear and easy to read. Each assertion confirms a part of the expected behavior, like checking text, visibility, or attributes. This helps ensure the tested feature works exactly as intended.
Why it matters
Without chaining multiple assertions, tests become longer, harder to read, and more error-prone. If you test each condition separately, you might miss how they relate or slow down your tests. Chaining keeps tests concise and efficient, catching problems early and saving time. It makes test reports clearer, showing exactly which part failed.
Where it fits
Before learning multiple assertions chaining, you should know basic Cypress commands and simple assertions. After mastering chaining, you can explore advanced Cypress features like custom commands, retries, and conditional testing. This topic fits in the middle of your Cypress learning path, bridging simple tests and complex test suites.
Mental Model
Core Idea
Chaining multiple assertions in Cypress lets you check many expected conditions on the same subject smoothly and clearly in one flow.
Think of it like...
It's like checking off multiple items on a shopping list while walking through the store aisle by aisle, without going back and forth.
cy.get('selector')
  ├─ .should('be.visible')
  ├─ .and('contain.text', 'Hello')
  └─ .and('have.class', 'active')
Build-Up - 7 Steps
1
FoundationBasic single assertion usage
🤔
Concept: Learn how to write a single assertion in Cypress to check one condition.
In Cypress, you use commands like cy.get() to find elements, then .should() to assert something about them. For example, cy.get('button').should('be.visible') checks if a button is visible on the page.
Result
The test passes if the button is visible; otherwise, it fails and shows an error.
Understanding single assertions is the base for chaining multiple checks later.
2
FoundationUnderstanding Cypress command chaining
🤔
Concept: Cypress commands return subjects that allow chaining more commands or assertions.
When you write cy.get('input'), Cypress finds the input element and passes it along. You can then chain .should() or other commands to keep working with that element without repeating the selector.
Result
Commands flow smoothly, making tests shorter and easier to read.
Knowing how Cypress chains commands helps you write multiple assertions on the same element without extra code.
3
IntermediateChaining multiple assertions with .should() and .and()
🤔Before reading on: do you think .should() can be used multiple times in a chain, or only once?
Concept: You can chain multiple assertions using .should() once and .and() for the rest to check many conditions on one element.
Example: cy.get('h1') .should('be.visible') .and('contain.text', 'Welcome') .and('have.class', 'title') This checks visibility, text content, and CSS class in one chain.
Result
If all assertions pass, the test passes; if any fail, Cypress reports exactly which one failed.
Using .and() after .should() keeps tests concise and groups related checks logically.
4
IntermediateUsing multiple .should() calls in a chain
🤔Before reading on: do you think multiple .should() calls run sequentially or independently?
Concept: You can use multiple .should() calls in a chain, each running its own assertion on the subject.
Example: cy.get('input') .should('be.enabled') .should('have.value', '') Each .should() runs separately but on the same element found by cy.get().
Result
Both assertions must pass for the test to succeed.
Knowing that multiple .should() calls work sequentially helps you organize complex checks clearly.
5
IntermediateChaining assertions on different subjects
🤔Before reading on: can you chain assertions on different elements without repeating cy.get()?
Concept: Chaining assertions works on the same subject; to test different elements, you need separate cy.get() calls.
Example: cy.get('button').should('be.visible') cy.get('input').should('be.enabled') You cannot chain these two assertions directly because they target different elements.
Result
Tests remain clear by separating element queries and their assertions.
Understanding subject chaining limits prevents confusing test failures and keeps tests maintainable.
6
AdvancedCustom assertion chaining with callbacks
🤔Before reading on: do you think you can write your own assertion logic inside .should() callbacks?
Concept: Cypress allows writing custom assertions inside .should() by passing a function that receives the subject and runs multiple checks.
Example: cy.get('ul').should(($list) => { expect($list).to.have.length(1) expect($list.find('li')).to.have.length.greaterThan(2) }) This runs multiple assertions inside one .should() call.
Result
You get flexible, powerful checks beyond built-in assertions.
Custom callbacks let you combine complex logic in one place, improving test expressiveness.
7
ExpertHandling asynchronous retries in chained assertions
🤔Before reading on: do chained assertions retry automatically if they fail initially in Cypress?
Concept: Cypress retries assertions automatically until they pass or timeout, even in chained assertions, making tests stable against slow UI changes.
Example: cy.get('button') .should('be.visible') .and('not.be.disabled') If the button is not visible or disabled at first, Cypress waits and retries until conditions are met or timeout occurs.
Result
Tests become more reliable without manual waits or delays.
Knowing Cypress retries chained assertions helps you write robust tests that handle real-world timing issues gracefully.
Under the Hood
Cypress commands queue up and run asynchronously. When you chain assertions like .should() and .and(), Cypress waits for the subject to appear and then runs each assertion in order. If an assertion fails, Cypress retries the entire chain until a timeout. This retry mechanism is built into Cypress's command queue and event loop, ensuring tests wait for the UI to reach the expected state before failing.
Why designed this way?
Cypress was designed to handle flaky UI tests caused by timing issues. Automatic retries and chaining allow writing tests that are both readable and resilient without manual waits. This design reduces test flakiness and developer frustration, making tests more maintainable and faster to write.
┌───────────────┐
│ cy.get()      │
│ (find element)│
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ .should()     │
│ (assertion 1) │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ .and()        │
│ (assertion 2) │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Retry loop if │
│ assertion fails│
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does chaining multiple assertions in Cypress run all assertions even if the first one fails? Commit yes or no.
Common Belief:All assertions in a chain run regardless of failures.
Tap to reveal reality
Reality:If an assertion fails, Cypress stops the chain and retries from the start until timeout or success.
Why it matters:Assuming all assertions run can lead to misunderstanding test failures and debugging confusion.
Quick: Can you chain assertions on different elements without separate cy.get() calls? Commit yes or no.
Common Belief:You can chain assertions on different elements directly after one cy.get().
Tap to reveal reality
Reality:Chaining works only on the same subject; different elements require separate cy.get() calls.
Why it matters:Misusing chaining causes tests to fail or check wrong elements, wasting debugging time.
Quick: Does Cypress retry only the failing assertion or the entire chain? Commit your answer.
Common Belief:Cypress retries only the failing assertion in a chain.
Tap to reveal reality
Reality:Cypress retries the entire chain of assertions from the start on failure.
Why it matters:Knowing this helps write efficient assertions and avoid unexpected delays.
Quick: Can you write complex logic inside .should() callbacks? Commit yes or no.
Common Belief:.should() only accepts simple string assertions.
Tap to reveal reality
Reality:.should() can accept functions with multiple custom assertions inside.
Why it matters:Missing this limits test expressiveness and leads to verbose, repetitive code.
Expert Zone
1
Chained assertions share the same subject, so mutations in one assertion affect the next, which can cause subtle bugs if not understood.
2
Using multiple .should() calls versus .should() with .and() can affect readability and retry behavior subtly, influencing test stability.
3
Custom assertion callbacks inside .should() run synchronously but can include multiple expect statements, allowing complex validations in one step.
When NOT to use
Avoid chaining multiple assertions when testing unrelated elements or when assertions require different subjects. Instead, use separate cy.get() calls or custom commands. Also, avoid overly long chains that reduce readability; split complex checks into smaller, named tests.
Production Patterns
In real projects, chaining multiple assertions is used to verify UI elements' state, content, and style in one go, reducing test code size. Teams often create custom commands that internally chain assertions for reusable checks like 'isLoggedIn' or 'formIsValid'. Cypress's retry mechanism with chained assertions helps handle dynamic content and animations gracefully.
Connections
Fluent Interface Pattern
Multiple assertions chaining in Cypress uses the fluent interface pattern to allow readable, chainable commands.
Understanding fluent interfaces in software design helps grasp why chaining assertions feels natural and expressive.
Unit Testing Assertions
Chaining multiple assertions in Cypress builds on the idea of unit test assertions but applies it to UI elements with asynchronous retries.
Knowing basic assertion principles from unit testing clarifies how Cypress extends them for end-to-end testing.
Quality Control in Manufacturing
Chaining multiple assertions is like checking several quality points on a product in one inspection process.
Seeing test assertions as quality checks helps appreciate why grouping them improves efficiency and clarity.
Common Pitfalls
#1Stopping test after first failed assertion without retries
Wrong approach:cy.get('button').should('be.visible').and('have.text', 'Submit') // If 'be.visible' fails, 'have.text' is never checked and test fails immediately without retry
Correct approach:cy.get('button').should('be.visible').and('have.text', 'Submit') // Cypress retries the entire chain until both conditions pass or timeout
Root cause:Misunderstanding Cypress's automatic retry mechanism and how it handles chained assertions.
#2Chaining assertions on different elements without separate queries
Wrong approach:cy.get('button').should('be.visible').and('have.value', 'Click') // 'have.value' applies to button, which may not have a value attribute
Correct approach:cy.get('button').should('be.visible') cy.get('input').should('have.value', 'Click')
Root cause:Confusing subject chaining with multiple element queries.
#3Writing overly long assertion chains reducing readability
Wrong approach:cy.get('form') .should('be.visible') .and('contain.text', 'Login') .and('have.class', 'active') .and('have.length', 1) .and('have.attr', 'data-test', 'login-form') .and('not.be.disabled')
Correct approach:Split into smaller tests or custom commands: cy.get('form').should('be.visible').and('contain.text', 'Login') cy.get('form').should('have.class', 'active').and('have.attr', 'data-test', 'login-form') cy.get('form').should('not.be.disabled')
Root cause:Trying to check too many things at once harms clarity and maintainability.
Key Takeaways
Chaining multiple assertions in Cypress lets you check many conditions on the same element clearly and efficiently.
Use .should() for the first assertion and .and() for additional ones to keep tests readable and grouped logically.
Cypress automatically retries the entire chain of assertions until they pass or timeout, making tests stable against UI delays.
Chaining works only on the same subject; testing different elements requires separate cy.get() calls.
Custom assertion callbacks inside .should() allow complex, flexible checks beyond built-in assertions.