"use strict"; const { chromium } = require("playwright"); const { BASE_URL, UI_TIMEOUT_MS, assert, cleanupDocuments, ensureAuthenticated, ensurePageOptionsVisible, openDocument, openFilesystemView, openSectionView, prepareTempTreeFixture, } = require("./tree-shell-smoke-helpers"); async function runPageTreeChecks(page, fixture) { await openDocument(page, fixture.workspaceId, fixture.parentId); await openSectionView(page); const shellHost = page.getByTestId("sidebar-page-tree-shell"); 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, }); return { pageTreeVisible: true, }; } async function runFileTreeChecks(page, fixture) { await openFilesystemView(page); const shellHost = page.getByTestId("sidebar-file-tree-shell"); const openStart = Date.now(); await shellHost.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const parentDocRow = shellHost.locator(`[data-testid="filetree-doc-row"][data-doc-id="${fixture.parentId}"]`).first(); const childDocRow = shellHost.locator(`[data-testid="filetree-doc-row"][data-doc-id="${fixture.childId}"]`).first(); const parentIndexRow = shellHost.locator(`[data-testid="filetree-index-row"][data-doc-id="${fixture.parentId}"]`).first(); await parentDocRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await childDocRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await parentIndexRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const filetreeVisibleMs = Date.now() - openStart; assert( (await shellHost.locator('iframe[title="mnote-web tree shell"]').count()) === 0, "文件树主路径仍残留 iframe tree shell", ); const navigateStart = Date.now(); await childDocRow.click({ timeout: UI_TIMEOUT_MS }); await page.waitForURL((url) => url.toString().includes(`/documents/${fixture.childId}`), { timeout: UI_TIMEOUT_MS, }); const filetreeNavigateMs = Date.now() - navigateStart; return { filetreeVisibleMs, filetreeNavigateMs, }; } async function runPickerChecks(page, fixture) { await ensurePageOptionsVisible(page); const openButton = page.getByRole("button", { name: "移动/嵌入到..." }); await openButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const openStart = Date.now(); await openButton.click({ timeout: UI_TIMEOUT_MS }); const dialog = page.getByRole("dialog"); await dialog.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const pickerSurface = dialog.getByTestId("tree-picker-surface"); await pickerSurface.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const pickerVisibleMs = Date.now() - openStart; assert( (await dialog.locator('iframe[title="mnote-web tree shell"]').count()) === 0, "picker 默认路径仍残留 iframe tree shell", ); const rootPick = dialog.getByTestId("tree-picker-root"); await rootPick.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await page.keyboard.press("Escape"); await dialog.waitFor({ state: "hidden", timeout: UI_TIMEOUT_MS }); await openButton.click({ timeout: UI_TIMEOUT_MS }); await dialog.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const searchInput = dialog.getByPlaceholder("移动到..."); await searchInput.fill(fixture.childTitle, { timeout: UI_TIMEOUT_MS }); const searchStart = Date.now(); await page.waitForTimeout(1000); const nextErrorOverlay = page.locator('[data-nextjs-dialog-content="true"]'); assert( (await nextErrorOverlay.count()) === 0, "picker 搜索触发了前端运行时错误", ); const searchButtons = dialog.getByRole("button"); const emptyState = dialog.getByText("没有匹配结果"); const hasSearchOutcome = (await searchButtons.count()) > 0 || (await emptyState.count()) > 0; assert(hasSearchOutcome, "picker 搜索后既没有结果也没有空态提示"); const pickerSearchMs = Date.now() - searchStart; await page.keyboard.press("Escape"); await dialog.waitFor({ state: "hidden", timeout: UI_TIMEOUT_MS }); return { pickerVisibleMs, pickerSearchMs, }; } 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 fixture = null; try { const viewer = await ensureAuthenticated(page, context.request); fixture = await prepareTempTreeFixture(context.request); const pageTree = await runPageTreeChecks(page, fixture); const filetree = await runFileTreeChecks(page, fixture); const picker = await runPickerChecks(page, fixture); console.log( JSON.stringify( { ok: true, baseUrl: BASE_URL, viewerUserId: viewer.userId, workspaceId: fixture.workspaceId, parentId: fixture.parentId, childId: fixture.childId, pageTree, filetree, picker, }, 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); });