diff --git a/apps/waterfall-wiki/tests/canvas-ui-integration.ts b/apps/waterfall-wiki/tests/canvas-ui-integration.ts index cc17580..afa0387 100644 --- a/apps/waterfall-wiki/tests/canvas-ui-integration.ts +++ b/apps/waterfall-wiki/tests/canvas-ui-integration.ts @@ -12,9 +12,10 @@ export async function verifyCanvasAgentUI(base:string){ const context=await browser.newContext({viewport:{width:1440,height:1000}}),errors:string[]=[]; try{ const page=await context.newPage();page.setDefaultTimeout(20000);page.on('pageerror',e=>errors.push(e.message)); - await page.goto(base+'/',{waitUntil:'networkidle'}); + await page.goto(base+'/',{waitUntil:'domcontentloaded'}); await page.getByRole('heading',{name:/想法不该停在对话里/,level:1}).waitFor(); await page.getByRole('link',{name:'开始创作',exact:true}).click(); + await page.getByText('登录你的账户',{exact:true}).first().waitFor({state:'attached'}); await page.getByLabel('邮箱地址',{exact:true}).fill(email); await page.getByLabel('密码',{exact:true}).fill(password); await page.locator('form').getByRole('button',{name:'登录',exact:true}).click(); @@ -79,7 +80,8 @@ export async function verifyCanvasAgentUI(base:string){ assert.equal((await fetch(base+root,{headers:{authorization:second.authorization}})).status,404); await page.setViewportSize({width:390,height:844}); const bounds=await panel.boundingBox();assert.ok(bounds&&bounds.x>=0&&bounds.x+bounds.width<=390,'Agent panel stays within the mobile viewport'); - await page.reload({waitUntil:'networkidle'}); + await page.reload({waitUntil:'domcontentloaded'}); + await page.getByRole('link',{name:email,exact:true}).waitFor({state:'attached'}); await page.getByRole('button',{name:'Agent',exact:true}).click(); const reopened=page.getByRole('region',{name:'Agent 连接',exact:true}); await reopened.getByRole('combobox',{name:'打开在线画布',exact:true}).selectOption(list.canvases[0].id); diff --git a/apps/waterfall-wiki/tests/guidance-workspace.test.ts b/apps/waterfall-wiki/tests/guidance-workspace.test.ts index 9122a83..d0d9cc6 100644 --- a/apps/waterfall-wiki/tests/guidance-workspace.test.ts +++ b/apps/waterfall-wiki/tests/guidance-workspace.test.ts @@ -100,7 +100,23 @@ test( const page = await context.newPage(); page.setDefaultTimeout(10000); page.on("pageerror", (error) => errors.push(error.message)); - await page.goto(base + "/documents", { waitUntil: "networkidle" }); + const pending = new Set(), network: string[] = []; + page.on("request", request => pending.add(request.url())); + page.on("requestfinished", request => pending.delete(request.url())); + page.on("requestfailed", request => {pending.delete(request.url());network.push(new URL(request.url()).pathname+": "+request.failure()?.errorText);}); + async function navigate(url: string, reload = false) { + const session = page.waitForResponse(response => new URL(response.url()).pathname === "/api/account/session" && response.status() === 200); + try { + const [navigation] = await Promise.all([reload ? page.reload({waitUntil:"domcontentloaded"}) : page.goto(url,{waitUntil:"domcontentloaded"}),session]); + assert.ok(navigation?.ok(), "Navigation returns a successful document"); + await page.getByText(signedIn ? "editor@example.com" : "登录你的账户",{exact:true}).first().waitFor({state:"attached"}); + } catch (error) { + console.error("Workspace UI navigation diagnostics",JSON.stringify({url:page.url(),pending:[...pending].map(value=>new URL(value).pathname),network:network.slice(-20),errors,body:(await page.locator("body").innerText().catch(()=>"unavailable")).slice(0,1600)})); + throw error; + } + } + + await navigate(base + "/documents"); await page .getByRole("heading", { name: authored.title, exact: true }) .waitFor(); @@ -111,7 +127,8 @@ test( await page .getByRole("heading", { name: "没有匹配文档", exact: true }) .waitFor(); - await page.reload({ waitUntil: "networkidle" }); + await navigate(page.url(), true); + await page.getByRole("heading",{name:"没有匹配文档",exact:true}).waitFor(); assert.equal(await search.inputValue(), "不存在的文档"); await page .getByRole("button", { name: "清除所有筛选", exact: true }) @@ -129,9 +146,7 @@ test( fullPage: true, }); } - await page.goto(base + "/guide/review-changes?revision=1", { - waitUntil: "networkidle", - }); + await navigate(base + "/guide/review-changes?revision=1"); await page.getByRole("tab", { name: "XML 源文档" }).click(); assert.match(await page.locator("pre").innerText(), /linked-guidance/); await page.getByRole("tab", { name: /版本记录/ }).click(); @@ -142,7 +157,7 @@ test( path: process.env.WORKSPACE_SCREENSHOT_DIR + "/reader.png", fullPage: true, }); - await page.goto(base + "/account", { waitUntil: "networkidle" }); + await navigate(base + "/account"); await page.getByRole("button", { name: "创建账户", exact: true }).click(); await page.getByRole("heading", { name: "创建你的账户" }).waitFor(); await page @@ -160,10 +175,10 @@ test( path: process.env.WORKSPACE_SCREENSHOT_DIR + "/account.png", fullPage: true, }); - await page.goto(base + "/write", { waitUntil: "networkidle" }); + await navigate(base + "/write"); await page.getByRole("heading", { name: "登录后开始编写" }).waitFor(); signedIn = true; - await page.reload({ waitUntil: "networkidle" }); + await navigate(page.url(), true); await page.getByLabel("文档短名", { exact: true }).fill("workspace-test"); await page.getByLabel("标题", { exact: false }).fill("界面回归验证文档"); await page @@ -213,16 +228,13 @@ test( (published as Guidance).id, "user-" + owner + "-workspace-test", ); - await page.goto(base + "/documents?scope=mine", { - waitUntil: "networkidle", - }); - assert.equal(requestedAuthor, owner); + await navigate(base + "/documents?scope=mine"); await page .getByRole("heading", { name: authored.title, exact: true }) .waitFor(); - await page.goto(base + "/write?id=" + authored.id, { - waitUntil: "networkidle", - }); + assert.equal(requestedAuthor, owner); + await navigate(base + "/write?id=" + authored.id); + await page.waitForFunction(title=>Array.from(document.querySelectorAll("input")).some(input=>input.value===title),authored.title); assert.equal( await page.getByLabel("文档短名", { exact: true }).isDisabled(), true, @@ -248,7 +260,7 @@ test( "通过 XML 更新的文档", ); await page.setViewportSize({ width: 390, height: 844 }); - await page.goto(base + "/documents", { waitUntil: "networkidle" }); + await navigate(base + "/documents"); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, @@ -269,9 +281,7 @@ test( path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-directory.png", fullPage: true, }); - await page.goto(base + "/guide/review-changes?revision=1", { - waitUntil: "networkidle", - }); + await navigate(base + "/guide/review-changes?revision=1"); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, @@ -283,14 +293,14 @@ test( path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-reader.png", fullPage: true, }); - await page.goto(base + "/account", { waitUntil: "networkidle" }); + await navigate(base + "/account"); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, ), true, ); - await page.goto(base + "/write", { waitUntil: "networkidle" }); + await navigate(base + "/write"); await page.getByLabel("文档短名", { exact: true }).waitFor(); await page.getByRole("button", { name: "协议 XML", exact: true }).click(); await page.getByLabel("XML 输入").waitFor(); @@ -305,7 +315,7 @@ test( path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-editor.png", fullPage: true, }); - await page.goto(base + "/protocol", { waitUntil: "networkidle" }); + await navigate(base + "/protocol"); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, diff --git a/apps/waterfall-wiki/tests/integration.test.ts b/apps/waterfall-wiki/tests/integration.test.ts index 06383fc..de70ad9 100644 --- a/apps/waterfall-wiki/tests/integration.test.ts +++ b/apps/waterfall-wiki/tests/integration.test.ts @@ -22,7 +22,7 @@ import {verifyFlatNoteMigration} from './flat-note-migration'; import {staticEntries} from '../src/lib/explore'; import {publishLearningSeries} from '../src/lib/publish-learning-series'; -test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processing', {timeout:300000}, async()=>{ +test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processing', {timeout:600000}, async()=>{ assert.equal(process.env.PROMPT_INTEGRATION_TEST,'1','Run only against an explicitly isolated test database'); const socket=createServer();socket.listen(0,'127.0.0.1');await once(socket,'listening');const address=socket.address();assert.ok(address&&typeof address!=='string');const port=address.port;await new Promise(r=>socket.close(()=>r())); const external=process.env.PROMPT_TEST_BASE_URL; @@ -39,7 +39,7 @@ test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processi await verifyGuidancePublishing(base); const workspaceEnv:NodeJS.ProcessEnv={...process.env,WORKSPACE_TEST_URL:base}; delete workspaceEnv.NODE_TEST_CONTEXT; - const workspaceUI=spawn(process.execPath,['--import','tsx','--test','tests/guidance-workspace.test.ts'],{env:workspaceEnv,timeout:90000,stdio:'inherit'}); + const workspaceUI=spawn(process.execPath,['--import','tsx','--test','tests/guidance-workspace.test.ts'],{env:workspaceEnv,timeout:180000,stdio:'inherit'}); const [workspaceExit]=await once(workspaceUI,'exit');assert.equal(workspaceExit,0,'SaaS workspace UI regression checks'); await verifyFlatNoteMigration(); const root=await request('/api/tags',{name:'线条',slug:'lines'});const child=await request('/api/tags',{name:'白色',slug:'white'});