deploy: hr-portal-v5-designs
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env node
|
||||
const puppeteer = require("puppeteer");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const DEFAULT_VIEWPORTS = {
|
||||
mobile: { width: 375, height: 812 },
|
||||
tablet: { width: 768, height: 1024 },
|
||||
desktop: { width: 1440, height: 900 },
|
||||
};
|
||||
|
||||
async function run() {
|
||||
const manifestPath = path.resolve(process.cwd(), "test-manifest.json");
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
console.error("ERROR: test-manifest.json not found in", process.cwd());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
||||
const baseUrl = manifest.baseUrl || "http://localhost:3000";
|
||||
const viewports = manifest.viewports
|
||||
? Object.fromEntries(
|
||||
Object.entries(manifest.viewports).map(function (entry) {
|
||||
return [entry[0], entry[1]];
|
||||
})
|
||||
)
|
||||
: DEFAULT_VIEWPORTS;
|
||||
|
||||
const resultsDir = path.resolve(process.cwd(), "test-results");
|
||||
if (!fs.existsSync(resultsDir)) {
|
||||
fs.mkdirSync(resultsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || "chromium";
|
||||
var browser;
|
||||
try {
|
||||
browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
executablePath: executablePath,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("ERROR: Failed to launch browser:", err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var results = { pages: [], passed: 0, failed: 0, errors: [] };
|
||||
|
||||
for (var pi = 0; pi < (manifest.pages || []).length; pi++) {
|
||||
var pageDef = manifest.pages[pi];
|
||||
var pageResult = {
|
||||
id: pageDef.id,
|
||||
url: baseUrl + (pageDef.path || "/"),
|
||||
viewports: {},
|
||||
passed: true,
|
||||
};
|
||||
|
||||
var vpEntries = Object.entries(viewports);
|
||||
for (var vi = 0; vi < vpEntries.length; vi++) {
|
||||
var vpName = vpEntries[vi][0];
|
||||
var vpSize = vpEntries[vi][1];
|
||||
var vpResult = {
|
||||
viewport: vpName,
|
||||
screenshots: [],
|
||||
actions: [],
|
||||
consoleErrors: [],
|
||||
passed: true,
|
||||
};
|
||||
|
||||
var browserPage = null;
|
||||
try {
|
||||
browserPage = await browser.newPage();
|
||||
await browserPage.setViewport(vpSize);
|
||||
|
||||
var consoleErrors = [];
|
||||
browserPage.on("console", function (msg) {
|
||||
if (msg.type() === "error") {
|
||||
consoleErrors.push(msg.text());
|
||||
}
|
||||
});
|
||||
browserPage.on("pageerror", function (err) {
|
||||
consoleErrors.push(err.message);
|
||||
});
|
||||
|
||||
await browserPage.goto(pageResult.url, {
|
||||
waitUntil: "networkidle2",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
if (pageDef.waitFor) {
|
||||
await browserPage.waitForSelector(pageDef.waitFor, { timeout: 10000 });
|
||||
}
|
||||
|
||||
var initialScreenshot = path.join(
|
||||
resultsDir,
|
||||
pageDef.id + "-" + vpName + "-initial.png"
|
||||
);
|
||||
await browserPage.screenshot({
|
||||
path: initialScreenshot,
|
||||
fullPage: true,
|
||||
});
|
||||
vpResult.screenshots.push(initialScreenshot);
|
||||
|
||||
var actions = pageDef.actions || [];
|
||||
for (var ai = 0; ai < actions.length; ai++) {
|
||||
var action = actions[ai];
|
||||
var actionResult = { id: action.id, type: action.type, passed: true, error: null };
|
||||
try {
|
||||
await executeAction(browserPage, action);
|
||||
|
||||
var actionScreenshot = path.join(
|
||||
resultsDir,
|
||||
pageDef.id + "-" + vpName + "-" + action.id + ".png"
|
||||
);
|
||||
await browserPage.screenshot({
|
||||
path: actionScreenshot,
|
||||
fullPage: true,
|
||||
});
|
||||
vpResult.screenshots.push(actionScreenshot);
|
||||
|
||||
if (action.expectAfter) {
|
||||
var valid = await validateExpectations(
|
||||
browserPage,
|
||||
action.expectAfter
|
||||
);
|
||||
if (!valid) {
|
||||
actionResult.passed = false;
|
||||
actionResult.error = "Expectation failed for " + action.id;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
actionResult.passed = false;
|
||||
actionResult.error = err.message;
|
||||
}
|
||||
|
||||
if (!actionResult.passed) {
|
||||
vpResult.passed = false;
|
||||
}
|
||||
vpResult.actions.push(actionResult);
|
||||
}
|
||||
|
||||
vpResult.consoleErrors = consoleErrors;
|
||||
if (consoleErrors.length > 0) {
|
||||
vpResult.passed = false;
|
||||
}
|
||||
} catch (err) {
|
||||
vpResult.passed = false;
|
||||
vpResult.error = err.message;
|
||||
results.errors.push({
|
||||
page: pageDef.id,
|
||||
viewport: vpName,
|
||||
error: err.message,
|
||||
});
|
||||
} finally {
|
||||
if (browserPage) {
|
||||
await browserPage.close().catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
if (!vpResult.passed) {
|
||||
pageResult.passed = false;
|
||||
}
|
||||
pageResult.viewports[vpName] = vpResult;
|
||||
}
|
||||
|
||||
if (pageResult.passed) {
|
||||
results.passed++;
|
||||
} else {
|
||||
results.failed++;
|
||||
}
|
||||
results.pages.push(pageResult);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
|
||||
var summaryPath = path.join(resultsDir, "summary.json");
|
||||
fs.writeFileSync(summaryPath, JSON.stringify(results, null, 2));
|
||||
|
||||
console.log("");
|
||||
console.log("=== Visual Test Results ===");
|
||||
console.log("Pages tested: " + results.pages.length);
|
||||
console.log("Passed: " + results.passed);
|
||||
console.log("Failed: " + results.failed);
|
||||
|
||||
for (var ri = 0; ri < results.pages.length; ri++) {
|
||||
var p = results.pages[ri];
|
||||
var icon = p.passed ? "PASS" : "FAIL";
|
||||
console.log(" [" + icon + "] " + p.id + " (" + p.url + ")");
|
||||
var vpKeys = Object.keys(p.viewports);
|
||||
for (var vk = 0; vk < vpKeys.length; vk++) {
|
||||
var vr = p.viewports[vpKeys[vk]];
|
||||
if (!vr.passed) {
|
||||
console.log(" " + vpKeys[vk] + ": FAIL" + (vr.error ? " - " + vr.error : ""));
|
||||
for (var ak = 0; ak < (vr.actions || []).length; ak++) {
|
||||
var a = vr.actions[ak];
|
||||
if (!a.passed) {
|
||||
console.log(" action " + a.id + ": " + a.error);
|
||||
}
|
||||
}
|
||||
if (vr.consoleErrors && vr.consoleErrors.length > 0) {
|
||||
console.log(" console errors: " + vr.consoleErrors.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log("Summary written to: " + summaryPath);
|
||||
process.exit(results.failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
async function executeAction(page, action) {
|
||||
switch (action.type) {
|
||||
case "click":
|
||||
await page.waitForSelector(action.selector, { timeout: 5000 });
|
||||
await page.click(action.selector);
|
||||
break;
|
||||
case "fill-form":
|
||||
var fields = action.fields || [];
|
||||
for (var fi = 0; fi < fields.length; fi++) {
|
||||
var field = fields[fi];
|
||||
await page.waitForSelector(field.selector, { timeout: 5000 });
|
||||
await page.click(field.selector, { clickCount: 3 });
|
||||
await page.type(field.selector, field.value);
|
||||
}
|
||||
break;
|
||||
case "select":
|
||||
await page.waitForSelector(action.selector, { timeout: 5000 });
|
||||
await page.select(action.selector, action.value);
|
||||
break;
|
||||
case "scroll":
|
||||
if (action.selector) {
|
||||
await page.waitForSelector(action.selector, { timeout: 5000 });
|
||||
await page.$eval(action.selector, function (el) {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
});
|
||||
} else {
|
||||
await page.evaluate(
|
||||
function (x, y) { window.scrollTo(x, y); },
|
||||
action.x || 0,
|
||||
action.y || 0
|
||||
);
|
||||
}
|
||||
await new Promise(function (r) { setTimeout(r, 500); });
|
||||
break;
|
||||
case "hover":
|
||||
await page.waitForSelector(action.selector, { timeout: 5000 });
|
||||
await page.hover(action.selector);
|
||||
await new Promise(function (r) { setTimeout(r, 300); });
|
||||
break;
|
||||
case "wait":
|
||||
if (action.selector) {
|
||||
await page.waitForSelector(action.selector, {
|
||||
timeout: action.timeout || 10000,
|
||||
});
|
||||
} else {
|
||||
await new Promise(function (r) { setTimeout(r, action.duration || 1000); });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown action type: " + action.type);
|
||||
}
|
||||
}
|
||||
|
||||
async function validateExpectations(page, expectations) {
|
||||
var items = Array.isArray(expectations) ? expectations : [expectations];
|
||||
for (var ei = 0; ei < items.length; ei++) {
|
||||
var expect = items[ei];
|
||||
if (expect.url) {
|
||||
var currentUrl = page.url();
|
||||
var pattern = new RegExp(expect.url);
|
||||
if (!pattern.test(currentUrl)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (expect.visible) {
|
||||
var el = await page.$(expect.visible);
|
||||
if (!el) return false;
|
||||
var isVisible = await page.evaluate(function (s) {
|
||||
var e = document.querySelector(s);
|
||||
if (!e) return false;
|
||||
var r = e.getBoundingClientRect();
|
||||
return r.width > 0 && r.height > 0;
|
||||
}, expect.visible);
|
||||
if (!isVisible) return false;
|
||||
}
|
||||
if (expect.hidden) {
|
||||
var elH = await page.$(expect.hidden);
|
||||
if (elH) {
|
||||
var isVis = await page.evaluate(function (s) {
|
||||
var e = document.querySelector(s);
|
||||
if (!e) return false;
|
||||
var r = e.getBoundingClientRect();
|
||||
return r.width > 0 && r.height > 0;
|
||||
}, expect.hidden);
|
||||
if (isVis) return false;
|
||||
}
|
||||
}
|
||||
if (expect.text) {
|
||||
var textContent = await page.$eval(
|
||||
expect.text.selector,
|
||||
function (el) { return el.textContent; }
|
||||
);
|
||||
if (!textContent || !textContent.includes(expect.text.contains)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
run().catch(function (err) {
|
||||
console.error("Fatal error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user