import { test, expect, Page } from '@playwright/test'; const TEST_PASS = 'testpass123'; let userCounter = 0; function shortUid() { userCounter++; return `${userCounter}${Math.random().toString(36).slice(2, 7)}`; } // Helper: register a unique user via API, set tokens, navigate to / async function loginViaAPI(page: Page) { const uid = shortUid(); const username = `t${uid}`; // short, max ~8 chars const resp = await page.request.post('/api/v1/auth/register', { data: { username, email: `${uid}@t.co`, password: TEST_PASS, }, }); if (!resp.ok()) { throw new Error(`Register failed: ${resp.status()} ${await resp.text()}`); } const body = await resp.json(); const tokens = body.tokens; await page.goto('/login'); await page.evaluate((t: { access: string; refresh: string }) => { localStorage.setItem('access_token', t.access); localStorage.setItem('refresh_token', t.refresh); }, tokens); await page.goto('/'); await page.locator('textarea').waitFor({ state: 'visible', timeout: 10000 }); } test.describe('Video Generation Page - P0 Acceptance', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page); }); test('P0-1: should show dark background #0a0a0f', async ({ page }) => { const body = page.locator('body'); const bgColor = await body.evaluate((el: Element) => getComputedStyle(el).backgroundColor); expect(bgColor).toBe('rgb(10, 10, 15)'); }); test('P0-2: InputBar should have correct styling', async ({ page }) => { const bar = page.locator('textarea').locator('xpath=ancestor::div[contains(@class, "bar")]').first(); await expect(bar).toBeVisible(); const bgColor = await bar.evaluate((el: Element) => getComputedStyle(el).backgroundColor); expect(bgColor).toBe('rgb(22, 22, 30)'); const borderRadius = await bar.evaluate((el: Element) => getComputedStyle(el).borderRadius); expect(borderRadius).toBe('20px'); }); test('P0-3: should default to universal mode with upload button and prompt', async ({ page }) => { await expect(page.getByText('参考内容')).toBeVisible(); await expect(page.locator('textarea')).toBeVisible(); }); test('P0-4: upload button should trigger file chooser', async ({ page }) => { const fileChooserPromise = page.waitForEvent('filechooser'); await page.getByText('参考内容').click(); const fileChooser = await fileChooserPromise; expect(fileChooser).toBeTruthy(); }); test('P0-7: toolbar buttons should be visible', async ({ page }) => { await expect(page.getByText('视频生成').first()).toBeVisible(); await expect(page.getByText('Seedance 2.0').first()).toBeVisible(); await expect(page.getByText('全能参考').first()).toBeVisible(); await expect(page.getByText('21:9').first()).toBeVisible(); await expect(page.getByText('15s').first()).toBeVisible(); }); test('P0-8: send button disabled when no content, enabled with text', async ({ page }) => { const sendBtn = page.locator('[class*="sendBtn"]'); await expect(sendBtn).toBeVisible(); await expect(sendBtn).toHaveClass(/sendDisabled/); await page.locator('textarea').fill('test prompt'); await expect(sendBtn).toHaveClass(/sendEnabled/, { timeout: 5000 }); }); }); test.describe('Video Generation Page - P1 Acceptance', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page); }); test('P1-9: generation type dropdown should open and show options', async ({ page }) => { await page.getByText('视频生成').first().click(); await expect(page.getByText('图片生成')).toBeVisible(); }); test('P1-9: model dropdown should open and show options', async ({ page }) => { await page.getByText('Seedance 2.0').first().click(); await expect(page.getByText('Seedance 2.0 Fast')).toBeVisible(); }); test('P1-10: aspect ratio dropdown should show 6 options', async ({ page }) => { await page.getByText('21:9').first().click(); await expect(page.getByText('16:9').first()).toBeVisible(); await expect(page.getByText('9:16')).toBeVisible(); await expect(page.getByText('1:1')).toBeVisible(); await expect(page.getByText('4:3')).toBeVisible(); await expect(page.getByText('3:4')).toBeVisible(); }); test('P1-11: duration dropdown should show 3 options', async ({ page }) => { await page.getByText('15s').first().click(); await expect(page.getByText('5s', { exact: true })).toBeVisible(); await expect(page.getByText('10s', { exact: true })).toBeVisible(); }); test('P1-12: switching to keyframe mode should update UI', async ({ page }) => { await page.getByText('全能参考').first().click(); await page.getByText('首尾帧').first().click(); await expect(page.getByText('自动匹配')).toBeVisible(); await expect(page.getByText('首帧').first()).toBeVisible(); await expect(page.getByText('尾帧').first()).toBeVisible(); await expect(page.getByText('5s').first()).toBeVisible(); await expect(page.getByText('@')).not.toBeVisible(); }); }); test.describe('Video Generation Page - P2 Acceptance', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page); }); test('P2-15: Ctrl+Enter should trigger send', async ({ page }) => { await page.locator('textarea').fill('test prompt'); await page.keyboard.press('Control+Enter'); await expect(page.getByText('在下方输入提示词,开始创作 AI 视频')).not.toBeVisible({ timeout: 5000 }); }); test('P2-16: textarea should be auto-focused on load', async ({ page }) => { const textarea = page.locator('textarea'); await expect(textarea).toBeFocused(); }); }); test.describe('Sidebar', () => { test('should show navigation items', async ({ page }) => { await loginViaAPI(page); await expect(page.getByText('灵感', { exact: true })).toBeVisible(); await expect(page.getByText('生成', { exact: true })).toBeVisible(); await expect(page.getByText('资产', { exact: true })).toBeVisible(); await expect(page.getByText('画布', { exact: true })).toBeVisible(); }); });