#!/usr/bin/env node "use strict"; const assert = require("node:assert"); const { chromium } = require("playwright"); const { BASE_URL, UI_TIMEOUT_MS, cleanupDocuments, createTempDocument, ensureAuthenticated, renameDocument, } = require("./tree-shell-smoke-helpers"); async function main() { const suffix = Date.now().toString(36); const title = `TEST-HERMES-AI-mobile-${suffix}`; const sessionId = `mnote_mobile_${suffix}`; const runId = `run_mobile_${suffix}`; const createdIds = []; const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 390, height: 844 }, }); const page = await context.newPage(); try { await page.route("**/api/ai-agent/run", async (route) => { throw new Error(`移动端页面 AI 不应请求旧 /api/ai-agent/run: ${route.request().url()}`); }); await page.route("**/api/hermes/client/sessions", async (route) => { await route.fulfill({ status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ ok: true, sessionId, title: "移动端问答", traceId: "trace_mobile", persistence: "hermes_on_first_run", }), }); }); await page.route(`**/api/hermes/client/sessions/${sessionId}`, async (route) => { await route.fulfill({ status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ ok: true, sessionId, traceId: "trace_mobile_restore", session: { sessionId, messages: [{ role: "assistant", content: "Mobile restored" }], }, }), }); }); await page.route("**/api/hermes/client/runs", async (route) => { await route.fulfill({ status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ ok: true, sessionId, runId, events: [], traceId: "trace_mobile", }), }); }); await page.route(`**/api/hermes/client/events/${runId}`, async (route) => { await route.fulfill({ status: 200, headers: { "content-type": "text/event-stream; charset=utf-8" }, body: `data: ${JSON.stringify({ event: "message.delta", run_id: runId, session_id: sessionId, delta: "Mobile " })}\n\n` + `data: ${JSON.stringify({ event: "run.completed", run_id: runId, session_id: sessionId, output: "Mobile response" })}\n\n`, }); }); await ensureAuthenticated(page, context.request); const target = await createTempDocument(context.request); createdIds.push(target.documentId); await renameDocument(context.request, target.workspaceId, target.documentId, title); await page.goto( `${BASE_URL}/documents/${encodeURIComponent(target.documentId)}?workspaceId=${encodeURIComponent(target.workspaceId)}`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }, ); await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS }); const metrics = await page.locator('[data-testid="wolai-page-ai-drawer"]').evaluate((drawer) => { const panel = drawer.querySelector(".wolai-page-ai-panel"); const drawerRect = drawer.getBoundingClientRect(); const panelRect = panel ? panel.getBoundingClientRect() : drawerRect; return { viewportWidth: window.innerWidth, documentWidth: document.documentElement.scrollWidth, drawerLeft: drawerRect.left, drawerRight: drawerRect.right, panelLeft: panelRect.left, panelRight: panelRect.right, }; }); assert(metrics.drawerLeft >= 0, `drawer 左侧溢出: ${JSON.stringify(metrics)}`); assert(metrics.drawerRight <= metrics.viewportWidth + 1, `drawer 右侧溢出: ${JSON.stringify(metrics)}`); assert(metrics.panelLeft >= 0, `panel 左侧溢出: ${JSON.stringify(metrics)}`); assert(metrics.panelRight <= metrics.viewportWidth + 1, `panel 右侧溢出: ${JSON.stringify(metrics)}`); assert(metrics.documentWidth <= metrics.viewportWidth + 1, `页面出现横向滚动: ${JSON.stringify(metrics)}`); await page.locator("[data-page-ai-input]").fill(`请总结 ${title}`, { timeout: UI_TIMEOUT_MS }); await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS }); await page.waitForFunction( () => (document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "").includes("Mobile response"), null, { timeout: UI_TIMEOUT_MS }, ); console.log( JSON.stringify( { ok: true, baseUrl: BASE_URL, documentId: target.documentId, workspaceId: target.workspaceId, sessionId, runId, metrics, }, null, 2, ), ); } finally { await cleanupDocuments(context.request, createdIds).catch(() => undefined); await context.close().catch(() => undefined); await browser.close().catch(() => undefined); } } main().catch((error) => { console.error(error instanceof Error ? error.stack || error.message : String(error)); process.exit(1); });