UNPKG

cypress-enterprise-commands

Version:

Reusable Cypress custom commands for enterprise web applications

235 lines (234 loc) 11 kB
"use strict"; 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((resolve, reject) => { let timedOut = false; const timeoutId = setTimeout(() => { timedOut = true; reject(new Error(`Network did not become idle within ${timeout}ms`)); }, timeout); const checkRequests = () => { const pendingRequests = win._networkState?.pendingRequests || 0; if (log) cy.log(`⏳ Pending requests: ${pendingRequests}`); if (pendingRequests === 0) { clearTimeout(timeoutId); resolve(); } else if (!timedOut) { setTimeout(checkRequests, interval); } }; checkRequests(); }); }); }; const waitForDOMStability = () => { cy.window().then((win) => { return new Cypress.Promise((resolve) => { let lastChange = Date.now(); const observer = new MutationObserver(() => { lastChange = Date.now(); }); observer.observe(win.document.body, { childList: true, subtree: true, attributes: true, characterData: true, }); const checkStability = () => { if (Date.now() - lastChange > 1000) { observer.disconnect(); resolve(); } else { setTimeout(checkStability, 500); } }; checkStability(); }); }); }; const waitForUIComponents = () => { cy.get("body", { timeout: 60000 }).should("be.visible"); cy.log("⏳ Waiting for loading indicators to disappear..."); cy.get(".spinner-overlay,.skeleton-loader", { timeout: 30000 }).should("not.exist", { timeout: 8000 }); ; }; // === Unified Execution Flow === cy.log("🔎 Ensuring page is ready (network + UI + DOM stability)"); waitForNetworkIdle(); waitForUIComponents(); waitForDOMStability(); cy.log("✅ Page is fully stable and ready."); }); let loginRetryCount = 0; Cypress.Commands.add("navigateAndLoginIfNeeded", (fullUrl, isInventory) => { cy.log(`🔗 Navigating to: ${fullUrl} (IsInventory: ${isInventory})`); // Ensure we capture potential initial errors during visit cy.visit(fullUrl, { failOnStatusCode: false }); // Add more specific waits or checks for initial page load if needed cy.ensurePageIsReady(); cy.catchUnCaughtException(); cy.url().then((currentUrl) => { cy.log(`Current URL after visit: ${currentUrl}`); const expectedUrlPart = fullUrl.replace(Cypress.env("erpBaseUrl"), ""); const redirectedToLogin = currentUrl.includes("login?returnUrl") || !currentUrl.includes(expectedUrlPart); if (redirectedToLogin) { cy.log(`🛑 Detected redirect to login or incorrect URL. Expected part: ${expectedUrlPart}`); if (loginRetryCount >= 2) { throw new Error(`🛑 Too many login retries (${loginRetryCount}). Failed to reach ${fullUrl}. Last URL: ${currentUrl}`); } cy.log("🔁 Redirected to login. Attempting login..."); loginRetryCount++; cy.implementLogin(isInventory); // Ensure this robustly logs in // Recursive call after login attempt cy.navigateAndLoginIfNeeded(fullUrl, isInventory); return; } cy.log(`✅ Successfully navigated to: ${fullUrl}. Current URL: ${currentUrl}`); cy.url({ timeout: 60000 }).should("include", expectedUrlPart); loginRetryCount = 0; // Reset retry count only upon successful final navigation cy.ensurePageIsReady(); }); }); Cypress.Commands.add("navigateToERPModule", (moduleExtension) => { const isInventory = moduleExtension.includes("inventory"); const fullUrl = `${Cypress.env("erpBaseUrl")}${moduleExtension}`; const translationRegex = new RegExp(`${isInventory ? "/inventory-apis/" : "/erp-apis/"}SideMenu/LoadTranslationFile\\?`, "i"); cy.intercept("GET", translationRegex, (req) => { // Add specific logging for this interception cy.log(`🔵 Intercepting LoadTranslationFile: ${req.url}`); // Force no-cache headers to ensure a fresh request req.headers["cache-control"] = "no-cache"; req.headers["pragma"] = "no-cache"; req.continue((res) => { // Log the response details immediately when it comes back cy.log(`🟢 LoadTranslationFile Response: Status ${res.statusCode}, URL: ${req.url}, Body:`, res.body); }); }).as("LoadTranslationFile"); cy.log(`Attempting to navigate and login for module: ${moduleExtension}`); cy.navigateAndLoginIfNeeded(fullUrl, isInventory); // --- Ensure the page is truly settled before waiting for API --- cy.log("⏳ Waiting for all page loading indicators to disappear (spinner-overlay, skeleton-loader)..."); cy.get(".spinner-overlay, .skeleton-loader", { timeout: 45000 }) // Increased timeout again .should("not.exist") .then(() => { cy.log("✅ All initial loading indicators disappeared."); }); // --- Wait for translation file with detailed error logging and a longer timeout --- cy.log("🌐 Waiting for '@LoadTranslationFile' alias..."); cy.wait("@LoadTranslationFile", { timeout: 30000 }) // Keep this timeout generous .then((interception) => { // It's possible for interception.response to be null if the request was aborted or // didn't actually go through the network (e.g., served from disk cache without reaching service worker) if (!interception?.response) { cy.log("⚠️ @LoadTranslationFile interception was successful but 'response' object is missing. This could indicate a cached response not hitting the network or an aborted request."); return; // Skip status assertion if no response } const { statusCode, body } = interception.response; cy.log(`✅ @LoadTranslationFile received. Status: ${statusCode}`); // Crucial: Detailed assertion failure message expect(statusCode).to.eq(200, `Translation file API failed. Expected 200, got ${statusCode}. Response URL: ${interception.request.url}. Response Body: ${JSON.stringify(body, null, 2)}`); }); // Language auto-detect logic (remains mostly the same, ensure robust selectors) cy.log("🔍 Detecting language from menu items..."); cy.get("p.date", { timeout: 60000 }) .filter(":visible") .first() .scrollIntoView() .should("be.visible") .then(($el) => { const text = $el.text().trim(); cy.log(`📋 Detected first visible menu item text: '${text}'`); const isArabic = /[\u0600-\u06FF]/.test(text); if (isArabic) { cy.log("🌐 Arabic detected in menu. Switching language..."); cy.changeLanguage(isInventory); cy.ensurePageIsReady(); cy.get(".spinner-overlay,.skeleton-loader", { timeout: 20000 }).should("not.exist"); } else { cy.log("🌐 English (or non-Arabic) detected. No language switch needed."); } }); // Consider if this reload is necessary. It re-initializes the page, // which might cause the translation file to be re-requested, but also slows down. cy.log("🔄 Reloading screen..."); cy.reloadScreen(); // Ensure this custom command is robust cy.ensurePageIsReady(); cy.log("✅ Screen reloaded and ready."); }); Cypress.Commands.add("navigateToTheLatestScreen", () => { cy.get("table").should("be.visible"); cy.get("table").then(($table) => { if ($table.find("tbody").is(":visible")) { cy.get("tbody").then((tbody) => { if (tbody.find("tr").is(":visible")) { cy.wrap(tbody).find("tr").last().scrollIntoView(); if (tbody.find("tr").length >= 25) { cy.get("p-paginator").then((paginator) => { if (paginator.find('button[aria-label="Last Page"]').is(":visible")) { cy.get('button[aria-label="Last Page"]').click({ force: true }); } }); } else { cy.log("the count of rows less than 25"); } } }); } else { throw new Error("Table is not visible"); } }); }); Cypress.Commands.add("navigateToJournalEntryViewScreen", () => { cy.ensurePageIsReady(); cy.navigateToERPModule("/accounting/transactions/journalentry"); }); Cypress.Commands.add("switchBetweenTabs", (index) => { cy.get('a[role="tab"]').eq(index).scrollIntoView().click({ force: true }); }); Cypress.Commands.add("zoomOut", () => { cy.viewport(1920, 1080); cy.window().then((win) => { win.document.body.style.zoom = "95%"; // Zoom out to 80% }); }); Cypress.Commands.add("reloadScreen", () => { cy.ensurePageIsReady(); cy.reload(); cy.ensurePageIsReady(); cy.get(".spinner-overlay").should("not.exist", { timeout: 30000 }); }); Cypress.Commands.add("goBack", () => { cy.get('body').then(($body) => { const cancelButtons = $body.find('button:contains("Cancel"), button:contains("Back") '); if (cancelButtons.is(':visible')) { cy.wrap(cancelButtons.first()).click({ force: true, multiple: true }); cy.log("Clicked 'Cancel' button."); return; // Exit after clicking } else { cy.logMsg("No 'Cancel' or 'Back' button found, proceeding to history back."); } }); cy.ensurePageIsReady(); }); Cypress.Commands.add("clickAddNew", () => { const addNewButtons = 'button:contains("Add"), button:contains("Create") '; cy.get(addNewButtons, { timeout: 45000 }).last().scrollIntoView().click({ force: true }); cy.ensurePageIsReady(); cy.get(".spinner-overlay").should("not.exist", { timeout: 30000 }); cy.contains("button", /save/i).should("be.visible"); });