| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- import { expect, Page, test } from '@playwright/test';
- import path from 'node:path';
- declare global {
- interface Window {
- __seedIpOperatorScenario?: (scenario: 'empty' | 'ready' | 'needs_input' | 'failed') => unknown;
- }
- }
- const scenarioSeedPath = path.resolve(__dirname, '../scripts/validation/ip-operator-scenario-seed.js');
- async function seedScenario(page: Page, scenario: 'empty' | 'ready' | 'needs_input' | 'failed', startTab: 'home' | 'ip-operator' = 'ip-operator') {
- await page.addInitScript({ path: scenarioSeedPath });
- await page.addInitScript(([nextScenario, nextTab]) => {
- window.__seedIpOperatorScenario?.(nextScenario);
- localStorage.setItem('tiktok.currentTab', nextTab);
- }, [scenario, startTab]);
- }
- test('sidebar entry and wizard form reach generation confirmation', async ({ page }) => {
- await seedScenario(page, 'empty', 'home');
- await page.goto('/');
- await page.getByRole('button', { name: /IP操盘/ }).click();
- await expect(page.locator('.ip-operator-page')).toBeVisible();
- await expect(page.locator('.ip-card h2')).toContainText('创建 IP 档案');
- const profileCard = page.locator('.ip-card').first();
- await profileCard.locator('input').nth(0).fill('E2E New Profile');
- await profileCard.locator('input').nth(1).fill('Local growth consultant');
- await profileCard.locator('input').nth(2).fill('Local service');
- await profileCard.locator('input').nth(3).fill('Traditional business owners');
- await profileCard.locator('input').nth(4).fill('IP diagnosis and content operation');
- await profileCard.locator('textarea').nth(0).fill('Content looks like advertising');
- await profileCard.locator('textarea').nth(1).fill('Served local stores with short-video conversion');
- await profileCard.locator('textarea').nth(2).fill('Positioning, topics, scripts');
- await profileCard.locator('textarea').nth(5).fill('Generate a usable 7-day test plan');
- await profileCard.locator('.btn-primary').click();
- const questionnaireCard = page.locator('.ip-card').first();
- await expect(questionnaireCard.locator('h2')).toContainText('轻问卷');
- await questionnaireCard.locator('textarea').nth(0).fill('They want trust but dislike hard selling.');
- await questionnaireCard.locator('textarea').nth(1).fill('I can turn cases into practical content.');
- await questionnaireCard.locator('textarea').nth(2).fill('I have real local-service experience.');
- await questionnaireCard.locator('.btn-primary').click();
- const benchmarkCard = page.locator('.ip-card').first();
- await expect(benchmarkCard.locator('h2')).toContainText('对标账号');
- await benchmarkCard.locator('input').nth(0).fill('Benchmark Owner Account');
- await benchmarkCard.locator('input').nth(2).fill('Why owner content fails to convert');
- await benchmarkCard.locator('textarea').nth(1).fill('It uses direct pain-point diagnosis.');
- await benchmarkCard.locator('.btn-primary').click();
- const generateCard = page.locator('.ip-card').first();
- await expect(generateCard.locator('h2')).toContainText('确认生成范围');
- await expect(generateCard.locator('.ip-generate-scope span')).toHaveCount(9);
- await expect(generateCard.locator('.btn-primary')).toBeEnabled();
- });
- test('needs_input plan blocks production entry points in the browser', async ({ page }) => {
- await seedScenario(page, 'needs_input');
- await page.goto('/');
- await expect(page.locator('.ip-result')).toBeVisible();
- await expect(page.locator('.ip-readiness-panel.is-blocked')).toBeVisible();
- await expect(page.locator('.ip-summary-card').filter({ hasText: '64/100' })).toBeVisible();
- await expect(page.locator('.ip-result-hero .ip-actions button').nth(1)).toBeDisabled();
- await expect(page.locator('.ip-result-hero .ip-actions .btn-primary')).toBeDisabled();
- });
- test('topic and script edits persist userEdited markers and audit trail', async ({ page }) => {
- await seedScenario(page, 'ready');
- await page.goto('/');
- await page.locator('.ip-result-tabs button').nth(1).click();
- const firstTopicTitle = page.locator('.ip-topic-card input').first();
- await firstTopicTitle.fill('Edited owner IP topic title');
- await firstTopicTitle.press('Tab');
- await expect.poll(async () => page.evaluate(() => {
- const planId = 'ip_plan_e2e_ready';
- const raw = localStorage.getItem(`tiktok.ipOperator.plan.${planId}.local-guest`);
- const plan = raw ? JSON.parse(raw) : null;
- return plan?.topics?.some((topic: { title: string; userEdited?: boolean }) => (
- topic.title === 'Edited owner IP topic title' && topic.userEdited === true
- ));
- })).toBe(true);
- await expect(page.locator('.ip-topic-card').first()).toContainText('已手动编辑');
- await page.locator('.ip-result-tabs button').nth(2).click();
- const firstScriptHook = page.locator('.ip-script-detail input').first();
- await firstScriptHook.fill('Edited script hook for browser validation');
- await firstScriptHook.press('Tab');
- await expect.poll(async () => page.evaluate(() => {
- const planId = 'ip_plan_e2e_ready';
- const raw = localStorage.getItem(`tiktok.ipOperator.plan.${planId}.local-guest`);
- const plan = raw ? JSON.parse(raw) : null;
- return plan?.scripts?.some((script: { hook: string; userEdited?: boolean }) => (
- script.hook === 'Edited script hook for browser validation' && script.userEdited === true
- ));
- })).toBe(true);
- const auditCount = await page.evaluate(() => {
- const raw = localStorage.getItem('tiktok.ipOperator.auditTrail.local-guest') || '[]';
- return JSON.parse(raw).length;
- });
- expect(auditCount).toBeGreaterThanOrEqual(3);
- });
- test('failed plan still renders diagnostics instead of a blank page', async ({ page }) => {
- await seedScenario(page, 'failed');
- await page.goto('/');
- await expect(page.locator('.ip-result')).toBeVisible();
- await expect(page.locator('.ip-summary-card').filter({ hasText: '生成失败' })).toBeVisible();
- await page.locator('.ip-debug-panel details summary').click();
- await expect(page.locator('.ip-debug-panel')).toContainText('llm_malformed_json');
- await expect(page.locator('.ip-debug-panel')).toContainText('Malformed JSON in diagnosis stage');
- });
- test('ready plan syncs high-priority topics and enters topic-to-video', async ({ page }) => {
- await seedScenario(page, 'ready');
- await page.goto('/');
- await expect(page.locator('.ip-operator-page h1')).toContainText('IP操盘工作台');
- await expect(page.locator('.ip-readiness-panel')).toContainText('可进入生产链路');
- await expect(page.locator('.ip-summary-card').filter({ hasText: '88/100' })).toBeVisible();
- await page.locator('.ip-result-hero .ip-actions .btn-primary').click();
- await expect.poll(async () => {
- return page.evaluate(() => {
- const items = JSON.parse(localStorage.getItem('videoWorkflow.topicPool.items') || '[]');
- return items.filter((item: { sourceType?: string }) => item.sourceType === 'ip_operator').length;
- });
- }).toBe(4);
- await page.locator('.ip-result-hero .ip-actions button').nth(1).click();
- await expect(page.locator('.topic-list')).toBeVisible();
- await expect(page.getByText('Why owner IP should not look like an ad account')).toBeVisible();
- await page.locator('.topic-pool-toolbar select').first().selectOption('ip_operator');
- await expect(page.getByText('Why owner IP should not look like an ad account')).toBeVisible();
- await expect(page.locator('.topic-source-card')).toHaveCount(4);
- await page.locator('.topic-card-actions .btn-primary').first().click();
- await expect(page.locator('.t2v-page')).toBeVisible();
- await expect(page.locator('.t2v-brief-card')).toBeVisible();
- const topicValue = await page.locator('textarea.dh-textarea').first().inputValue();
- expect(topicValue).toMatch(/Why owner IP should not look like an ad account|Owner IP trust topic [2-4]/);
- });
|