- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。 - 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。 - 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。 - 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
105 lines
4.8 KiB
TypeScript
105 lines
4.8 KiB
TypeScript
/** 真实浏览器和隔离库:任务材料清单、来源证据与漂移后的失效说明可见。 */
|
||
import { readFileSync } from "node:fs";
|
||
import { expect, test } from "@playwright/test";
|
||
|
||
test.use({ trace: "off" });
|
||
|
||
// @muse-case {"case_id":"NC-browser-context-workbench","environment":"真实浏览器","given":"真实隔离库上的工作台与一处真实漂移","when":"真实浏览器登录并打开任务详情","then":["材料清单/来源证据/失效说明可见(DOM 断言 24 条,页面与控制台错误 0);截图视觉判定四项清晰可读"],"contract":"docs/系统架构/新版设计/接口契约/任务工具与事件.md"}
|
||
test("NC-browser-context-workbench:任务材料清单、来源证据与失效说明", async ({
|
||
page,
|
||
}, info) => {
|
||
if (!process.env.MUSE_WORKBENCH_URL || !process.env.MUSE_AUTHOR_PASSWORD_FILE)
|
||
throw new Error("需要明确的隔离后端和作者口令文件。");
|
||
const taskId = process.env.MUSE_CONTEXT_TASK_ID!;
|
||
const chapterId = process.env.MUSE_CONTEXT_CHAPTER_ID ?? "ch-1";
|
||
if (!process.env.MUSE_CONTEXT_TASK_ID)
|
||
throw new Error("需要带冻结快照的任务编号(MUSE_CONTEXT_TASK_ID)。");
|
||
const consoleErrors: string[] = [];
|
||
page.on("pageerror", (error) => consoleErrors.push(String(error)));
|
||
|
||
await page.goto("/");
|
||
await page
|
||
.getByLabel("访问口令")
|
||
.fill(readFileSync(process.env.MUSE_AUTHOR_PASSWORD_FILE, "utf8").trim());
|
||
await page.getByRole("button", { name: "进入工作台" }).click();
|
||
await page.getByRole("link", { name: /任务记录/ }).waitFor();
|
||
// 连接前探测会话的 401 与 favicon 404 属正常噪声;只记录进入工作台后的真实错误。
|
||
page.on("console", (message) => {
|
||
if (message.type() === "error" && !/favicon/.test(message.text()))
|
||
consoleErrors.push(message.text());
|
||
});
|
||
|
||
async function 打开任务() {
|
||
await page.goto("/tasks");
|
||
await page.getByLabel("任务编号").fill(taskId);
|
||
await page.getByRole("button", { name: "查看任务" }).click();
|
||
await expect(
|
||
page.locator('section[aria-label="任务材料"]'),
|
||
).toBeVisible();
|
||
}
|
||
|
||
await 打开任务();
|
||
const 材料 = page.locator('section[aria-label="任务材料"]');
|
||
await expect(材料.getByText("材料快照 01")).toBeVisible();
|
||
await expect(材料.getByText("仍可用于回读")).toBeVisible();
|
||
await expect(材料.getByText("历史:ch-1")).toBeVisible();
|
||
await expect(材料.getByText("已按冻结版本装配")).toBeVisible();
|
||
await expect(材料.getByText("参考:超长素材")).toBeVisible();
|
||
await expect(
|
||
材料.getByText("省略原因:超出材料预算,整项省略"),
|
||
).toBeVisible();
|
||
await expect(材料.getByText(/材料 \d+ 字节/)).toBeVisible();
|
||
await expect(材料.getByText(/文本哈希 [0-9a-f]{64}/)).toBeVisible();
|
||
await expect(材料.getByText("来源证据")).toBeVisible();
|
||
await expect(材料.getByText("1 处来源")).toBeVisible();
|
||
await expect(材料.getByText("版本 1", { exact: true })).toBeVisible();
|
||
await expect(
|
||
材料.getByText(/visible-text-codepoints\/v1:production:generation/),
|
||
).toBeVisible();
|
||
await expect(材料.getByText("仍有效")).toBeVisible();
|
||
|
||
// 造一处真实漂移:以当前正文版本为基线保存新版本,冻结快照的来源随即失效。
|
||
const 当前 = await (
|
||
await page.request.get(`/api/v1/chapters/${chapterId}/documents`)
|
||
).json();
|
||
const save = await page.request.post(`/api/v1/chapters/${chapterId}/documents`, {
|
||
headers: { Origin: process.env.MUSE_WORKBENCH_URL },
|
||
data: {
|
||
command_id: `browser-drift-revision-${当前.revision}`,
|
||
chapter_id: chapterId,
|
||
expected_revision: 当前.revision,
|
||
document: {
|
||
format_version: 1,
|
||
paragraphs: [
|
||
{
|
||
paragraph_id: "p-2",
|
||
content: [{ type: "text", text: "第二天清晨,雨停了。", marks: [] }],
|
||
},
|
||
],
|
||
},
|
||
},
|
||
});
|
||
expect(save.ok()).toBeTruthy();
|
||
await 打开任务();
|
||
await expect(材料.getByText("来源已漂移,需重新授权")).toBeVisible();
|
||
await expect(材料.getByText(/文稿 ch-1 已漂移/)).toBeVisible();
|
||
await expect(
|
||
材料.getByText(/文稿 ch-1 已从版本 \d+ 前进到 \d+/),
|
||
).toBeVisible();
|
||
await expect(材料.getByText("已漂移", { exact: true })).toBeVisible();
|
||
// 失效不改写历史装配:清单与省略原因仍按冻结时原样显示。
|
||
await expect(材料.getByText("历史:ch-1")).toBeVisible();
|
||
await expect(
|
||
材料.getByText("省略原因:超出材料预算,整项省略"),
|
||
).toBeVisible();
|
||
await expect(材料.getByText("版本 1", { exact: true })).toBeVisible();
|
||
|
||
await page.screenshot({
|
||
path: info.outputPath("任务材料与失效说明.png"),
|
||
fullPage: true,
|
||
});
|
||
expect(consoleErrors, `浏览器控制台错误:${consoleErrors.join(" | ")}`).toEqual(
|
||
[],
|
||
);
|
||
});
|