đ Supercharge Your Test Stability with cy.ensurePageIsReady() in Cypress.io
In the fast-paced world of web applications, testing isnât just about clicking buttonsâââitâs about ensuring that every element is trulyâŠ
đ Supercharge Your Test Stability with cy.ensurePageIsReady() in Cypress.io
In the fast-paced world of web applications, testing isnât just about clicking buttonsâââitâs about ensuring that every element is truly ready before interaction begins. As automation engineers, weâve all battled with flaky tests, unpredictable UI loaders, and unstable screens that lead to false negatives in our test runs.
If youâre building serious E2E tests with Cypress.io, thereâs one custom command you canât afford to ignore:
â
*cy.ensurePageIsReady()*
This powerful utility wraps the best of Cypress stability practices into one unified and reliable command, ensuring your test execution waits for:
- đ All network requests to complete
- đŻ All UI components to be rendered
- đ§ââïž The DOM to stabilize
Letâs dive into why and how this command should become a core part of your test automation toolbox.
đ Why cy.ensurePageIsReady() Is a Game-Changer
Hereâs the real-world problem:
â
*cy.wait(5000)*is not reliable, not scalable, and definitely not intelligent.
Modern apps built with Angular, React, Vue, or PrimeNG load data asynchronously and often update the DOM multiple times after the page âlooksâ ready. Your test might click on a button just before itâs really readyâââresulting in flaky or failed tests.
This is where cy.ensurePageIsReady() shines.
â What It Does (Behind the Scenes)
This custom command intelligently waits for three layers of readiness:
1ïžâŁ Network Silence Detection
It checks for zero pending XHR requests using Cypressâs access to the browserâs network state.
const pendingRequests = (win as any)._networkState?.pendingRequests || 0;
This avoids proceeding while your app is still fetching data or initializing complex components.
2ïžâŁ UI Component Stability
It waits for known loading indicators to vanish, including:
-
.spinner-overlay -
.skeleton-loader - Core layout containers like
body
cy.get(".spinner-overlay,.skeleton-loader", { timeout: 30000 }).should("not.exist");
This guarantees the UI is ready to interact with.
3ïžâŁ DOM Mutation Observation
Using MutationObserver, it ensures no further DOM changes are happening due to animations, lazy-loading, or JavaScript framework updates.
const observer = new MutationObserver(() => {
lastChange = Date.now();
});
Only when the DOM is quiet for at least 1000ms, the test proceeds.
đ§ The Custom Command
Hereâs the full, production-grade command:
Cypress.Commands.add("ensurePageIsReady", () => {
const waitForNetworkIdle = (options = {}) => {
const { timeout = 10000, log = true, interval = 500 } = options;
if (log) {
Cypress.log({
name: 'waitForNetworkIdle',
message: `Waiting for network idle (timeout: ${timeout}ms)`,
});
}
return cy.window({ log: false }).then({ timeout: timeout + 1000 }, (win) => {
return new Cypress.Promise<void>((resolve, reject) => {
const timeoutId = setTimeout(() => reject(new Error("Network idle timeout")), timeout);
const check = () => {
const pending = (win as any)._networkState?.pendingRequests || 0;
if (pending === 0) {
clearTimeout(timeoutId);
resolve();
} else {
setTimeout(check, interval);
}
};
check();
});
});
};
const waitForDOMStability = () => {
cy.window().then((win) => {
return new Cypress.Promise<void>((resolve) => {
let lastChange = Date.now();
const observer = new MutationObserver(() => lastChange = Date.now());
observer.observe(win.document.body, { childList: true, subtree: true, attributes: true });
const check = () => {
if (Date.now() - lastChange > 1000) {
observer.disconnect();
resolve();
} else {
setTimeout(check, 500);
}
};
check();
});
});
};
const waitForUIComponents = () => {
cy.get("body", { timeout: 60000 }).should("be.visible");
cy.get(".spinner-overlay,.skeleton-loader", { timeout: 30000 }).should("not.exist");
};
cy.log("đ Ensuring page is fully stable...");
waitForNetworkIdle();
waitForUIComponents();
waitForDOMStability();
cy.log("â
Page is ready for testing.");
});
đ§Ș When to Use It
Use cy.ensurePageIsReady() anywhere you:
- Visit a page with dynamic content
- Navigate between modules
- Login or load dashboards
- Click dropdowns or date pickers that trigger async loading
- Wait before asserting UI state
đ Real-World Usage Example
cy.visit('/erp/transactions');
cy.ensurePageIsReady();
cy.get('[data-testid="addInvoice"]').click();
cy.ensurePageIsReady();
cy.get('input[data-testid="invoiceNo"]').should('be.visible');
No cy.wait(4000), no guessingâââjust deterministic, clean, stable tests.
đ§ Final Thoughts
âFlaky tests are worse than no tests.â
âââEvery QA Engineer Ever
By introducing cy.ensurePageIsReady() into your Cypress project, you're building resilient, scalable, and deterministic automation.
This command is not just a utilityâââitâs a testing philosophy:
 âWait only as long as needed, and only when it matters.â
đ Ready to Take Your Cypress Tests to the Next Level?
đ Replace cy.wait() with intelligence
 đȘ Boost your confidence in test stability
 𧩠Build smarter test flows that mimic real user behavior
 đ„ Share this post with your team and automate like a pro!
đ„ Drop a Comment Below
Let me know:
- How do you handle flaky tests in your Cypress projects?
- What would you add to
cy.ensurePageIsReady()?
Letâs build a smarter, more stable test worldâââone command at a time.
Written by Mohamed Said Ibrahim, Test Automation Engineer & Cypress Evangelist.
đ Follow me for more Cypress tips, custom command libraries, and test architecture breakdowns.
By Mohamed Said Ibrahim on June 24, 2025.
Exported from Medium on October 2, 2025.


Top comments (0)