Synchronizing your tests with network traffic is the key to testing modern dynamic applications reliably. Let's explore two common, practical case studies that demonstrate the power of combining cy.intercept()
and cy.wait()
.
Case Study 1: Verifying Data Loading on a Dashboard
Imagine a user dashboard that fetches user data and a list of recent activities from two different API endpoints when it loads. You want to test that the correct welcome message and the number of activities are displayed.
Without proper waiting, you might try to check for these elements immediately after visiting the page, which would likely fail as the data hasn't arrived yet.
The Robust Solution:
// Test to verify dashboard data is loaded and displayed correctly
it('should display user data and activities after they are fetched', () => {
// Intercept the relevant GET requests and assign aliases
cy.intercept('GET', '/api/v1/user/profile').as('getUserProfile');
cy.intercept('GET', '/api/v1/user/activities').as('getActivities');
// Visit the dashboard page, which triggers the API calls
cy.visit('/dashboard');
// Wait for both network requests to complete before proceeding.
// Cypress is smart enough to wait for both to resolve.
cy.wait(['@getUserProfile', '@getActivities']);
// Now that we know the data has arrived, we can safely make assertions
cy.get('.welcome-banner').should('contain.text', 'Welcome back, Alice!');
cy.get('.activity-list-item').should('have.length', 5);
});
In this example, cy.wait()
acts as a synchronization point. It ensures that the test only continues after the application has received all the necessary data. This approach eliminates any guesswork about timing. Furthermore, you can make assertions on the network calls themselves. As noted in MIT research on reliable testing, verifying both the cause (the API call) and the effect (the UI update) leads to more robust validation.
cy.wait('@getUserProfile').its('response.statusCode').should('eq', 200);
cy.wait('@getActivities').then((interception) => {
expect(interception.response.body.activities).to.be.an('array');
});
Case Study 2: Handling Form Submissions with Dependent UI Changes
Consider a settings page where a user updates their profile. After clicking 'Save', a 'Saving...' spinner appears, the button is disabled, and upon a successful API response, a 'Profile Saved!' toast message is shown.
Testing this flow requires precise timing. You need to cypress wait for element states like the spinner to appear and disappear, but the most reliable trigger is the underlying network request.
The Robust Solution:
// Test to verify the profile update flow
it('shows a success toast after the profile is saved', () => {
// Intercept the API call for updating the profile
cy.intercept('PUT', '/api/v1/user/profile').as('updateProfile');
// Fill out the form
cy.get('[data-cy=profile-name-input]').clear().type('Alice Smith');
cy.get('[data-cy=profile-bio-input]').clear().type('Lead Software Engineer');
// Click the save button
cy.get('[data-cy=save-profile-button]').click();
// Assert that the UI is in a loading state
// Cypress's automatic waiting handles this perfectly.
cy.get('[data-cy=save-profile-button]').should('be.disabled');
cy.get('.spinner').should('be.visible');
// The crucial step: wait for the network request to complete
cy.wait('@updateProfile').its('response.statusCode').should('eq', 200);
// Now, assert the final state of the UI
cy.get('.toast-message').should('be.visible').and('contain', 'Profile Saved!');
cy.get('[data-cy=save-profile-button]').should('be.enabled');
cy.get('.spinner').should('not.exist');
});
By waiting on @updateProfile
, we create a deterministic test. We know that once the wait is resolved, the backend has processed the request, and the frontend should have reacted accordingly. We can then confidently assert the final state of the UI without any flaky cy.wait(1000)
commands. This aligns with BDD principles where tests describe behavior, and the network interaction is a key part of that behavior. A Gartner overview of BDD emphasizes testing behavior over implementation, and this method does exactly that.