"use strict"; const { chromium } = require("playwright"); const { BASE_URL, UI_TIMEOUT_MS, assert, cleanupDocuments, ensureAuthenticated, openDocument, openSectionView, prepareTempTreeFixture, } = require("./tree-shell-smoke-helpers"); async function runPrimaryPath(page, fixture) { const streamRequestPromise = page.waitForRequest( (request) => request.url().includes("/api/stream/events") && request.method() === "GET", { timeout: UI_TIMEOUT_MS }, ); await openDocument(page, fixture.workspaceId, fixture.parentId); await openSectionView(page); const streamRequest = await streamRequestPromise; const shellHost = page.getByTestId("sidebar-page-tree-shell"); const firstVisibleStart = Date.now(); await shellHost.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const parentRow = shellHost.locator(`[data-testid="page-tree-row"][data-node-id="${fixture.parentId}"]`); const childRow = shellHost.locator(`[data-testid="page-tree-row"][data-node-id="${fixture.childId}"]`); await parentRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await childRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const firstVisibleMs = Date.now() - firstVisibleStart; assert( (await shellHost.locator('iframe[title="mnote-web tree shell"]').count()) === 0, "Sidebar 主树仍残留 iframe tree shell", ); const navigateStart = Date.now(); await childRow.getByTestId("page-tree-open").click({ timeout: UI_TIMEOUT_MS }); await page.waitForURL((url) => url.toString().includes(`/documents/${fixture.childId}`), { timeout: UI_TIMEOUT_MS, }); const navigateMs = Date.now() - navigateStart; return { streamUrl: streamRequest.url(), firstVisibleMs, navigateMs, }; } async function runFallbackPath(context, fixture) { const page = await context.newPage(); let blockedCount = 0; await page.route("**/api/stream/events**", async (route) => { blockedCount += 1; await route.abort("failed"); }); try { await openDocument(page, fixture.workspaceId, fixture.parentId); await openSectionView(page); const shellHost = page.getByTestId("sidebar-page-tree-shell"); const fallbackStart = Date.now(); await shellHost.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await shellHost.locator(`[data-testid="page-tree-row"][data-node-id="${fixture.parentId}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS, }); await shellHost.locator(`[data-testid="page-tree-row"][data-node-id="${fixture.childId}"]`).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS, }); const fallbackVisibleMs = Date.now() - fallbackStart; assert(blockedCount > 0, "未命中 stream fallback 拦截"); assert( (await shellHost.locator('iframe[title="mnote-web tree shell"]').count()) === 0, "fallback 路径仍残留 iframe tree shell", ); return { blockedCount, fallbackVisibleMs, }; } finally { await page.close().catch(() => undefined); } } async function main() { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 1440, height: 960 }, }); const page = await context.newPage(); let caughtError = null; let result = null; let fixture = null; try { const viewer = await ensureAuthenticated(page, context.request); fixture = await prepareTempTreeFixture(context.request); const primary = await runPrimaryPath(page, fixture); const fallback = await runFallbackPath(context, fixture); result = { ok: true, baseUrl: BASE_URL, viewerUserId: viewer.userId, workspaceId: fixture.workspaceId, parentId: fixture.parentId, childId: fixture.childId, primary, fallback, }; console.log(JSON.stringify(result, null, 2)); } catch (error) { caughtError = error; } finally { if (fixture) { try { await cleanupDocuments(context.request, fixture.createdIds); } catch (cleanupError) { if (!caughtError) { caughtError = cleanupError; } else { console.error( `清理临时页面失败:${ cleanupError instanceof Error ? cleanupError.stack || cleanupError.message : String(cleanupError) }`, ); } } } await page.close().catch(() => undefined); await context.close().catch(() => undefined); await browser.close().catch(() => undefined); } if (caughtError) { throw caughtError; } } main().catch((error) => { console.error(error instanceof Error ? error.stack || error.message : String(error)); process.exit(1); });