- 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>
70 lines
3.1 KiB
TypeScript
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 });
|
|
});
|