Learning_Management_System_LMS/scripts/ui-audit-cdp.mjs

426 خطوط
16 KiB
JavaScript

import { writeFile } from "node:fs/promises";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const targets = await fetch("http://127.0.0.1:9222/json").then((response) => response.json());
const target = targets.find((item) => item.type === "page" && item.url.startsWith("http://127.0.0.1:3000"));
if (!target) throw new Error("Local application tab was not found.");
const socket = new WebSocket(target.webSocketDebuggerUrl);
await new Promise((resolve, reject) => {
socket.addEventListener("open", resolve, { once: true });
socket.addEventListener("error", reject, { once: true });
});
let id = 0;
const pending = new Map();
socket.addEventListener("message", (event) => {
const message = JSON.parse(String(event.data));
if (!message.id || !pending.has(message.id)) return;
const { resolve, reject } = pending.get(message.id);
pending.delete(message.id);
if (message.error) reject(new Error(message.error.message));
else resolve(message.result);
});
function send(method, params = {}) {
const messageId = ++id;
socket.send(JSON.stringify({ id: messageId, method, params }));
return new Promise((resolve, reject) => pending.set(messageId, { resolve, reject }));
}
async function evaluate(expression) {
const result = await send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true });
if (result.exceptionDetails) throw new Error(JSON.stringify(result.exceptionDetails));
return result.result.value;
}
await send("Page.enable");
await send("Runtime.enable");
await sleep(1500);
const action = process.argv[2] ?? "status";
const requestedViewport = process.argv[4]?.match(/^(\d+)x(\d+)$/);
if (requestedViewport) {
await send("Emulation.setDeviceMetricsOverride", {
width: Number(requestedViewport[1]),
height: Number(requestedViewport[2]),
deviceScaleFactor: 1,
mobile: Number(requestedViewport[1]) < 720,
});
await send("Page.reload", { ignoreCache: true });
await sleep(2200);
}
if (action === "go-login") {
await evaluate(`(() => { location.href = "/login"; return true; })()`);
await sleep(2200);
}
if (action === "login") {
await evaluate(`(() => {
const button = [...document.querySelectorAll("button")].find((item) => item.textContent.trim() === "ورود");
if (!button) throw new Error("Login button not found");
button.click();
return true;
})()`);
await sleep(5000);
}
if (action === "profile") {
await evaluate(`(() => {
const openDialogClose = [...document.querySelectorAll("button")].find((item) => (item.getAttribute("aria-label") || "").startsWith("بستن تغییر کاربر"));
if (openDialogClose) openDialogClose.click();
const button = [...document.querySelectorAll("button")].find((item) => (item.title || "").includes(" - ") && !(item.title || "").includes("تغییر"));
if (!button) throw new Error("Profile button not found");
button.click();
return true;
})()`);
await sleep(1200);
}
if (action === "dark-mode") {
await evaluate(`(() => {
const trigger = [...document.querySelectorAll("button")].find((item) => item.getAttribute("aria-label") === "تنظیمات سریع" || item.title === "تنظیمات سریع");
if (!trigger) throw new Error("Quick settings trigger not found");
trigger.click();
return true;
})()`);
await sleep(700);
await evaluate(`(() => {
const dialog = document.querySelector("[role='dialog']") || document;
const button = [...dialog.querySelectorAll("button")].find((item) => item.textContent.trim() === "تاریک");
if (!button) throw new Error("Dark theme button not found");
button.click();
return true;
})()`);
await sleep(1200);
}
if (action === "close-dialog") {
await evaluate(`(() => {
const button = [...document.querySelectorAll("button")].find((item) => (item.getAttribute("aria-label") || "").startsWith("بستن "));
if (!button) return false;
button.click();
return true;
})()`);
await sleep(800);
}
if (action === "dismiss-tour") {
await evaluate(`(() => {
const button = [...document.querySelectorAll("button")].find((item) => item.textContent.includes("دیگر نمایش نده"));
if (!button) return false;
button.click();
return true;
})()`);
await sleep(800);
}
if (action === "open-control-center") {
await evaluate(`(() => {
const tourButton = [...document.querySelectorAll("button")].find((item) => item.textContent.includes("دیگر نمایش نده"));
if (tourButton) tourButton.click();
const trigger = [...document.querySelectorAll("button")].find((item) => item.getAttribute("aria-label") === "تنظیمات سریع" || item.title === "تنظیمات سریع");
if (!trigger) throw new Error("Quick settings trigger not found");
trigger.click();
return true;
})()`);
await sleep(900);
}
if (action === "toggle-workspace") {
await evaluate(`(() => {
const tourButton = [...document.querySelectorAll("button")].find((item) => item.textContent.includes("دیگر نمایش نده"));
if (tourButton) tourButton.click();
const button = document.querySelector("[data-dock-workspace-toggle]");
if (!button) throw new Error("Dock workspace toggle not found");
button.click();
return true;
})()`);
await sleep(1200);
}
if (action === "switch-employee") {
await evaluate(`(() => {
const switcher = [...document.querySelectorAll("button")].find((item) => item.title === "تغییر کاربر تستی");
if (!switcher) throw new Error("Test user switcher not found");
switcher.click();
return true;
})()`);
await sleep(800);
await evaluate(`(() => {
const dialog = document.querySelector("[role='dialog']");
const button = [...(dialog?.querySelectorAll("button") ?? [])].find((item) => item.textContent.includes("کارکنان"));
if (!button) throw new Error("Employee test account not found");
button.click();
return true;
})()`);
await sleep(2600);
}
if (action === "open-exams" || action === "open-my-courses") {
const appTitle = action === "open-exams" ? "آزمون‌ها" : "دوره‌های من";
await evaluate(`(() => {
const title = ${JSON.stringify(appTitle)};
const button = [...document.querySelectorAll("button")].find((item) => item.title === title && !item.closest("[data-window-id]"))
|| [...document.querySelectorAll("button")].find((item) => item.textContent.trim() === title && !item.closest("[data-window-id]"));
if (!button) throw new Error(title + " app button not found");
button.click();
return true;
})()`);
await sleep(2200);
}
if (action === "open-launcher") {
await evaluate(`(() => {
const button = [...document.querySelectorAll("button")].find((item) => item.getAttribute("aria-label") === "نمایش برنامه‌ها");
if (!button) throw new Error("Launcher switch not found");
button.click();
return true;
})()`);
await sleep(900);
}
if (action === "close-windows-and-open-launcher") {
await evaluate(`(() => {
const closeButtons = [...document.querySelectorAll("[data-window-id] button")].filter((item) => {
const label = item.getAttribute("aria-label") || item.title || "";
return label.includes("بستن");
});
closeButtons.forEach((button) => button.click());
return closeButtons.length;
})()`);
await sleep(900);
await evaluate(`(() => {
const button = [...document.querySelectorAll("button")].find((item) => item.getAttribute("aria-label") === "نمایش برنامه‌ها");
if (!button) throw new Error("Launcher switch not found");
button.click();
return true;
})()`);
await sleep(900);
}
if (action === "open-exam-tab") {
await evaluate(`(() => {
const windowItem = [...document.querySelectorAll("[data-window-id]")].at(-1);
const button = [...(windowItem?.querySelectorAll("main button") ?? [])].find((item) => item.textContent.trim().startsWith("آزمون‌های من"));
if (!button) throw new Error("Learner exam tile not found");
button.click();
return true;
})()`);
await sleep(1000);
}
if (action === "drag-window") {
const point = await evaluate(`(() => {
const item = [...document.querySelectorAll("[data-window-id]")].at(-1);
const toolbar = item?.querySelector(".window-module-toolbar");
if (!item || !toolbar) throw new Error("Draggable window toolbar not found");
const box = toolbar.getBoundingClientRect();
const point = { x: box.left + box.width * 0.48, y: box.top + 18 };
const target = document.elementFromPoint(point.x, point.y);
window.__auditDragTarget = {
point,
tag: target?.tagName,
className: String(target?.className ?? ""),
cancel: Boolean(target?.closest(".mac-window-no-drag,button,input,select,textarea,a,[role='button']")),
handle: Boolean(target?.closest(".mac-window-handle")),
};
return point;
})()`);
await send("Input.dispatchMouseEvent", { type: "mousePressed", button: "left", buttons: 1, clickCount: 1, x: point.x, y: point.y });
await sleep(120);
await send("Input.dispatchMouseEvent", { type: "mouseMoved", button: "left", buttons: 1, x: point.x + 24, y: point.y + 16 });
await sleep(120);
await send("Input.dispatchMouseEvent", { type: "mouseMoved", button: "left", buttons: 1, x: point.x + 64, y: point.y + 42 });
await sleep(120);
await send("Input.dispatchMouseEvent", { type: "mouseReleased", button: "left", buttons: 0, clickCount: 1, x: point.x + 64, y: point.y + 42 });
await sleep(900);
}
if (action === "open-users") {
await evaluate(`(() => {
const profileClose = [...document.querySelectorAll("button")].find((item) => item.textContent.trim() === "بستن");
if (profileClose) profileClose.click();
const button = [...document.querySelectorAll("button")].find((item) => item.title === "کاربران سازمان");
if (!button) throw new Error("Users app button not found");
button.click();
return true;
})()`);
await sleep(1800);
}
if (action === "open-social") {
await evaluate(`(() => {
if (innerWidth < 720) {
location.href = "/apps/social";
return true;
}
const profileClose = [...document.querySelectorAll("button")].find((item) => item.textContent.trim() === "بستن");
if (profileClose) profileClose.click();
const button = [...document.querySelectorAll("button")].find((item) => item.title === "شبکه سازمانی" || item.textContent.trim() === "شبکه سازمانی");
if (!button) throw new Error("Social app button not found");
button.click();
return true;
})()`);
await sleep(2200);
}
if (action === "dock-menu") {
const point = await evaluate(`(() => {
const item = document.querySelector("[data-dock-app-id]");
if (!item) throw new Error("Dock item not found");
const box = item.getBoundingClientRect();
const point = { x: box.left + box.width / 2, y: box.top + box.height / 2 };
window.__auditDockClick = point;
item.dispatchEvent(new MouseEvent("contextmenu", {
bubbles: true,
cancelable: true,
button: 2,
buttons: 2,
clientX: point.x,
clientY: point.y,
view: window,
}));
return point;
})()`);
await sleep(700);
}
if (action === "dock-menu-open") {
const isDashboard = await evaluate(`location.pathname === "/dashboard"`);
if (!isDashboard) {
await evaluate(`(() => { location.href = "/dashboard"; return true; })()`);
await sleep(2600);
}
await evaluate(`(() => {
const item = document.querySelector("[data-dock-app-id]");
const button = item?.querySelector("button");
if (!item || !button) throw new Error("Dock item not found");
button.click();
return true;
})()`);
await sleep(1500);
await evaluate(`(() => {
const item = document.querySelector("[data-dock-app-id]");
if (!item) throw new Error("Dock item not found");
const box = item.getBoundingClientRect();
const point = { x: box.left + box.width / 2, y: box.top + box.height / 2 };
window.__auditDockClick = point;
item.dispatchEvent(new MouseEvent("contextmenu", {
bubbles: true,
cancelable: true,
button: 2,
buttons: 2,
clientX: point.x,
clientY: point.y,
view: window,
}));
return true;
})()`);
await sleep(700);
}
if (action === "focus-check") {
await send("Input.dispatchKeyEvent", { type: "keyDown", key: "Tab", code: "Tab", windowsVirtualKeyCode: 9 });
await send("Input.dispatchKeyEvent", { type: "keyUp", key: "Tab", code: "Tab", windowsVirtualKeyCode: 9 });
await evaluate(`(() => {
const target = [...document.querySelectorAll("button")].find((item) => item.textContent.trim() === "فید سازمانی")
|| document.querySelector("button");
if (!target) throw new Error("Focusable control not found");
target.focus();
return target === document.activeElement;
})()`);
await sleep(350);
}
const state = await evaluate(`(() => ({
url: location.href,
title: document.title,
body: document.body.innerText.slice(0, 1200),
buttons: [...document.querySelectorAll("button")].slice(0, 80).map((item) => ({ text: item.innerText.trim(), title: item.title, aria: item.getAttribute("aria-label"), box: item.getBoundingClientRect().toJSON() })),
localStorage: Object.keys(localStorage),
viewport: { width: innerWidth, height: innerHeight, dpr: devicePixelRatio },
ranges: [...document.querySelectorAll('input[type="range"]')].map((item) => ({
aria: item.getAttribute("aria-label"),
min: item.min,
max: item.max,
value: item.value,
rangeValue: getComputedStyle(item).getPropertyValue("--range-value").trim(),
box: item.getBoundingClientRect().toJSON(),
})),
focus: (() => {
const item = document.activeElement;
if (!item || item === document.body) return null;
const styles = getComputedStyle(item);
return {
tag: item.tagName,
text: item.textContent?.trim() || "",
aria: item.getAttribute("aria-label"),
outline: styles.outline,
boxShadow: styles.boxShadow,
box: item.getBoundingClientRect().toJSON(),
};
})(),
dockMenu: (() => {
const panel = document.querySelector("[data-dock-context-menu]");
if (!panel) return null;
const styles = getComputedStyle(panel);
return {
click: window.__auditDockClick || null,
box: panel.getBoundingClientRect().toJSON(),
rowGap: styles.rowGap,
items: [...panel.querySelectorAll("button")].map((item) => {
const itemStyles = getComputedStyle(item);
return {
text: item.innerText.trim(),
background: itemStyles.backgroundColor,
border: itemStyles.border,
color: itemStyles.color,
box: item.getBoundingClientRect().toJSON(),
};
}),
};
})(),
dragTarget: window.__auditDragTarget ?? null,
windows: [...document.querySelectorAll("[data-window-id]")].map((item) => {
const box = item.getBoundingClientRect();
const controls = item.querySelector("[data-window-controls]");
const toolbar = item.querySelector(".window-module-toolbar");
const selectedTabs = [...item.querySelectorAll("[role='tab'][aria-selected='true']")];
return {
id: item.getAttribute("data-window-id"),
box: box.toJSON(),
controls: controls?.getBoundingClientRect().toJSON() ?? null,
toolbar: toolbar?.getBoundingClientRect().toJSON() ?? null,
selectedTabs: selectedTabs.map((tab) => {
const styles = getComputedStyle(tab);
return {
text: tab.textContent.trim(),
color: styles.color,
background: styles.backgroundColor,
boxShadow: styles.boxShadow,
box: tab.getBoundingClientRect().toJSON(),
};
}),
hasLegacyTitleBar: [...item.children].some((child) => child.tagName === "HEADER"),
hasRemovedMessage: item.textContent.includes("دوره جدید به دوره‌های من اضافه شد."),
};
}),
iconTiles: [...document.querySelectorAll("[data-app-icon-tile]")].slice(0, 40).map((item) => {
const styles = getComputedStyle(item);
const button = item.closest("button");
return {
box: item.getBoundingClientRect().toJSON(),
buttonBox: button?.getBoundingClientRect().toJSON() ?? null,
filter: styles.filter,
backdropFilter: styles.backdropFilter,
backgroundImage: styles.backgroundImage,
};
})
}))()`);
const screenshot = await send("Page.captureScreenshot", { format: "png", captureBeyondViewport: false });
const fileName = process.argv[3] ?? "ui-audit.png";
await writeFile(fileName, Buffer.from(screenshot.data, "base64"));
console.log(JSON.stringify({ ...state, screenshot: fileName }, null, 2));
socket.close();