muse-agent-example/web/tests/旅程/分支缓冲与事件收尾.spec.ts
zizi ae02fd1732 前端:工作台页面、事件标签与旅程用例
- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。
- 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。
- 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。
- 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
2026-09-18 01:15:07 +08:00

98 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { readFileSync } from "node:fs";
import { expect, test } from "@playwright/test";
test.use({ trace: "off" });
// @muse-case {"case_id":"O08-browser-branch-buffer-lifecycle","then":"真实浏览器中同章两分支草稿刷新不串用,未知保存刷新重放原请求;终态任务没有持续事件请求"}
test("O08-browser-branch-buffer-lifecycle:分支缓冲与终态读取旅程", async ({
page,
}, info) => {
test.setTimeout(120_000);
const work = process.env.MUSE_WORK_ID,
chapter = process.env.MUSE_CHAPTER_ID,
taskId = process.env.MUSE_TERMINAL_TASK_ID;
const branchA = process.env.MUSE_BRANCH_A,
branchB = process.env.MUSE_BRANCH_B;
if (
!work ||
!chapter ||
!taskId ||
!branchA ||
!branchB ||
branchA === branchB
)
throw new Error(
"需要预置合成作品、同章两个独立分支与已completed/cancelled任务身份。",
);
await page.goto("/");
await page
.getByLabel("访问口令")
.fill(readFileSync(process.env.MUSE_AUTHOR_PASSWORD_FILE!, "utf8").trim());
await page.getByRole("button", { name: "进入工作台" }).click();
const route = (branch: string) =>
`/works/${encodeURIComponent(work)}/candidates/${encodeURIComponent(chapter)}?branch=${encodeURIComponent(branch)}`;
await page.goto(route(branchA));
await page.getByRole("button", { name: "另拟改稿" }).click();
const aText = `A分支合成稿-${Date.now()}`;
await page.getByRole("textbox", { name: "候选改稿" }).fill(aText);
await page.goto(route(branchB));
await page.getByRole("button", { name: "另拟改稿" }).click();
await expect(
page.getByRole("textbox", { name: "候选改稿" }),
).not.toContainText(aText);
const bText = `B分支合成稿-${Date.now()}`;
await page.getByRole("textbox", { name: "候选改稿" }).fill(bText);
await page.reload();
await page.getByRole("button", { name: "另拟改稿" }).click();
await expect(page.getByRole("textbox", { name: "候选改稿" })).toContainText(
bText,
);
await page.goto(route(branchA));
await page.getByRole("button", { name: "另拟改稿" }).click();
await expect(page.getByRole("textbox", { name: "候选改稿" })).toContainText(
aText,
);
const requests: unknown[] = [];
let lose = true;
await page.route(
`**/api/v1/chapters/${encodeURIComponent(chapter)}/writing-candidates`,
async (intercepted) => {
if (intercepted.request().method() !== "POST") {
await intercepted.continue();
return;
}
requests.push(intercepted.request().postDataJSON());
const response = await intercepted.fetch();
if (lose) {
lose = false;
await intercepted.abort("failed");
} else await intercepted.fulfill({ response });
},
);
await page.getByRole("button", { name: "保存候选版本", exact: true }).click();
await expect(
page.getByRole("button", { name: "重试候选保存" }),
).toBeVisible();
await page.reload();
await page.getByRole("button", { name: "另拟改稿" }).click();
await page.getByRole("button", { name: "重试候选保存" }).click();
await expect(page.getByText(/候选新版本已保存/)).toBeVisible();
expect(requests).toHaveLength(2);
expect(requests[1]).toEqual(requests[0]);
const streamRequests: string[] = [],
taskReads: string[] = [];
page.on("request", (request) => {
if (request.url().includes("/events/stream"))
streamRequests.push(request.url());
if (new URL(request.url()).pathname === `/api/v1/tasks/${taskId}`)
taskReads.push(request.url());
});
await page.goto(`/tasks?task=${encodeURIComponent(taskId)}`);
await expect(page.getByText("执行已收尾")).toBeVisible();
await page.waitForTimeout(60_000);
expect(streamRequests).toHaveLength(0);
expect(taskReads).toHaveLength(1);
await page.screenshot({
path: info.outputPath("终态执行与独立账目.png"),
fullPage: true,
});
});