Initial commit: Pixel AI comic/video creation platform
- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents - Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow - Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.) - All HTTP clients migrated from sync requests to async httpx - Admin-managed API keys via environment variables - SSRF vulnerability fixed in ensure_url()
This commit is contained in:
151
frontend/tests/e2e/project-canvas.spec.ts
Normal file
151
frontend/tests/e2e/project-canvas.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('Project Canvas Baseline Flow', () => {
|
||||
test('enter project, load canvas, edit prompt node, and trigger save', async ({ page }) => {
|
||||
const savePayloads: Array<Record<string, unknown>> = [];
|
||||
|
||||
await page.route('**/api/v1/**', async (route) => {
|
||||
const req = route.request();
|
||||
const url = new URL(req.url());
|
||||
const path = url.pathname;
|
||||
const method = req.method();
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/config/models') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: { image: {}, video: {}, llm: {}, audio: {} },
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/projects/e2e-project') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {
|
||||
id: 'e2e-project',
|
||||
name: 'E2E Project',
|
||||
type: 'video',
|
||||
status: 'active',
|
||||
created_at: '2026-01-01T00:00:00.000Z',
|
||||
updated_at: '2026-01-01T00:00:00.000Z',
|
||||
episodes: [],
|
||||
assets: [],
|
||||
storyboards: [],
|
||||
generalCanvases: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/projects/e2e-project/canvases') {
|
||||
const canvasType = url.searchParams.get('canvas_type');
|
||||
if (canvasType === 'general') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ code: 0, message: 'ok', data: [] }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (method === 'POST' && path === '/api/v1/projects/e2e-project/canvases') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {
|
||||
id: 'canvas-main-1',
|
||||
projectId: 'e2e-project',
|
||||
canvasType: 'general',
|
||||
relatedEntityType: null,
|
||||
relatedEntityId: null,
|
||||
name: '通用画布',
|
||||
description: '',
|
||||
orderIndex: 0,
|
||||
isPinned: false,
|
||||
tags: [],
|
||||
nodeCount: 0,
|
||||
accessCount: 0,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/canvas') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {
|
||||
id: 'canvas-main-1',
|
||||
nodes: [],
|
||||
connections: [],
|
||||
history: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (method === 'POST' && path === '/api/v1/canvas') {
|
||||
const payload = req.postDataJSON() as Record<string, unknown>;
|
||||
savePayloads.push(payload);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ code: 0, message: 'ok', data: { id: 'canvas-main-1' } }),
|
||||
});
|
||||
}
|
||||
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ code: 0, message: 'ok', data: {} }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/project/e2e-project');
|
||||
|
||||
await expect(
|
||||
page.getByPlaceholder('有什么可以帮您?(可输入 @ 选择素材)')
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId('canvas-add-node-toggle').click();
|
||||
await page.getByTestId('add-node-prompt_input').click();
|
||||
|
||||
const promptTextarea = page.getByTestId('prompt-input-textarea');
|
||||
await expect(promptTextarea).toBeVisible();
|
||||
await promptTextarea.fill('E2E: prompt update should be persisted');
|
||||
|
||||
await page.waitForTimeout(900);
|
||||
await page.keyboard.press('Control+S');
|
||||
|
||||
await expect.poll(() => savePayloads.length, { timeout: 10000 }).toBeGreaterThan(0);
|
||||
|
||||
await expect.poll(
|
||||
() =>
|
||||
savePayloads.some((payload) => {
|
||||
const nodes = (payload.nodes ?? []) as Array<{ data?: { type?: string; prompt?: string } }>;
|
||||
return nodes.some(
|
||||
(node) =>
|
||||
node.data?.type === 'PROMPT_INPUT'
|
||||
&& node.data?.prompt?.includes('E2E: prompt update should be persisted')
|
||||
);
|
||||
}),
|
||||
{ timeout: 10000 }
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user