video-shuoshan/web/test/e2e/video-generation.spec.ts
zyc ffe92f7b15 Initial commit: 即梦视频生成平台
- web/: React + Vite + TypeScript 前端
- backend/: Django + DRF + SimpleJWT 后端
- prototype/: HTML 设计原型
- docs/: PRD 和设计评审文档
- test: 单元测试 + E2E 极限测试
2026-03-13 09:59:33 +08:00

161 lines
6.0 KiB
TypeScript

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();
});
});