oh-my-muse/muse-studio/e2e/planning-edit.spec.ts
lili 355cdec314 test(studio): B5 planning 编辑器 e2e 真跑 + meta-schema fixture[P1]
- global-setup.ts 种 2 个 active MetaSchema(setting/worldview,novel_work)+字段+版本级可见性+绑 work1(此前 muse_slice_live 无 active schema→meta-projection unavailable)。

- planning-edit.spec.ts:真后端端到端——进 work1 workspace→作品规划 Tab→meta-projections 真实返回 setting→选 setting 渲染字段→填 time_period→PUT /planning/setting(验 usedFieldKeys 含 time_period+content)→切回验 value 回填。

验证:playwright planning-edit 1 passed + 直查 DB 确认 captureFieldSnapshot 真落库(muse_content_planning_field_snapshot 有 time_period/schema_id=1 行、planning section content_payload={time_period:古代})。B 字段级链路端到端真实贯通。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:50:08 -07:00

70 lines
3.1 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test';
/**
* B5 planning 编辑器端到端(真实后端,MSW off)。
*
* 覆盖 B 字段级链路:work1 经 globalSetup 绑 setting schema(target_type=novel_work)→
* 真实 GET /meta-projections 列投影 → 选 setting 渲染字段 → 填 time_period →
* 真实 PUT /planning/setting(usedFieldKeys)→ 切回验证 value 回填。
* 依赖 globalSetup 种的 active MetaSchema(setting/worldview)+ work1 绑定。
*/
const TOKEN = 'test1';
async function seedToken(page: Page): Promise<void> {
await page.addInitScript((t) => {
window.localStorage.setItem('accessToken', t);
window.localStorage.setItem('tenantId', '1');
}, TOKEN);
}
test('用户按 schema 字段填写规划并经真实后端落库(usedFieldKeys)', async ({ page }) => {
await seedToken(page);
await page.goto('/workspace/1');
await expect(page.getByRole('heading', { name: /正在创作/ })).toBeVisible({ timeout: 15_000 });
// 切到「作品规划」Tab,捕获真实 meta-projections 列表请求(非 mock)。
const listPromise = page.waitForResponse(
(r) =>
/\/meta-projections(\?|$)/.test(r.url()) && r.request().method() === 'GET'
);
await page.getByRole('button', { name: '作品规划' }).click();
const listResp = await listPromise;
// 真后端投影非 unavailable:200 + code 0 + 列出 setting(同 targetType 的 active schema)。
expect(listResp.status()).toBe(200);
const listBody = await listResp.json();
expect(listBody.code).toBe(0);
expect(Array.isArray(listBody.data)).toBe(true);
expect(listBody.data.map((p: { projectionKey: string }) => p.projectionKey)).toContain('setting');
// 选「故事设定」(setting)→ 字段按 schema 渲染。
await page.getByRole('button', { name: '故事设定' }).click();
const timeSelect = page.getByLabel('时代背景');
await expect(timeSelect).toBeVisible({ timeout: 10_000 });
// 填 time_period,保存。
await timeSelect.selectOption('古代');
const savePromise = page.waitForResponse(
(r) => r.url().includes('/planning/setting') && r.request().method() === 'PUT'
);
await page.getByRole('button', { name: /保存规划/ }).click();
const saveResp = await savePromise;
// PUT 200 + 请求体含 usedFieldKeys(字段级用量来源)+ content + 响应 newRevision。
expect(saveResp.status()).toBe(200);
const reqBody = JSON.parse(saveResp.request().postData() || '{}');
expect(reqBody.usedFieldKeys).toContain('time_period');
expect(reqBody.content.time_period).toBe('古代');
const saveBody = await saveResp.json();
expect(saveBody.code).toBe(0);
expect(saveBody.data.sectionKey).toBe('setting');
expect(saveBody.data.newRevision).toBeGreaterThanOrEqual(1); // 重跑 revision 累加,幂等。
// 切走再切回,验证 value 经真后端 projection 回填(time_period='古代')。
await page.getByRole('button', { name: 'AI 创作助手' }).click();
await page.getByRole('button', { name: '作品规划' }).click();
await page.getByRole('button', { name: '故事设定' }).click();
await expect(page.getByLabel('时代背景')).toHaveValue('古代', { timeout: 10_000 });
});