oh-my-muse/muse-studio/e2e/source-attribution.spec.ts
lili ca0e04c9a9 test(studio-e2e): source-attribution 解耦到 pristine block3 根治测试间来源污染 flaky
source-attribution 只读 spec 期望 block1 来源=user_original,但全量串行时前序 accept-suggestion(写 ai_suggestion)/block-autosave/block-structure 按字母序先跑且都写 block1,block1 顶层 revision 来源在 user_original/ai_suggestion 漂移→偶发渲染"AI 生成"红;global-setup 每轮仅跑一次、无法撤销跑中途污染。修:spec 改读 pristine work3/block3(grep 确认无人导航 /workspace/3、无人写 block3)+global-setup §3 每轮删净 block3 归因并重建唯一 user_original rev1 行(幂等)。断言未放宽仍真断言 user_original。免疫验证:强灌 block1 顶层 ai_suggestion(旧 spec 必红条件)新 spec 仍绿;独立连跑 source-attribution 3 次全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:35:19 -07:00

45 lines
2.4 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test';
/**
* Block 来源归因查看端到端(真实后端,MSW off)。
*
* 覆盖 E 链路:进 work3 workspace → 右侧「来源」Tab → 真实 GET source-attribution →
* 展示来源(work3/block3 真后端含 user_original → 渲染「用户原创」)。纯读,无副作用,幂等。
*
* WHY 用 work3 而非 work1(反污染、根治偶发红):本读断言「block 当前 revision 的来源=用户原创」。若读 work1/block1,
* 该 block 被 accept-suggestion(写 ai_suggestion 来源)、block-autosave、block-structure 三个 spec 在前序消费(字母序均先
* 于本 spec),其顶层 revision 的 source_type 由「最后写 block1 的 spec」决定 → 非确定,顶层落 ai_suggestion 时本断言渲染
* 「AI 生成」偶发红。改读 work3/block3:无任何 spec 导航 /workspace/3 或写 block3(grep 全 spec 确认),globalSetup §3 每轮
* 把 block3 复位 rev1 + 重建唯一 user_original 来源,使本读恒确定真(断言未放宽,仍真断言「用户原创」)。
*/
const TOKEN = 'test1';
/** 读取来源归因的目标作品:work3/block3 是无任何 spec 写入的 pristine Block(见上文 WHY),globalSetup §3 每轮复位其来源。 */
const WORK_ID = 3;
async function seedToken(page: Page): Promise<void> {
await page.addInitScript((t) => {
window.localStorage.setItem('accessToken', t);
window.localStorage.setItem('tenantId', '1');
}, TOKEN);
}
test('用户查看章节正文来源归因(真实后端)', async ({ page }) => {
await seedToken(page);
await page.goto(`/workspace/${WORK_ID}`);
await expect(page.getByRole('heading', { name: /正在创作/ })).toBeVisible({ timeout: 15_000 });
// 等章节结构条出现(activeBlock 已加载,来源 Tab 才有目标 Block)。
await expect(page.getByText(/章节结构/)).toBeVisible({ timeout: 15_000 });
// 切到「来源」Tab,捕获真实 source-attribution 请求。
const attrResp = page.waitForResponse(
(r) => /\/blocks\/[^/]+\/source-attribution/.test(r.url()) && r.request().method() === 'GET'
);
await page.getByRole('button', { name: '来源' }).click();
expect((await attrResp).status()).toBe(200);
// 面板渲染来源(真后端 user_original → 用户原创)。
await expect(page.getByText('正文来源归因')).toBeVisible({ timeout: 10_000 });
await expect(page.getByText('用户原创')).toBeVisible({ timeout: 10_000 });
});