- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。 - 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。 - 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。 - 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
122 lines
5.9 KiB
TypeScript
122 lines
5.9 KiB
TypeScript
import { readFileSync, writeFileSync } from "node:fs";
|
||
import { expect, test } from "@playwright/test";
|
||
|
||
// @muse-case {"case_id":"NC-browser-delivery-serial","environment":"真实浏览器与隔离PG","given":"本作者完整目录、确切正文版本与独立隔离数据","when":"通过定稿、S02导出、跨章候选或连载反馈实际入口执行并检查失败恢复","then":["全书定稿实际导出与版本反馈"],"contract":"docs/系统架构/新版设计/模块设计/B08-交付连载.md"}
|
||
test("NC-browser-delivery-serial:全书定稿实际导出与版本反馈", async ({
|
||
page,
|
||
}, info) => {
|
||
const password = process.env.MUSE_AUTHOR_PASSWORD_FILE;
|
||
const work = process.env.MUSE_FEEDBACK_ID;
|
||
if (!password || !work) throw new Error("需要隔离作品及作者配置");
|
||
const errors: string[] = [];
|
||
page.on("pageerror", (e) => errors.push(e.message));
|
||
await page.goto("/");
|
||
await page.getByLabel("访问口令").fill(readFileSync(password, "utf8").trim());
|
||
await page.getByRole("button", { name: "进入工作台" }).click();
|
||
await page.goto(`/works/${work}/delivery`);
|
||
await expect(
|
||
page.getByRole("heading", { name: "全书整理", exact: true }),
|
||
).toBeVisible();
|
||
await expect(
|
||
page.getByRole("heading", { name: "第二章 雨停" }),
|
||
).toBeVisible();
|
||
await page.getByLabel("交付书名").fill("浏览器定稿");
|
||
await page.getByLabel("原称 1", { exact: true }).fill("原句");
|
||
await page.getByLabel("建议称谓 1", { exact: true }).fill("新句");
|
||
await page.getByRole("button", { name: "检查文字疑点" }).click();
|
||
await page
|
||
.getByRole("region", { name: "跨章校对候选" })
|
||
.getByRole("checkbox")
|
||
.check();
|
||
await page.getByRole("button", { name: "生成所选跨章候选" }).click();
|
||
await expect(page.getByText("候选已保存,当前正文保持原样。")).toBeVisible();
|
||
await page.getByLabel("作者署名").fill("合成作者");
|
||
await page.getByLabel("作品简介").fill("真实隔离入口,不含私人资料。");
|
||
await page.getByRole("button", { name: "预览定稿材料" }).click();
|
||
await expect(
|
||
page.getByRole("region", { name: "待确认定稿预览" }),
|
||
).toBeVisible();
|
||
const frozen = page.waitForResponse(
|
||
(r) =>
|
||
r.url().endsWith("/api/v1/deliveries") && r.request().method() === "POST",
|
||
);
|
||
await page.getByRole("button", { name: "确认冻结这份定稿" }).click();
|
||
const frozenResponse = await frozen;
|
||
expect(frozenResponse.status()).toBe(200);
|
||
const receipt = await frozenResponse.json();
|
||
const did: string = receipt.results[0].delivery_id;
|
||
writeFileSync(info.outputPath("定稿回执.json"), JSON.stringify(receipt));
|
||
await page.getByRole("link", { name: "查看固定版本并导出" }).click();
|
||
await page.getByRole("button", { name: "启动所选格式导出" }).click();
|
||
await expect(
|
||
page.getByText("所选格式导出已完成,可以下载文件。"),
|
||
).toBeVisible();
|
||
const downloaded = page.waitForEvent("download");
|
||
await page
|
||
.getByRole("button", { name: "下载 TXT 纯文本", exact: true })
|
||
.click();
|
||
const artifact = await downloaded;
|
||
const path = await artifact.path();
|
||
expect(path).toBeTruthy();
|
||
expect(readFileSync(path!, "utf8")).toContain("浏览器定稿\n\n合成作者");
|
||
expect(readFileSync(path!, "utf8")).toContain("原句「保留」");
|
||
await page.screenshot({
|
||
path: info.outputPath("定稿导出.png"),
|
||
fullPage: true,
|
||
});
|
||
|
||
await page.goto(`/works/${work}/serial`);
|
||
await page.getByLabel("用于计划和发布的定稿").selectOption(did);
|
||
await page.getByLabel("安排《改稿》", { exact: true }).check();
|
||
await page
|
||
.getByLabel("《改稿》计划时间", { exact: true })
|
||
.fill("2026-09-01T12:00");
|
||
await page.getByLabel("计划时区(UTC 偏移)").fill("+08:00");
|
||
await page.getByRole("button", { name: "保存连载计划", exact: true }).click();
|
||
await expect(
|
||
page.getByText("连载计划已保存,实际发布需另外登记。"),
|
||
).toBeVisible();
|
||
await page.getByLabel("发布《改稿》", { exact: false }).check();
|
||
await page.getByLabel("发布平台", { exact: true }).fill("合成平台");
|
||
await page
|
||
.getByLabel("实际发布时间", { exact: true })
|
||
.fill("2026-09-01T12:10");
|
||
await page.getByLabel("发布时区(UTC 偏移)").fill("+08:00");
|
||
await page
|
||
.getByLabel("发布证据地址")
|
||
.fill("https://example.invalid/browser-release");
|
||
const releaseWait = page.waitForResponse((r) =>
|
||
r.url().endsWith("/api/v1/releases"),
|
||
);
|
||
await page.getByRole("button", { name: "确认登记实际发布" }).click();
|
||
const release = await releaseWait;
|
||
expect(release.status()).toBe(200);
|
||
const rid: string = (await release.json()).results[0].id;
|
||
await page.screenshot({
|
||
path: info.outputPath("连载发布.png"),
|
||
fullPage: true,
|
||
});
|
||
|
||
await page.goto(`/works/${work}/reader-feedback`);
|
||
await page.getByLabel("反馈来源").fill("合成读者留言");
|
||
await page.getByLabel("反馈原话").fill("不知道所读的是哪个版本。");
|
||
await page.getByLabel("收到反馈的时间").fill("2026-09-01T12:11");
|
||
await page.getByLabel("反馈时区(UTC 偏移)").fill("+08:00");
|
||
await page.getByRole("button", { name: "保存读者反馈" }).click();
|
||
await expect(page.getByText("未关联发布版本", { exact: true })).toBeVisible();
|
||
await page.getByLabel("对应实际发布").selectOption(rid);
|
||
await page.getByLabel("对应章节(可选)").selectOption("chapter-candidate");
|
||
await page.getByLabel("反馈来源").fill("合成编辑来信");
|
||
await page.getByLabel("反馈原话").fill("原版末段可以更明确。");
|
||
await page.getByLabel("收到反馈的时间").fill("2026-09-01T12:12");
|
||
await page.getByRole("button", { name: "保存读者反馈" }).click();
|
||
await expect(
|
||
page.getByText("原版末段可以更明确。", { exact: true }),
|
||
).toBeVisible();
|
||
await page.screenshot({
|
||
path: info.outputPath("读者反馈.png"),
|
||
fullPage: true,
|
||
});
|
||
expect(errors).toEqual([]);
|
||
});
|