- web/: React + Vite + TypeScript 前端 - backend/: Django + DRF + SimpleJWT 后端 - prototype/: HTML 设计原型 - docs/: PRD 和设计评审文档 - test: 单元测试 + E2E 极限测试
161 lines
6.0 KiB
TypeScript
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();
|
|
});
|
|
});
|