426 خطوط
16 KiB
JavaScript
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();
|