cypress-enterprise-commands
Version:
Reusable Cypress custom commands for enterprise web applications
235 lines (234 loc) • 11 kB
JavaScript
;
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");
});