diff --git a/apps/waterfall-wiki/tests/guidance-integration.ts b/apps/waterfall-wiki/tests/guidance-integration.ts index 71f422a..395210c 100644 --- a/apps/waterfall-wiki/tests/guidance-integration.ts +++ b/apps/waterfall-wiki/tests/guidance-integration.ts @@ -1,4 +1,5 @@ import assert from 'node:assert/strict'; +import {verifyGuidanceWorkspaceUI} from './guidance-workspace-ui'; import {chromium} from 'playwright-core'; import {randomUUID} from 'node:crypto'; import {newToken,tokenDigest,ACCOUNT_COOKIE} from '../src/lib/account-crypto'; @@ -70,6 +71,9 @@ export async function verifyGuidancePublishing(base:string){ assert.equal(await page.locator('h1').textContent(),httpDoc.title); await page.goto(base+'/write',{waitUntil:'domcontentloaded'});assert.equal(await page.locator('h1').textContent(),'发布独立文档'); }catch(error){console.error('Document navigation diagnostics',JSON.stringify({url:page.url(),headings:await page.locator('h1').allTextContents(),body:(await page.locator('body').innerText()).slice(0,2200),inflight:[...inflight].map(url=>new URL(url).pathname),events:navigationDiagnostics}));throw error;} + await page.close(); + await verifyGuidanceWorkspaceUI(base,browser); + console.log('Workspace desktop/mobile, filters, reading, account and publishing assertions passed in a fresh browser context'); }finally{await browser.close();} assert.equal((await request('/'+httpDoc.id,'DELETE',undefined,{cookie:''})).status,401); assert.equal((await request('/'+httpDoc.id,'DELETE')).status,200); diff --git a/apps/waterfall-wiki/tests/guidance-workspace-ui.ts b/apps/waterfall-wiki/tests/guidance-workspace-ui.ts new file mode 100644 index 0000000..a1b8f6e --- /dev/null +++ b/apps/waterfall-wiki/tests/guidance-workspace-ui.ts @@ -0,0 +1,325 @@ +/** UI fixtures run in a fresh context; real storage and authentication have separate coverage. */ +import assert from "node:assert/strict"; +import { mkdir } from "node:fs/promises"; +import type { Browser } from "playwright-core"; +import { documentInput, parseDocumentXml } from "../src/lib/guidance-input"; +import { guidanceXml, type Guidance } from "../src/content/guides/catalog"; +const owner = "11111111-1111-4111-8111-111111111111"; +const authored: Guidance = { + id: "user-" + owner + "-review", + revision: 1, + kind: "guide", + title: "准备一次清晰的代码审查", + summary: "用明确的问题和证据,完成一次有用的代码审查。", + applies: "提交合并请求前", + body: "先理解改动目标,再核对实现与验证。", + steps: [], + checks: [], + links: [], + tags: [], +}; +export async function verifyGuidanceWorkspaceUI(base: string, browser: Browser) { + const context = await browser.newContext({ + viewport: { width: 1440, height: 1000 }, + }); + let signedIn = false, + previewError = false, + requestedAuthor: string | null = null, + published: Guidance | null = null; + const errors: string[] = []; + try { + await context.route("**/api/account/session", (route) => + route.fulfill({ + json: { + user: signedIn + ? { + id: owner, + email: "editor@example.com", + verified_at: "2026-10-29T00:00:00Z", + } + : null, + }, + }), + ); + await context.route( + (url) => url.pathname === "/api/documents", + async (route) => { + if (route.request().method() === "GET") { + requestedAuthor = new URL(route.request().url()).searchParams.get( + "author", + ); + return route.fulfill({ + json: { documents: [{ ...authored, author: owner }] }, + }); + } + published = documentInput.parse(route.request().postDataJSON()); + return route.fulfill({ status: 201, json: { document: published } }); + }, + ); + await context.route("**/api/documents/preview", (route) => { + if (previewError) + return route.fulfill({ + status: 400, + json: { error: "输入不符合协议", fields: ["applies"] }, + }); + let document: Guidance; + try { + document = + route.request().headers()["content-type"] === "application/xml" + ? parseDocumentXml(route.request().postData() || "") + : documentInput.parse(route.request().postDataJSON()); + } catch { + return route.fulfill({ + status: 400, + json: { error: "输入不符合协议" }, + }); + } + return route.fulfill({ + json: { + document, + xml: guidanceXml(document), + links: document.links.map((link) => ({ ...link, resolved: true })), + }, + }); + }); + await context.route("**/api/documents/" + authored.id, (route) => + route.fulfill({ json: { document: authored } }), + ); + const page = await context.newPage(); + page.setDefaultTimeout(10000); + page.on("pageerror", (error) => errors.push(error.message)); + page.on("crash", () => errors.push("Browser renderer crashed")); + 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) { + const probeStarted=Date.now(); + const server=await fetch(url,{signal:AbortSignal.timeout(3000)}).then(async response=>{await response.body?.cancel();return {status:response.status,elapsedMs:Date.now()-probeStarted}}).catch(error=>({error:error instanceof Error?error.name:"unknown",elapsedMs:Date.now()-probeStarted})); + console.error("Workspace UI navigation diagnostics",JSON.stringify({url:page.url(),browserConnected:browser.isConnected(),server,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(); + assert.equal(await page.title(), "社区文档 · 织页"); + const search = page.getByRole("textbox", { name: "搜索文档" }); + await search.fill("不存在的文档"); + await search.press("Enter"); + await page + .getByRole("heading", { name: "没有匹配文档", exact: true }) + .waitFor(); + 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 }) + .click(); + await page + .getByRole("heading", { name: authored.title, exact: true }) + .waitFor(); + await page.getByRole("tab", { name: /事实依据/ }).focus(); + await page.keyboard.press("Home"); + await page.getByRole("tab", { name: /全部文档/ }).waitFor(); + if (process.env.WORKSPACE_SCREENSHOT_DIR) { + await mkdir(process.env.WORKSPACE_SCREENSHOT_DIR, { recursive: true }); + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/directory.png", + fullPage: true, + }); + } + 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(); + await page.getByText("当前阅读", { exact: true }).waitFor(); + await page.getByRole("tab", { name: "文档概览" }).click(); + if (process.env.WORKSPACE_SCREENSHOT_DIR) + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/reader.png", + fullPage: true, + }); + await navigate(base + "/account"); + await page.getByRole("button", { name: "创建账户", exact: true }).click(); + await page.getByRole("heading", { name: "创建你的账户" }).waitFor(); + await page + .getByLabel("密码", { exact: true }) + .fill("a-long-example-password"); + await page.getByRole("button", { name: "显示密码" }).click(); + assert.equal( + await page.getByLabel("密码", { exact: true }).getAttribute("type"), + "text", + ); + await page.getByRole("button", { name: "隐藏密码" }).click(); + await page.getByLabel("密码", { exact: true }).fill(""); + if (process.env.WORKSPACE_SCREENSHOT_DIR) + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/account.png", + fullPage: true, + }); + await navigate(base + "/write"); + await page.getByRole("heading", { name: "登录后开始编写" }).waitFor(); + signedIn = true; + await navigate(page.url(), true); + await page.getByLabel("文档短名", { exact: true }).fill("workspace-test"); + await page.getByLabel("标题", { exact: false }).fill("界面回归验证文档"); + await page + .getByLabel("一句话摘要", { exact: false }) + .fill("可独立使用的指导"); + await page + .getByLabel("适用条件", { exact: false }) + .fill("检查界面流程时"); + await page + .getByLabel("正文", { exact: false }) + .fill("先判断适用条件,再阅读正文。"); + await page + .getByLabel("建议步骤", { exact: true }) + .fill("第一步\n\n第二步"); + previewError = true; + await page + .getByRole("button", { name: "校验并预览", exact: true }) + .click(); + await page.getByRole("alert").filter({ hasText: "适用条件" }).waitFor(); + previewError = false; + await page + .getByRole("button", { name: "校验并预览", exact: true }) + .click(); + await page.getByRole("dialog").waitFor({ state: "visible" }); + await page.getByRole("button", { name: "返回编辑" }).click(); + assert.equal( + await page.getByLabel("正文", { exact: false }).inputValue(), + "先判断适用条件,再阅读正文。", + ); + await page.getByRole("button", { name: "添加条件链接" }).click(); + await page.getByRole("button", { name: "移除关联文档 1" }).click(); + if (process.env.WORKSPACE_SCREENSHOT_DIR) { + await page.evaluate(() => window.scrollTo(0, 0)); + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/editor.png", + fullPage: true, + }); + } + await page.getByRole("button", { name: "发布文档", exact: true }).click(); + await page + .getByRole("status") + .filter({ hasText: "已公开发布" }) + .waitFor(); + assert.ok(published); + assert.deepEqual((published as Guidance).steps, ["第一步", "第二步"]); + assert.equal( + (published as Guidance).id, + "user-" + owner + "-workspace-test", + ); + await navigate(base + "/documents?scope=mine"); + await page + .getByRole("heading", { name: authored.title, exact: true }) + .waitFor(); + 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, + ); + assert.equal( + await page.getByLabel("标题", { exact: false }).inputValue(), + authored.title, + ); + await page.getByRole("button", { name: "协议 XML", exact: true }).click(); + const xmlInput = page.getByLabel("XML 输入"); + assert.match(await xmlInput.inputValue(), /revision="2"/); + await xmlInput.fill( + (await xmlInput.inputValue()).replace( + authored.title, + "通过 XML 更新的文档", + ), + ); + await page + .getByRole("button", { name: "可视化编辑", exact: true }) + .click(); + assert.equal( + await page.getByLabel("标题", { exact: false }).inputValue(), + "通过 XML 更新的文档", + ); + await page.setViewportSize({ width: 390, height: 844 }); + await navigate(base + "/documents"); + assert.equal( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + true, + ); + await page.getByRole("button", { name: "打开导航" }).click(); + await page + .getByRole("dialog", { name: "工作区导航" }) + .waitFor({ state: "visible" }); + await page.keyboard.press("Escape"); + assert.equal( + await page.getByRole("dialog", { name: "工作区导航" }).isVisible(), + false, + ); + if (process.env.WORKSPACE_SCREENSHOT_DIR) + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-directory.png", + fullPage: true, + }); + await navigate(base + "/guide/review-changes?revision=1"); + assert.equal( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + true, + ); + if (process.env.WORKSPACE_SCREENSHOT_DIR) + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-reader.png", + fullPage: true, + }); + await navigate(base + "/account"); + assert.equal( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + true, + ); + await navigate(base + "/write"); + await page.getByLabel("文档短名", { exact: true }).waitFor(); + await page.getByRole("button", { name: "协议 XML", exact: true }).click(); + await page.getByLabel("XML 输入").waitFor(); + assert.equal( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + true, + ); + if (process.env.WORKSPACE_SCREENSHOT_DIR) + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-editor.png", + fullPage: true, + }); + await navigate(base + "/protocol"); + assert.equal( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + true, + ); + if (process.env.WORKSPACE_SCREENSHOT_DIR) + await page.screenshot({ + path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-protocol.png", + fullPage: true, + }); + assert.deepEqual(errors, []); + } finally { + await context.close(); + } +} diff --git a/apps/waterfall-wiki/tests/guidance-workspace.test.ts b/apps/waterfall-wiki/tests/guidance-workspace.test.ts index d0d9cc6..474316f 100644 --- a/apps/waterfall-wiki/tests/guidance-workspace.test.ts +++ b/apps/waterfall-wiki/tests/guidance-workspace.test.ts @@ -1,336 +1,13 @@ -/** UI contract checks use isolated API fixtures; real storage/auth remain covered by guidance-integration.ts. */ -import assert from "node:assert/strict"; import { test } from "node:test"; -import { mkdir } from "node:fs/promises"; import { chromium } from "playwright-core"; -import { documentInput, parseDocumentXml } from "../src/lib/guidance-input"; -import { guidanceXml, type Guidance } from "../src/content/guides/catalog"; +import { verifyGuidanceWorkspaceUI } from "./guidance-workspace-ui"; const base = process.env.WORKSPACE_TEST_URL; -const owner = "11111111-1111-4111-8111-111111111111"; -const authored: Guidance = { - id: "user-" + owner + "-review", - revision: 1, - kind: "guide", - title: "准备一次清晰的代码审查", - summary: "用明确的问题和证据,完成一次有用的代码审查。", - applies: "提交合并请求前", - body: "先理解改动目标,再核对实现与验证。", - steps: [], - checks: [], - links: [], - tags: [], -}; -test( - "workspace filters, reading tabs, account states and editor work on desktop and mobile", - { skip: !base }, - async () => { - const browser = await chromium.launch({ - executablePath: - process.env.CHROMIUM_PATH || - "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", - headless: true, - args: ["--no-sandbox"], - }); - const context = await browser.newContext({ - viewport: { width: 1440, height: 1000 }, - }); - let signedIn = false, - previewError = false, - requestedAuthor: string | null = null, - published: Guidance | null = null; - const errors: string[] = []; - try { - await context.route("**/api/account/session", (route) => - route.fulfill({ - json: { - user: signedIn - ? { - id: owner, - email: "editor@example.com", - verified_at: "2026-10-29T00:00:00Z", - } - : null, - }, - }), - ); - await context.route( - (url) => url.pathname === "/api/documents", - async (route) => { - if (route.request().method() === "GET") { - requestedAuthor = new URL(route.request().url()).searchParams.get( - "author", - ); - return route.fulfill({ - json: { documents: [{ ...authored, author: owner }] }, - }); - } - published = documentInput.parse(route.request().postDataJSON()); - return route.fulfill({ status: 201, json: { document: published } }); - }, - ); - await context.route("**/api/documents/preview", (route) => { - if (previewError) - return route.fulfill({ - status: 400, - json: { error: "输入不符合协议", fields: ["applies"] }, - }); - let document: Guidance; - try { - document = - route.request().headers()["content-type"] === "application/xml" - ? parseDocumentXml(route.request().postData() || "") - : documentInput.parse(route.request().postDataJSON()); - } catch { - return route.fulfill({ - status: 400, - json: { error: "输入不符合协议" }, - }); - } - return route.fulfill({ - json: { - document, - xml: guidanceXml(document), - links: document.links.map((link) => ({ ...link, resolved: true })), - }, - }); - }); - await context.route("**/api/documents/" + authored.id, (route) => - route.fulfill({ json: { document: authored } }), - ); - const page = await context.newPage(); - page.setDefaultTimeout(10000); - page.on("pageerror", (error) => errors.push(error.message)); - 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(); - assert.equal(await page.title(), "社区文档 · 织页"); - const search = page.getByRole("textbox", { name: "搜索文档" }); - await search.fill("不存在的文档"); - await search.press("Enter"); - await page - .getByRole("heading", { name: "没有匹配文档", exact: true }) - .waitFor(); - 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 }) - .click(); - await page - .getByRole("heading", { name: authored.title, exact: true }) - .waitFor(); - await page.getByRole("tab", { name: /事实依据/ }).focus(); - await page.keyboard.press("Home"); - await page.getByRole("tab", { name: /全部文档/ }).waitFor(); - if (process.env.WORKSPACE_SCREENSHOT_DIR) { - await mkdir(process.env.WORKSPACE_SCREENSHOT_DIR, { recursive: true }); - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/directory.png", - fullPage: true, - }); - } - 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(); - await page.getByText("当前阅读", { exact: true }).waitFor(); - await page.getByRole("tab", { name: "文档概览" }).click(); - if (process.env.WORKSPACE_SCREENSHOT_DIR) - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/reader.png", - fullPage: true, - }); - await navigate(base + "/account"); - await page.getByRole("button", { name: "创建账户", exact: true }).click(); - await page.getByRole("heading", { name: "创建你的账户" }).waitFor(); - await page - .getByLabel("密码", { exact: true }) - .fill("a-long-example-password"); - await page.getByRole("button", { name: "显示密码" }).click(); - assert.equal( - await page.getByLabel("密码", { exact: true }).getAttribute("type"), - "text", - ); - await page.getByRole("button", { name: "隐藏密码" }).click(); - await page.getByLabel("密码", { exact: true }).fill(""); - if (process.env.WORKSPACE_SCREENSHOT_DIR) - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/account.png", - fullPage: true, - }); - await navigate(base + "/write"); - await page.getByRole("heading", { name: "登录后开始编写" }).waitFor(); - signedIn = true; - await navigate(page.url(), true); - await page.getByLabel("文档短名", { exact: true }).fill("workspace-test"); - await page.getByLabel("标题", { exact: false }).fill("界面回归验证文档"); - await page - .getByLabel("一句话摘要", { exact: false }) - .fill("可独立使用的指导"); - await page - .getByLabel("适用条件", { exact: false }) - .fill("检查界面流程时"); - await page - .getByLabel("正文", { exact: false }) - .fill("先判断适用条件,再阅读正文。"); - await page - .getByLabel("建议步骤", { exact: true }) - .fill("第一步\n\n第二步"); - previewError = true; - await page - .getByRole("button", { name: "校验并预览", exact: true }) - .click(); - await page.getByRole("alert").filter({ hasText: "适用条件" }).waitFor(); - previewError = false; - await page - .getByRole("button", { name: "校验并预览", exact: true }) - .click(); - await page.getByRole("dialog").waitFor({ state: "visible" }); - await page.getByRole("button", { name: "返回编辑" }).click(); - assert.equal( - await page.getByLabel("正文", { exact: false }).inputValue(), - "先判断适用条件,再阅读正文。", - ); - await page.getByRole("button", { name: "添加条件链接" }).click(); - await page.getByRole("button", { name: "移除关联文档 1" }).click(); - if (process.env.WORKSPACE_SCREENSHOT_DIR) { - await page.evaluate(() => window.scrollTo(0, 0)); - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/editor.png", - fullPage: true, - }); - } - await page.getByRole("button", { name: "发布文档", exact: true }).click(); - await page - .getByRole("status") - .filter({ hasText: "已公开发布" }) - .waitFor(); - assert.ok(published); - assert.deepEqual((published as Guidance).steps, ["第一步", "第二步"]); - assert.equal( - (published as Guidance).id, - "user-" + owner + "-workspace-test", - ); - await navigate(base + "/documents?scope=mine"); - await page - .getByRole("heading", { name: authored.title, exact: true }) - .waitFor(); - 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, - ); - assert.equal( - await page.getByLabel("标题", { exact: false }).inputValue(), - authored.title, - ); - await page.getByRole("button", { name: "协议 XML", exact: true }).click(); - const xmlInput = page.getByLabel("XML 输入"); - assert.match(await xmlInput.inputValue(), /revision="2"/); - await xmlInput.fill( - (await xmlInput.inputValue()).replace( - authored.title, - "通过 XML 更新的文档", - ), - ); - await page - .getByRole("button", { name: "可视化编辑", exact: true }) - .click(); - assert.equal( - await page.getByLabel("标题", { exact: false }).inputValue(), - "通过 XML 更新的文档", - ); - await page.setViewportSize({ width: 390, height: 844 }); - await navigate(base + "/documents"); - assert.equal( - await page.evaluate( - () => document.documentElement.scrollWidth <= window.innerWidth, - ), - true, - ); - await page.getByRole("button", { name: "打开导航" }).click(); - await page - .getByRole("dialog", { name: "工作区导航" }) - .waitFor({ state: "visible" }); - await page.keyboard.press("Escape"); - assert.equal( - await page.getByRole("dialog", { name: "工作区导航" }).isVisible(), - false, - ); - if (process.env.WORKSPACE_SCREENSHOT_DIR) - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-directory.png", - fullPage: true, - }); - await navigate(base + "/guide/review-changes?revision=1"); - assert.equal( - await page.evaluate( - () => document.documentElement.scrollWidth <= window.innerWidth, - ), - true, - ); - if (process.env.WORKSPACE_SCREENSHOT_DIR) - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-reader.png", - fullPage: true, - }); - await navigate(base + "/account"); - assert.equal( - await page.evaluate( - () => document.documentElement.scrollWidth <= window.innerWidth, - ), - true, - ); - await navigate(base + "/write"); - await page.getByLabel("文档短名", { exact: true }).waitFor(); - await page.getByRole("button", { name: "协议 XML", exact: true }).click(); - await page.getByLabel("XML 输入").waitFor(); - assert.equal( - await page.evaluate( - () => document.documentElement.scrollWidth <= window.innerWidth, - ), - true, - ); - if (process.env.WORKSPACE_SCREENSHOT_DIR) - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-editor.png", - fullPage: true, - }); - await navigate(base + "/protocol"); - assert.equal( - await page.evaluate( - () => document.documentElement.scrollWidth <= window.innerWidth, - ), - true, - ); - if (process.env.WORKSPACE_SCREENSHOT_DIR) - await page.screenshot({ - path: process.env.WORKSPACE_SCREENSHOT_DIR + "/mobile-protocol.png", - fullPage: true, - }); - assert.deepEqual(errors, []); - } finally { - await context.close(); - await browser.close(); - } - }, -); +test("workspace filters, reading tabs, account states and editor work on desktop and mobile", {skip: !base}, async () => { + const browser = await chromium.launch({ + executablePath: process.env.CHROMIUM_PATH || "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + headless: true, + args: ["--no-sandbox", "--disable-dev-shm-usage"], + }); + try { await verifyGuidanceWorkspaceUI(base!, browser); } + finally { await browser.close(); } +}); diff --git a/apps/waterfall-wiki/tests/integration.test.ts b/apps/waterfall-wiki/tests/integration.test.ts index de70ad9..ca8c41a 100644 --- a/apps/waterfall-wiki/tests/integration.test.ts +++ b/apps/waterfall-wiki/tests/integration.test.ts @@ -37,10 +37,6 @@ test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processi await verifyCanvasWorkspace(base); if(process.env.VERIFY_CANVAS_UI==='1')await verifyCanvasAgentUI(base); 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: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'}); const sameTag=await request('/api/tags',{name:'#线条'});assert.equal(sameTag.id,root.id,'Hashtag creation reuses the globally unique tag');