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, }); });