Files
mnote/scripts/task500-ui-debug-components-smoke.js
2026-06-25 21:08:17 +08:00

157 lines
7.4 KiB
JavaScript

"use strict";
const fs = require("node:fs");
const path = require("node:path");
const { chromium } = require("playwright");
const { BASE_URL, UI_TIMEOUT_MS, assert, ensureAuthenticated } = require("./tree-shell-smoke-helpers");
const MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES = process.env.MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES || "0";
if (MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES !== "1") {
console.log("[task500] 跳过: MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES 未设为 1 (当前值: '%s')", MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES);
console.log("[task500] 设置 MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES=1 并重启 mnote-web 后再运行此 smoke。");
process.exit(0);
}
const PAGE_URL = `${BASE_URL}/ui-debug/components`;
const OUT_DIR = process.env.MNOTE_UI_COMPONENTS_OUTPUT_DIR || path.resolve(__dirname, "..", "tmp", "ui-components");
async function main() {
console.log("[task500] 启动 UI Debug 组件矩阵 smoke …");
fs.mkdirSync(OUT_DIR, { recursive: true });
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// 1. 认证
await ensureAuthenticated(page);
console.log("[task500] ✓ 已认证");
// 2. 导航到 /ui-debug/components
await page.goto(PAGE_URL, { waitUntil: "networkidle", timeout: UI_TIMEOUT_MS });
console.log("[task500] ✓ 页面已加载: %s", PAGE_URL);
// 3. 页面标题
const title = await page.title();
assert(title.includes("UI Debug"), `标题应包含 "UI Debug",实际: "${title}"`);
console.log("[task500] ✓ 页面标题: %s", title);
// 4. 页面根元素
const root = page.locator(".ui-debug-root");
await root.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
console.log("[task500] ✓ .ui-debug-root 可见");
// 5. Button 矩阵应当可见
const matrix = page.locator(".ui-debug-matrix");
await matrix.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
const btnCount = await matrix.locator("button").count();
assert(btnCount >= 12, `按钮矩阵应至少有 12 个按钮,实际: ${btnCount}`);
console.log("[task500] ✓ 按钮矩阵: %d 个按钮", btnCount);
// 6. 主题切换
const darkBtn = page.locator("[data-ui-debug-theme=\"dark\"]");
await darkBtn.click();
await page.waitForTimeout(200);
const theme = await root.getAttribute("data-ui-debug-theme");
assert(theme === "dark", `主题应为 dark,实际: "${theme}"`);
console.log("[task500] ✓ 主题切换: dark");
// 切回 light
const lightBtn = page.locator("[data-ui-debug-theme=\"light\"]");
await lightBtn.click();
await page.waitForTimeout(200);
const theme2 = await root.getAttribute("data-ui-debug-theme");
assert(theme2 === "light", `主题应为 light,实际: "${theme2}"`);
// 7. Dialog 对话框
const dialogTrigger = page.locator("[data-ui-debug-dialog=\"info\"]");
await dialogTrigger.click();
await page.waitForTimeout(200);
const dialogOverlay = page.locator("#ui-debug-dialog-overlay");
const overlayVisible = await dialogOverlay.isVisible();
assert(overlayVisible, "对话框应可见");
const dialogTitle = await page.locator("#ui-debug-dialog-title").textContent();
assert(dialogTitle.includes("提示"), `对话框标题应包含"提示",实际: "${dialogTitle}"`);
console.log("[task500] ✓ 对话框打开: %s", dialogTitle);
// 关闭对话框
const closeBtn = page.locator("[data-ui-debug-dialog-close=\"true\"]").first();
await closeBtn.click();
await page.waitForTimeout(200);
const hidden = await dialogOverlay.getAttribute("hidden");
assert(hidden !== null, "对话框应已关闭 (hidden)");
console.log("[task500] ✓ 对话框已关闭");
// 8. Popover 菜单
const popoverTrigger = page.locator("[data-ui-debug-popover=\"actions\"]");
await popoverTrigger.click();
await page.waitForTimeout(200);
const popoverPanel = page.locator("[data-ui-debug-popover-panel=\"actions\"]");
const popoverVisible = await popoverPanel.isVisible();
assert(popoverVisible, "Popover 菜单应可见");
const menuItems = await popoverPanel.locator("[role=\"menuitem\"]").count();
assert(menuItems >= 2, `Popover 应至少有 2 个菜单项,实际: ${menuItems}`);
console.log("[task500] ✓ Popover 打开: %d 个菜单项", menuItems);
// 关闭 popover
await page.locator("body").click({ position: { x: 10, y: 10 } });
await page.waitForTimeout(200);
// 9. Toast
const toastTrigger = page.locator("[data-ui-debug-toast-kind=\"success\"]").first();
await toastTrigger.click();
await page.waitForTimeout(400);
const toast = page.locator(".ui-debug-toast--visible");
const toastCount = await toast.count();
assert(toastCount >= 1, `Toast 应可见,实际数量: ${toastCount}`);
console.log("[task500] ✓ Toast 显示");
// 10. Input / Select / Textarea
const textInput = page.locator("#ui-debug-input-text");
await textInput.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
await textInput.fill("测试文本");
const inputValue = await textInput.inputValue();
assert(inputValue === "测试文本", `输入框值应为"测试文本",实际: "${inputValue}"`);
console.log("[task500] ✓ 文本输入: %s", inputValue);
const selectEl = page.locator("#ui-debug-select");
await selectEl.selectOption("b");
const selectValue = await selectEl.inputValue();
assert(selectValue === "b", `下拉选择值应为"b",实际: "${selectValue}"`);
console.log("[task500] ✓ 下拉选择: %s", selectValue);
const textarea = page.locator("#ui-debug-textarea");
await textarea.fill("多行\n文本");
const textareaValue = await textarea.inputValue();
assert(textareaValue === "多行\n文本", `文本区域值应包含"多行",实际: "${textareaValue}"`);
console.log("[task500] ✓ 文本区域: %s", textareaValue.replace(/\n/g, "\\n"));
// 11. CSS 变量检查
const cssVar = await root.evaluate((el) => {
const style = getComputedStyle(el);
return style.getPropertyValue("--ui-debug-primary").trim();
});
assert(cssVar.length > 0, `--ui-debug-primary 应有值,实际: "${cssVar}"`);
console.log("[task500] ✓ CSS 变量 --ui-debug-primary: %s", cssVar);
const screenshotPath = path.join(OUT_DIR, "ui-debug-components.png");
await page.screenshot({ path: screenshotPath, fullPage: true });
console.log("[task500] ✓ 截图输出: %s", screenshotPath);
console.log("[task500] ✅ 全部断言通过");
} catch (error) {
console.error("[task500] ✗ 失败:", error.message);
try {
const failurePath = path.join(OUT_DIR, "task500-failure.png");
await page.screenshot({ path: failurePath, fullPage: true });
console.log("[task500] 失败截图: %s", failurePath);
} catch (_) {}
process.exitCode = 1;
} finally {
await browser.close();
}
}
main();