🔐 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)