"use strict"; const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); const MNOTE_WEB_BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3104").replace(/\/+$/, ""); const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_TIMEOUT_MS || 20_000); const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000); const TEST_LOGIN_BUTTON_NAME = "测试账号快速登录"; function assert(condition, message) { if (!condition) { throw new Error(message); } } async function requestJson(requestContext, path, init = {}) { const response = await requestContext.fetch(`${BASE_URL}${path}`, { ...init, headers: init.data !== undefined ? { "content-type": "application/json", ...(init.headers || {}), } : { ...(init.headers || {}), }, timeout: REQUEST_TIMEOUT_MS, }); const text = await response.text(); let payload = null; try { payload = text ? JSON.parse(text) : null; } catch { payload = text; } if (!response.ok()) { throw new Error( `${path} 请求失败: ${response.status()} ${response.statusText()} ${ typeof payload === "string" ? payload : JSON.stringify(payload) }`, ); } const contentType = response.headers()["content-type"] || ""; if (!contentType.includes("application/json")) { const snippet = typeof payload === "string" ? payload.slice(0, 200) : JSON.stringify(payload).slice(0, 200); throw new Error(`${path} 返回了非 JSON 内容:${snippet}`); } return payload; } async function createTempDocument(requestContext, parentId = null) { const payload = await requestJson(requestContext, "/api/documents/create", { method: "POST", data: { parentId }, }); assert(payload && typeof payload.id === "string", "创建临时页面失败:缺少 id"); assert(payload && typeof payload.workspace_id === "string", "创建临时页面失败:缺少 workspace_id"); return { documentId: payload.id, workspaceId: payload.workspace_id, }; } async function renameDocument(requestContext, workspaceId, documentId, title) { await requestJson(requestContext, "/api/documents/title", { method: "POST", data: { workspaceId, documentId, title, }, }); } async function purgeDocument(requestContext, documentId) { await requestJson(requestContext, "/api/documents/purge", { method: "POST", data: { documentId }, }); } async function getViewerIdentity(requestContext) { const payload = await requestJson(requestContext, "/api/auth/whoami", { method: "GET" }); assert(payload && typeof payload.userId === "string" && payload.userId, "获取当前用户失败:缺少 userId"); return payload; } async function ensureAuthenticated(page, requestContext) { await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); if (page.url().includes("/auth")) { const quickLoginButton = page.getByRole("button", { name: TEST_LOGIN_BUTTON_NAME }); await quickLoginButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await quickLoginButton.click({ timeout: UI_TIMEOUT_MS }); await page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: UI_TIMEOUT_MS, }); } return await getViewerIdentity(requestContext); } async function prepareTempTreeFixture(requestContext) { const uniqueSuffix = Date.now().toString(); const parentTitle = `task-tree-parent-${uniqueSuffix}`; const childTitle = `task-tree-child-${uniqueSuffix}`; const parent = await createTempDocument(requestContext, null); const child = await createTempDocument(requestContext, parent.documentId); await renameDocument(requestContext, parent.workspaceId, parent.documentId, parentTitle); await renameDocument(requestContext, parent.workspaceId, child.documentId, childTitle); return { uniqueSuffix, workspaceId: parent.workspaceId, parentId: parent.documentId, childId: child.documentId, parentTitle, childTitle, createdIds: [parent.documentId, child.documentId], }; } async function cleanupDocuments(requestContext, createdIds) { for (const documentId of [...createdIds].reverse()) { await purgeDocument(requestContext, documentId); } } async function openDocument(page, workspaceId, documentId) { const url = `${BASE_URL}/documents/${documentId}?workspaceId=${encodeURIComponent(workspaceId)}`; await page.goto(url, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); return url; } async function openSectionView(page) { const button = page.getByRole("button", { name: "分组" }); await button.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await button.click({ timeout: UI_TIMEOUT_MS }); } async function openFilesystemView(page) { const button = page.getByRole("button", { name: "文件" }); await button.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await button.click({ timeout: UI_TIMEOUT_MS }); } async function ensurePageOptionsVisible(page) { const toggle = page.getByRole("button", { name: /显示页面选项|隐藏页面选项/ }); await toggle.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const label = (await toggle.getAttribute("aria-label")) || ""; if (label.includes("显示")) { await toggle.click({ timeout: UI_TIMEOUT_MS }); } } module.exports = { BASE_URL, MNOTE_WEB_BASE_URL, REQUEST_TIMEOUT_MS, UI_TIMEOUT_MS, assert, cleanupDocuments, createTempDocument, ensureAuthenticated, ensurePageOptionsVisible, getViewerIdentity, openDocument, openFilesystemView, openSectionView, prepareTempTreeFixture, purgeDocument, renameDocument, requestJson, };