ip-operator-workbench.spec.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import { expect, Page, test } from '@playwright/test';
  2. import path from 'node:path';
  3. declare global {
  4. interface Window {
  5. __seedIpOperatorScenario?: (scenario: 'empty' | 'ready' | 'needs_input' | 'failed') => unknown;
  6. }
  7. }
  8. const scenarioSeedPath = path.resolve(__dirname, '../scripts/validation/ip-operator-scenario-seed.js');
  9. async function seedScenario(page: Page, scenario: 'empty' | 'ready' | 'needs_input' | 'failed', startTab: 'home' | 'ip-operator' = 'ip-operator') {
  10. await page.addInitScript({ path: scenarioSeedPath });
  11. await page.addInitScript(([nextScenario, nextTab]) => {
  12. window.__seedIpOperatorScenario?.(nextScenario);
  13. localStorage.setItem('tiktok.currentTab', nextTab);
  14. }, [scenario, startTab]);
  15. }
  16. test('sidebar entry opens the account-driven workbench without legacy V1 tools', async ({ page }) => {
  17. await seedScenario(page, 'empty', 'home');
  18. await page.goto('/');
  19. await page.getByRole('button', { name: /^IP操盘\s*V1$/ }).click();
  20. await expect(page.locator('.ip-operator-page')).toBeVisible();
  21. await expect(page.locator('.account-workbench')).toBeVisible();
  22. await expect(page.locator('.operator-cockpit')).toContainText('账号运营驾驶舱');
  23. await expect(page.locator('.legacy-plan-archive')).toHaveCount(0);
  24. await expect(page.locator('.low-frequency-panel')).toHaveCount(0);
  25. await expect(page.getByRole('button', { name: '新建档案' })).toHaveCount(0);
  26. await expect(page.getByText('创建 IP 档案')).toHaveCount(0);
  27. });
  28. test('ready scenario keeps account evidence, directions and publish review in the current workbench', async ({ page }) => {
  29. await seedScenario(page, 'ready');
  30. await page.goto('/');
  31. await expect(page.locator('.account-workbench')).toContainText('账号运营驾驶舱');
  32. await expect(page.locator('.account-monitor-panel')).toContainText('账号内容与数据');
  33. await expect(page.locator('.priority-task-board')).toContainText('今天必须做');
  34. await page.getByRole('button', { name: /方向选题/ }).click();
  35. await expect(page.locator('.direction-workspace')).toBeVisible();
  36. await expect(page.locator('.direction-topic-card').first()).toBeVisible();
  37. const firstTopicCard = page.locator('.direction-topic-card').first();
  38. await firstTopicCard.getByRole('button', { name: /生成分镜脚本|分镜脚本已生成/ }).click();
  39. await expect(page.locator('.shot-script-modal')).toBeVisible();
  40. await expect(page.locator('.shot-script-modal')).toContainText('开头钩子');
  41. await page.locator('.shot-script-modal__close').click();
  42. await firstTopicCard.getByRole('button', { name: /生成并查看发布包|查看发布包/ }).click();
  43. await expect(page.locator('.publish-package-modal')).toBeVisible();
  44. await expect(page.locator('.publish-package-modal')).toContainText('标题候选');
  45. await expect(page.locator('.publish-package-modal')).toContainText('风险检查');
  46. await page.locator('.publish-package-modal__close').click({ force: true });
  47. await expect(page.locator('.publish-binding-panel').first()).toContainText('发布复盘:把真实表现反哺下一轮');
  48. await expect(page.locator('.publish-binding-panel').first()).toContainText('已绑定,待复盘');
  49. });
  50. test('ready scenario exposes the full calibration and retrospective loop without storage internals', async ({ page }) => {
  51. await seedScenario(page, 'ready');
  52. await page.goto('/');
  53. await expect(page.locator('.account-lifecycle-card')).toContainText('当前阶段');
  54. await expect(page.locator('.account-lifecycle-card')).toContainText('下一步');
  55. await expect(page.locator('.strategy-report-meta')).toContainText('判断置信度');
  56. await expect(page.locator('.strategy-report-meta')).toContainText('中');
  57. await expect(page.locator('.strategy-report-meta')).toContainText('已采用校准');
  58. await expect(page.locator('.strategy-report-meta')).toContainText('仍缺发布后复盘数据的长期对比');
  59. await expect(page.locator('.account-calibration-panel')).toContainText('减少纯工具合集');
  60. await expect(page.locator('.publish-retrospective-summary')).toContainText('符合预期');
  61. await expect(page.locator('.publish-retrospective-summary')).toContainText('下一步:继续放大');
  62. const pageText = await page.locator('.account-workbench').textContent();
  63. expect(pageText).not.toContain('Parse');
  64. expect(pageText).not.toContain('VideoWorkflowEntity');
  65. expect(pageText).not.toContain('entityType');
  66. expect(pageText).not.toContain('evidenceRefs');
  67. await page.getByLabel('定位/关注理由修正').fill('继续强调老板经营问题,不再泛讲工具清单');
  68. await page.getByLabel('不要再推荐的话题').fill('纯工具合集, 泛 AI 工具盘点');
  69. await page.getByLabel('不要使用的表达').fill('保姆级万能');
  70. await page.getByLabel('老号经验补充').fill('案例拆解更容易带来收藏和有效评论');
  71. await page.getByRole('button', { name: '保存账号校准' }).click();
  72. await expect(page.locator('.calibration-summary')).toContainText('继续强调老板经营问题');
  73. await page.getByRole('button', { name: /方向选题/ }).click();
  74. await expect(page.locator('.direction-workspace')).toBeVisible();
  75. await page.getByRole('button', { name: /总览/ }).click();
  76. await expect(page.locator('.calibration-summary')).toContainText('继续强调老板经营问题');
  77. });
  78. test('failed scenario still renders the account cockpit instead of a blank legacy page', async ({ page }) => {
  79. await seedScenario(page, 'failed');
  80. await page.goto('/');
  81. await expect(page.locator('.account-workbench')).toBeVisible();
  82. await expect(page.locator('.operator-cockpit')).toContainText('账号运营驾驶舱');
  83. await expect(page.locator('.legacy-plan-archive')).toHaveCount(0);
  84. await expect(page.locator('.account-diagnosis-panel, .operator-cockpit__empty')).toHaveCount(1);
  85. });