feat: cut over rust web main shell
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
|
||||
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
|
||||
const OUTPUT_DIR = path.resolve(__dirname, "..", "test-results");
|
||||
const SCREENSHOT_PATH = path.join(OUTPUT_DIR, "task119-rust-web-wolai-visual-regression-root.png");
|
||||
|
||||
function assertBox(box, label) {
|
||||
assert(box, `${label} 缺少可见布局盒`);
|
||||
assert(box.width > 0 && box.height > 0, `${label} 尺寸异常: ${JSON.stringify(box)}`);
|
||||
}
|
||||
|
||||
async function boundingBox(locator, label) {
|
||||
await locator.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
const box = await locator.boundingBox();
|
||||
assertBox(box, label);
|
||||
return box;
|
||||
}
|
||||
|
||||
function boxesIntersect(a, b) {
|
||||
return !(
|
||||
a.x + a.width <= b.x ||
|
||||
b.x + b.width <= a.x ||
|
||||
a.y + a.height <= b.y ||
|
||||
b.y + b.height <= a.y
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } });
|
||||
|
||||
try {
|
||||
const response = await page.goto(`${BASE_URL}/`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
timeout: UI_TIMEOUT_MS,
|
||||
});
|
||||
assert(response, "根页没有返回响应");
|
||||
assert.equal(response.status(), 200, `根页状态码异常: ${response.status()}`);
|
||||
assert.equal(response.headers()["x-mnote-web-owner"], "mnote-web", "根页必须由 mnote-web 拥有");
|
||||
|
||||
await page.waitForSelector('[data-mnote-shell="workspace"]', { timeout: UI_TIMEOUT_MS });
|
||||
const bodyText = await page.locator("body").innerText({ timeout: UI_TIMEOUT_MS });
|
||||
assert(!bodyText.includes("欢迎使用 MNOTE 知识管理平台"), "根页仍停留在极简欢迎页");
|
||||
|
||||
const sidebar = page.getByTestId("wolai-sidebar");
|
||||
const topbar = page.getByTestId("wolai-topbar");
|
||||
const floatingAi = page.getByTestId("wolai-floating-ai");
|
||||
const floatingHelp = page.getByTestId("wolai-floating-help");
|
||||
const content = page.locator(".mnote-content").first();
|
||||
const homeContent = page.locator(".mnote-home").first();
|
||||
|
||||
const sidebarBox = await boundingBox(sidebar, "左侧栏");
|
||||
const topbarBox = await boundingBox(topbar, "顶栏");
|
||||
const contentBox = await boundingBox(content, "主内容区");
|
||||
const homeContentBox = await boundingBox(homeContent, "首页正文");
|
||||
const floatingAiBox = await boundingBox(floatingAi, "AI 浮动按钮");
|
||||
const floatingHelpBox = await boundingBox(floatingHelp, "帮助浮动按钮");
|
||||
|
||||
assert(sidebarBox.width >= 240 && sidebarBox.width <= 340, `左栏宽度应稳定在 Wolai 范围内: ${sidebarBox.width}`);
|
||||
assert(sidebarBox.x >= -1 && sidebarBox.x <= 1, `左栏应贴齐视口左侧: ${sidebarBox.x}`);
|
||||
assert(topbarBox.x >= sidebarBox.width - 2, "顶栏不应覆盖左侧栏");
|
||||
assert(contentBox.x >= sidebarBox.width - 2, "主内容区不应被侧栏覆盖");
|
||||
assert(topbarBox.y >= -1 && topbarBox.height >= 36 && topbarBox.height <= 72, `顶栏高度异常: ${topbarBox.height}`);
|
||||
assert(contentBox.y >= topbarBox.y + topbarBox.height - 2, "主内容区不应被顶栏遮挡");
|
||||
assert(floatingAiBox.x > sidebarBox.width, "AI 浮动按钮不应落入左侧栏");
|
||||
assert(floatingHelpBox.x > sidebarBox.width, "帮助浮动按钮不应落入左侧栏");
|
||||
assert(!boxesIntersect(floatingAiBox, homeContentBox), "AI 浮动按钮不应遮挡首页正文");
|
||||
assert(!boxesIntersect(floatingHelpBox, homeContentBox), "帮助浮动按钮不应遮挡首页正文");
|
||||
assert(floatingHelpBox.y > floatingAiBox.y, "帮助按钮应位于 AI 按钮下方");
|
||||
|
||||
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
baseUrl: BASE_URL,
|
||||
screenshot: SCREENSHOT_PATH,
|
||||
sidebarWidth: Math.round(sidebarBox.width),
|
||||
topbarHeight: Math.round(topbarBox.height),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
await page.close().catch(() => undefined);
|
||||
await browser.close().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.stack || error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user