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>
45 lines
2.4 KiB
TypeScript
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 });
|
|
});
|