DEV Community

mohamed Said Ibrahim
mohamed Said Ibrahim

Posted on • Originally published at Medium

🔐 Supercharge Your Test Stability with cy.ensurePageIsReady() in Cypress.io

🔐 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();  
});
Enter fullscreen mode Exit fullscreen mode

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.");  
});
Enter fullscreen mode Exit fullscreen mode

🧪 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');
Enter fullscreen mode Exit fullscreen mode

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)