#!/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_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); const ROOT_URI = process.env.MNOTE_KNOWLEDGE_RAG_ROOT_URI || "file:///mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space"; const WORKSPACE_ID = process.env.MNOTE_KNOWLEDGE_RAG_WORKSPACE_ID || "local-ws:mnote-e2e:my-space"; const QUERY = process.env.MNOTE_KNOWLEDGE_RAG_OFFICE_QUERY || "三乙基硅"; const EXPECTED_RESOURCE = process.env.MNOTE_KNOWLEDGE_RAG_EXPECTED_RESOURCE || "有机合成中的保护基/[OCR]_有机合成中的保护基-酚羰基羧基巯基的保护_20250201_1908.layered_删减-2025-02-04 18-59-42.docx"; const EXPECTED_BLOCK_ID = process.env.MNOTE_KNOWLEDGE_RAG_EXPECTED_BLOCK_ID || "0ace5daa070f0911e09d8ab37c64eeea"; const EXPECTED_HIGHLIGHT = process.env.MNOTE_KNOWLEDGE_RAG_EXPECTED_HIGHLIGHT || "三乙基硅酯"; const OUTPUT_DIR = path.join(process.cwd(), "tmp", "task540-knowledge-rag-office-result-open-locator-smoke"); const RESULT_PATH = path.join(OUTPUT_DIR, "result.json"); const SEARCH_SCREENSHOT_PATH = path.join(OUTPUT_DIR, "office-search-result.png"); const OPEN_SCREENSHOT_PATH = path.join(OUTPUT_DIR, "office-open-locator.png"); const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000); const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium"] .find((candidate) => fs.existsSync(candidate)); async function main() { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); const browser = await chromium.launch({ headless: true, executablePath: CHROMIUM_EXECUTABLE_PATH }); const context = await browser.newContext({ viewport: { width: 1360, height: 900 } }); try { const auth = await context.request.post(`${BASE_URL}/api/auth`, { data: { action: "auth:signIn", args: { provider: "password", params: { email: "mnote.e2e@example.com", password: process.env.MNOTE_E2E_PASSWORD || "MnoteE2E123!", flow: "signIn", }, }, }, }); assert(auth.ok(), `登录失败: ${auth.status()} ${await auth.text()}`); const apiSearch = await context.request.post(`${BASE_URL}/api/knowledge-rag/search`, { data: { rootUri: ROOT_URI, workspaceId: WORKSPACE_ID, query: QUERY, mode: "mix", topK: 8, chunkTopK: 8, includeChunkContent: true, sourcePaths: [EXPECTED_RESOURCE], }, }); assert(apiSearch.ok(), `knowledge-rag search 失败: ${apiSearch.status()} ${await apiSearch.text()}`); const apiPayload = await apiSearch.json(); const apiFirst = apiPayload.results?.[0]; assert(apiFirst, `API 未返回资料库结果: ${JSON.stringify(apiPayload, null, 2).slice(0, 3000)}`); assert.equal(apiFirst.locator?.blockId, EXPECTED_BLOCK_ID, `API locator blockId 不匹配: ${JSON.stringify(apiFirst, null, 2)}`); assert(String(apiFirst.snippet || "").includes(EXPECTED_HIGHLIGHT), `API snippet 未命中 ${EXPECTED_HIGHLIGHT}: ${apiFirst.snippet}`); const page = await context.newPage(); const pageErrors = []; const consoleErrors = []; page.on("pageerror", (error) => pageErrors.push(String(error?.stack || error?.message || error))); page.on("console", (message) => { if (["error", "warning"].includes(message.type())) consoleErrors.push(`${message.type()}: ${message.text()}`); }); await page.goto(`${BASE_URL}/`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); await page.evaluate(() => { window.localStorage?.setItem("mnote.search.collapseSourcesDefault.v1", "0"); }); await page.waitForSelector('[data-mnote-action="open-search-modal"]', { timeout: UI_TIMEOUT_MS }); await page.click('[data-mnote-action="open-search-modal"]'); await page.waitForSelector('[data-testid="wolai-search-input"]', { timeout: UI_TIMEOUT_MS }); await page.fill('[data-testid="wolai-search-input"]', QUERY); await page.evaluate(() => { const button = document.querySelector('[data-search-switch="knowledge"]'); if (button instanceof HTMLElement) { button.setAttribute("aria-checked", "true"); button.classList.add("is-on"); } document.querySelector('[data-testid="wolai-search-input"]') ?.dispatchEvent(new Event("input", { bubbles: true })); }); await page.waitForFunction( (expected) => { const first = document.querySelector('[data-testid="wolai-search-result-row"]'); return first && first.textContent.includes(expected); }, EXPECTED_HIGHLIGHT, { timeout: UI_TIMEOUT_MS }, ); await page.screenshot({ path: SEARCH_SCREENSHOT_PATH, fullPage: true }); await page.click('[data-testid="wolai-search-result-row"]'); await page.waitForFunction( (expectedResource) => document.querySelector(`[data-mnote-resource-tab-panel][data-resource-path="${CSS.escape(expectedResource)}"]`), EXPECTED_RESOURCE, { timeout: UI_TIMEOUT_MS }, ); await page.waitForTimeout(8_000); const state = await page.evaluate((expectedResource) => { const panel = document.querySelector(`[data-mnote-resource-tab-panel][data-resource-path="${CSS.escape(expectedResource)}"]`); const frame = panel?.querySelector("iframe.mnote-resource-tab-frame"); const result = { url: location.href, panelVisible: !!panel && !panel.hidden, panelResourcePath: panel ? panel.getAttribute("data-resource-path") : "", panelBlockId: panel ? panel.getAttribute("data-mnote-evidence-block-id") : "", panelEvidenceText: panel ? panel.getAttribute("data-mnote-evidence-text") : "", frameSrc: frame ? frame.getAttribute("src") : "", }; if (frame?.contentDocument) { const doc = frame.contentDocument; const viewer = doc.querySelector("#mnote-office-viewer"); const highlighted = doc.querySelector('[data-mnote-office-evidence-target="true"]'); result.iframe = { readyState: doc.readyState, status: doc.documentElement.getAttribute("data-mnote-office-preview-status") || "", applied: doc.documentElement.getAttribute("data-mnote-office-evidence-applied") || "", bodyEvidenceText: doc.body?.dataset?.evidenceText || "", textHasQuery: (viewer?.textContent || "").includes("三乙基硅"), scrollY: frame.contentWindow?.scrollY || 0, highlightedText: highlighted?.textContent?.slice(0, 160) || "", highlightedTop: highlighted ? Math.round(highlighted.getBoundingClientRect().top) : null, }; } return result; }, EXPECTED_RESOURCE); await page.screenshot({ path: OPEN_SCREENSHOT_PATH, fullPage: true }); assert.equal(state.panelVisible, true, `搜索结果未打开资源标签: ${JSON.stringify(state, null, 2)}`); assert.equal(state.panelResourcePath, EXPECTED_RESOURCE, `资源路径不匹配: ${JSON.stringify(state, null, 2)}`); assert.equal(state.panelBlockId, EXPECTED_BLOCK_ID, `资源标签 blockId 不匹配: ${JSON.stringify(state, null, 2)}`); assert.equal(state.iframe?.applied, "true", `Office preview 未应用 evidence locator: ${JSON.stringify(state, null, 2)}`); assert(String(state.iframe?.highlightedText || "").includes(EXPECTED_HIGHLIGHT), `Office preview 未高亮 ${EXPECTED_HIGHLIGHT}: ${JSON.stringify(state, null, 2)}`); const result = { ok: true, baseUrl: BASE_URL, rootUri: ROOT_URI, workspaceId: WORKSPACE_ID, query: QUERY, expectedResource: EXPECTED_RESOURCE, expectedBlockId: EXPECTED_BLOCK_ID, apiFirst: { snippet: apiFirst.snippet, blockId: apiFirst.locator?.blockId, citationUrl: apiFirst.citationUrl, }, state, pageErrors, consoleErrors, screenshots: { search: SEARCH_SCREENSHOT_PATH, open: OPEN_SCREENSHOT_PATH, }, }; fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); console.log(JSON.stringify(result, null, 2)); } finally { await browser.close().catch(() => undefined); } } main().catch((error) => { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); fs.writeFileSync( path.join(OUTPUT_DIR, "failure.json"), `${JSON.stringify({ ok: false, error: error.stack || error.message || String(error) }, null, 2)}\n`, "utf8", ); console.error(error.stack || error.message || String(error)); process.exit(1); });