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();