Files
makelore/tests/e2e/image-workspace-v2.spec.ts
T

730 lines
36 KiB
TypeScript

import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
test.describe('AI Design V2 workspace', () => {
test('removes the inspiration entry and redirects its retired route to Canvas', async ({ launchElectronApp }) => {
test.setTimeout(90_000);
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await app.evaluate(({ ipcMain }) => {
const respond = (json: unknown) => ({
ok: true,
data: { status: 200, ok: true, json },
});
ipcMain.removeHandler('hostapi:fetch');
ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string }) => {
const path = request.path ?? '';
if (path === '/api/works/image-workspace') {
return respond({
success: true,
data: {
capabilities: {
conversation: true,
generation: true,
image: true,
video: true,
generationOptions: {
image: {
available: true,
supportedAspectRatios: ['1:1', '3:4', '9:16', '4:3', '16:9'],
supportedDurationSeconds: [],
maxOutputCount: 4,
requiresFirstFrame: false,
supportedReferenceRoles: ['inspiration'],
},
video: {
available: true,
supportedAspectRatios: ['1:1', '3:4', '9:16', '4:3', '16:9'],
supportedDurationSeconds: [1, 6, 12],
maxOutputCount: 1,
requiresFirstFrame: true,
supportedReferenceRoles: ['first_frame'],
},
},
},
workspaces: [],
},
});
}
return { ok: false, error: { message: `Unexpected E2E Host API request: ${path}` } };
});
});
await page.getByTestId('ai-module-option-painting').click();
await expect(page.getByLabel('正在加载 AI 设计')).toBeVisible();
await expect(page.getByRole('button', { name: /获取.*灵感/u })).toHaveCount(0);
await page.evaluate(() => {
window.location.hash = '#/image-prompts';
});
await expect.poll(() => page.evaluate(() => window.location.hash)).toBe('#/image-canvas');
await expect(page.getByLabel('正在加载 AI 设计')).toBeVisible();
} finally {
await closeElectronApp(app);
}
});
test('keeps split-panel drafts, direct edits and production results on one canonical design', async ({ launchElectronApp }, testInfo) => {
test.setTimeout(150_000);
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await app.evaluate(({ ipcMain }) => {
const summary = {
workspaceId: 'workspace-1',
clientWorkspaceId: 'client-workspace-1',
title: '咖啡机新品主视觉',
directionId: 'direction-1',
sessionId: 'session-1',
directionRevision: 4,
specificationRevision: 3,
workspaceViewRevision: 6,
updatedAt: '2026-08-30T10:00:00.000Z',
};
const workspace = {
workspace: summary,
form: {
schemaVersion: 1,
workspaceId: 'workspace-1',
directionId: 'direction-1',
directionRevision: 4,
rawTurnSequence: 1,
specificationRevision: 3,
workspaceViewRevision: 6,
specificationRevisionId: 'specification-revision-3',
specificationDigest: 'a'.repeat(64),
specification: {
schema_version: 1,
values: {
intent: {
media: 'image',
creation_intent: 'create',
purpose: '新品发布主视觉',
channel: '小红书首图',
audience: '年轻消费者',
},
content: {
concept: '把便携咖啡机呈现为城市通勤中的精密工具',
subjects: [],
exact_content: [],
},
visual: {},
layout: { regions: [] },
references: [],
image: {
operation_intent: 'new_generation',
preserve_instructions: [],
change_instructions: [],
},
video: { shots: [], total_duration_seconds: null as number | null },
output: {
aspect_ratio: '3:4',
orientation: 'portrait',
delivery_format: 'PNG',
variant_count: 1,
variant_purposes: [],
},
constraints: {
must_include: [],
must_preserve: [],
must_avoid: [],
legal_notes: [],
safety_notes: [],
acceptance_criteria: [],
},
},
field_decisions: {
'intent.media': {
resolution: 'resolved',
provenance: 'user',
locked: true,
changed_by: 'interaction-1',
origin_turn_id: 'turn-1',
},
'output.aspect_ratio': {
resolution: 'resolved',
provenance: 'user',
locked: false,
changed_by: 'interaction-1',
origin_turn_id: 'turn-1',
},
},
},
decisionPrompts: [],
activeQuotes: [] as Array<Record<string, unknown>>,
recentChangeSet: { interaction_id: 'interaction-1', changes: [] },
},
turns: [{
turnId: 'turn-1',
rawTurnSequence: 1,
userMessage: '做一张便携咖啡机的小红书主视觉',
assistantMessage: '明白了:你想做一张适合小红书的便携咖啡机图片。',
}],
tasks: [] as Array<Record<string, unknown>>,
assets: [] as Array<Record<string, unknown>>,
};
const respond = (json: unknown) => ({
ok: true,
data: { status: 200, ok: true, json },
});
const envelope = (data: unknown) => respond({ success: true, data });
const e2eSession = {
accessToken: 'e2e-design-access-token',
tokenType: 'Bearer',
expiresAt: Date.now() + 60 * 60 * 1000,
lastActiveAt: Date.now(),
canRefresh: false,
};
const e2eGlobal = globalThis as typeof globalThis & {
__designE2eAuthReady?: boolean;
__designE2eProfileReady?: boolean;
__designE2eCompleteTask?: () => void;
__designE2eOpenIncompleteVideo?: () => void;
__designE2eDownloadCount?: number;
};
e2eGlobal.__designE2eAuthReady = false;
e2eGlobal.__designE2eProfileReady = false;
e2eGlobal.__designE2eDownloadCount = 0;
e2eGlobal.__designE2eOpenIncompleteVideo = () => {
workspace.form.specification.values.intent.media = 'video';
workspace.form.specification.values.video.total_duration_seconds = null;
workspace.form.directionRevision += 1;
workspace.form.specificationRevision += 1;
workspace.workspace.specificationRevision = workspace.form.specificationRevision;
};
e2eGlobal.__designE2eCompleteTask = () => {
workspace.tasks[0] = {
...workspace.tasks[0],
status: 'succeeded',
taskRevision: 2,
progress: { stage: 'completed', completedRequiredSteps: 1, totalRequiredSteps: 1 },
resultAssetIds: ['result-1'],
nextActions: ['view_result'],
};
workspace.assets = [{
assetId: 'result-1', workspaceId: 'workspace-1', role: 'generated',
mediaType: 'image', mimeType: 'image/svg+xml', width: 1600, height: 2400,
durationMilliseconds: null, generationTaskId: 'task-1',
createdAt: '2026-08-30T10:02:00.000Z',
contentPath: '/api/works/image-workspace/workspaces/workspace-1/assets/result-1/content',
}];
};
ipcMain.removeHandler('hostapi:fetch');
ipcMain.handle('hostapi:fetch', async (_event, request: {
path?: string;
method?: string;
body?: string | null;
}) => {
const path = request.path ?? '';
if (path.endsWith('/assets/result-1/download')) {
e2eGlobal.__designE2eDownloadCount! += 1;
return envelope({ status: 'saved' });
}
if (path === '/api/auth/session/sync') {
return respond({ success: true, session: e2eSession });
}
if (path === '/api/auth/me') {
e2eGlobal.__designE2eAuthReady = true;
return respond({
success: true,
moduleAccess: { programming: true, design: true, robot: true },
});
}
if (path === '/api/works/user/agent-profile') {
e2eGlobal.__designE2eProfileReady = true;
return respond({
success: true,
profile: {
display_name: 'E2E 设计用户',
age: null,
gender: null,
avatar_url: null,
share_age_with_agents: false,
share_gender_with_agents: false,
analysis_enabled: true,
completed: true,
version: 1,
updated_at: '2026-08-30T10:00:00.000Z',
},
});
}
if (path === '/api/works/image-workspace') {
return envelope({
capabilities: {
conversation: true,
generation: true,
image: true,
video: true,
generationOptions: {
image: {
available: true,
supportedAspectRatios: ['1:1', '3:4', '9:16', '4:3', '16:9'],
supportedDurationSeconds: [],
maxOutputCount: 4,
requiresFirstFrame: false,
supportedReferenceRoles: ['inspiration'],
},
video: {
available: true,
supportedAspectRatios: ['1:1', '3:4', '9:16', '4:3', '16:9'],
supportedDurationSeconds: [1, 6, 12],
maxOutputCount: 1,
requiresFirstFrame: true,
supportedReferenceRoles: ['first_frame'],
},
},
},
workspaces: [workspace.workspace],
});
}
if (path === '/api/works/image-workspace/workspaces/workspace-1' && request.method === 'GET') {
return envelope(workspace);
}
if (path === '/api/works/image-workspace/workspaces/workspace-1/commands') {
const command = JSON.parse(request.body ?? '{}') as {
kind?: string;
clientOperationId?: string;
input?: {
kind?: string;
message?: string;
operations?: Array<{ kind?: string; path?: string; value?: unknown }>;
};
};
if (command.kind === 'apply_input') {
if (command.input?.kind === 'prepare_generation') {
(globalThis as typeof globalThis & { __designPreparedRevision?: number }).__designPreparedRevision = workspace.form.specificationRevision + 1;
}
if (command.input?.kind === 'direct_edit') {
for (const operation of command.input.operations ?? []) {
if (operation.kind === 'set' && operation.path === 'output.aspect_ratio') {
workspace.form.specification.values.output.aspect_ratio = String(operation.value ?? '');
}
if (operation.kind === 'set' && operation.path === 'output.orientation') {
workspace.form.specification.values.output.orientation = String(operation.value ?? '');
}
if (operation.kind === 'set' && operation.path === 'video.total_duration_seconds') {
workspace.form.specification.values.video.total_duration_seconds = Number(operation.value);
}
}
}
if (command.input?.kind === 'chat' && command.input.message) {
workspace.form.rawTurnSequence += 1;
workspace.turns.push({
turnId: `turn-${workspace.form.rawTurnSequence}`,
rawTurnSequence: workspace.form.rawTurnSequence,
userMessage: command.input.message,
assistantMessage: '记住了:主体更简洁,也会为标题留出空间。',
});
}
workspace.form.directionRevision += 1;
workspace.form.specificationRevision += 1;
workspace.form.workspaceViewRevision += 1;
workspace.form.specificationRevisionId = `specification-revision-${workspace.form.specificationRevision}`;
workspace.workspace.directionRevision = workspace.form.directionRevision;
workspace.workspace.specificationRevision = workspace.form.specificationRevision;
workspace.workspace.workspaceViewRevision = workspace.form.workspaceViewRevision;
}
if (command.kind === 'request_quote') {
if ((globalThis as typeof globalThis & { __designPreparedRevision?: number }).__designPreparedRevision !== workspace.form.specificationRevision) {
throw new Error('Quote must use the latest prepared revision');
}
workspace.form.activeQuotes = [{
quoteId: 'quote-1',
status: 'offered',
specificationRevision: workspace.form.specificationRevision,
specificationRevisionId: workspace.form.specificationRevisionId,
specificationDigest: workspace.form.specificationDigest,
medium: 'image',
compilerVersion: 'design-compiler-v2',
outputSummary: {
medium: 'image',
strategy: 'direct',
aspectRatio: workspace.form.specification.values.output.aspect_ratio,
outputCount: 1,
deliveryFormat: 'PNG',
durationSeconds: null,
requiredOutputRoles: ['primary'],
},
warnings: [],
quotedDesignPoints: 12,
maximumCustomerChargeAtoms: 1200,
expiresAt: '2030-01-02T03:04:05.000Z',
}];
}
if (command.kind === 'confirm_generation') {
const offeredQuote = workspace.form.activeQuotes[0] ?? {};
workspace.form.activeQuotes = [{ ...offeredQuote, status: 'consumed' }];
workspace.tasks = [{
taskId: 'task-1',
quoteId: 'quote-1',
status: 'queued',
taskRevision: 1,
specificationRevision: workspace.form.specificationRevision,
specificationRevisionId: workspace.form.specificationRevisionId,
specificationDigest: workspace.form.specificationDigest,
medium: 'image',
compilerVersion: 'design-compiler-v2',
outputSummary: offeredQuote.outputSummary,
maximumCustomerChargeAtoms: 1200,
customerBilling: {
quotedAtoms: 1200,
heldAtoms: 1200,
chargedAtoms: 0,
refundedAtoms: 0,
pendingResolutionAtoms: 0,
},
customerHoldExpiresAt: '2030-01-02T03:04:05.000Z',
resolutionDeadlineAt: '2030-01-02T03:09:05.000Z',
progress: { stage: 'queued', completedRequiredSteps: 0, totalRequiredSteps: 1 },
executionHealth: 'normal',
cancellation: { state: 'available', outcome: null },
issues: [],
resultAssetIds: [],
nextActions: ['cancel'],
createdAt: '2026-08-30T10:01:00.000Z',
}];
}
return envelope({
clientOperationId: command.clientOperationId ?? 'operation-e2e',
runId: `run-${command.clientOperationId ?? 'e2e'}`,
workspace,
});
}
return { ok: false, error: { message: `Unexpected E2E Host API request: ${path}` } };
});
});
await page.addInitScript(() => {
const now = Date.now();
localStorage.setItem('niancode-settings', JSON.stringify({
state: { sidebarCollapsed: true },
version: 0,
}));
localStorage.setItem('niancode-auth', JSON.stringify({
state: {
authBase: 'https://biz.nianxx.cn/auth/',
clientId: 'app',
accessToken: 'e2e-design-access-token',
tokenType: 'Bearer',
expiresAt: now + 60 * 60 * 1000,
lastActiveAt: now,
canRefresh: false,
legacyRefreshToken: null,
user: {
username: 'e2e-design-user',
userId: 'e2e-user-1',
tenantId: null,
deptId: null,
authorities: [],
},
moduleAccess: { programming: true, design: true, robot: true },
},
version: 2,
}));
});
await page.reload();
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await expect.poll(async () => await app.evaluate(() => Boolean(
(globalThis as typeof globalThis & { __designE2eAuthReady?: boolean }).__designE2eAuthReady,
))).toBe(true);
await page.setViewportSize({ width: 1440, height: 900 });
await page.getByTestId('ai-module-option-painting').click();
await expect(page.getByTestId('image-workspace')).toBeVisible();
const conversation = page.getByTestId('image-workspace-conversation');
const split = page.getByTestId('canvas-workspace-split');
const conversationPane = split.getByRole('region', { name: '创作对话', exact: true });
const worksPane = split.getByRole('region', { name: '作品工作区', exact: true });
const separator = page.getByRole('separator', { name: '调整对话与作品宽度' });
const creationCard = page.getByTestId('youth-creation-card');
const composer = page.getByLabel('告诉 AI 你想创作什么');
const currentTab = worksPane.getByRole('button', { name: '当前作品', exact: true });
const historyTab = worksPane.getByRole('button', { name: /^历史作品/u });
await expect(conversation).toBeVisible();
await expect(page.getByTestId('design-message-composer')).toBeVisible();
await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible();
await expect(page.getByTestId('image-workspace-history-rail')).toBeVisible();
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveCount(0);
await expect(page.getByRole('heading', { name: '和 AI 一起完善创作' })).toHaveCount(0);
await expect(page.getByText('当前设计项目的制作记录')).toHaveCount(0);
await expect(page.getByText('实时同步')).toHaveCount(0);
await expect(page.getByRole('button', { name: '刷新作品' })).toHaveCount(0);
await expect(worksPane.getByRole('heading', { name: '制作方案' })).toBeVisible();
await expect(worksPane.getByTestId('youth-creation-card')).toHaveCount(1);
await expect(conversation.getByTestId('youth-creation-card')).toHaveCount(0);
await expect(page.getByRole('textbox', { name: '创作提示词' })).toHaveValue(
'把便携咖啡机呈现为城市通勤中的精密工具',
);
await expect(page.getByText('Living Form')).toHaveCount(0);
await expect(page.getByText('目标与用途')).toHaveCount(0);
await expect(page.locator('[data-testid^="sidebar-image-conversation-"]')).toHaveCount(0);
const splitBounds = (await split.boundingBox())!;
const conversationBounds = (await conversationPane.boundingBox())!;
const worksBounds = (await worksPane.boundingBox())!;
const separatorBounds = (await separator.boundingBox())!;
const headerBounds = (await page.getByTestId('image-workspace-header').boundingBox())!;
expect(headerBounds.height).toBe(64);
expect(conversationBounds.height).toBeGreaterThan(500);
expect(Math.abs(headerBounds.y + headerBounds.height - splitBounds.y)).toBeLessThanOrEqual(1);
expect(separatorBounds.width).toBe(8);
expect(Math.abs(conversationBounds.x + conversationBounds.width - separatorBounds.x)).toBeLessThanOrEqual(1);
expect(Math.abs(separatorBounds.x + separatorBounds.width - worksBounds.x)).toBeLessThanOrEqual(1);
expect(Math.abs(conversationBounds.y - worksBounds.y)).toBeLessThanOrEqual(1);
expect(Math.abs(conversationBounds.height - worksBounds.height)).toBeLessThanOrEqual(1);
expect(conversationBounds.width / (splitBounds.width - separatorBounds.width)).toBeCloseTo(0.42, 2);
expect(worksBounds.width).toBeGreaterThan(conversationBounds.width);
await expect(separator).toHaveAttribute('aria-valuenow', '42');
await page.screenshot({ path: testInfo.outputPath('canvas-desktop-split.png'), animations: 'disabled' });
// Resize changes usable panel widths, and both keyboard and pointer reset work.
await separator.focus();
await separator.press('ArrowRight');
await expect(separator).toHaveAttribute('aria-valuenow', '44');
expect((await conversationPane.boundingBox())!.width).toBeGreaterThan(conversationBounds.width + 15);
await separator.press('ArrowLeft');
await expect(separator).toHaveAttribute('aria-valuenow', '42');
await page.mouse.move(separatorBounds.x + separatorBounds.width / 2, separatorBounds.y + 100);
await page.mouse.down();
await page.mouse.move(splitBounds.x + (splitBounds.width - 8) * 0.55, separatorBounds.y + 100, { steps: 8 });
await page.mouse.up();
await expect(separator).toHaveAttribute('aria-valuenow', '55');
expect((await conversationPane.boundingBox())!.width).toBeGreaterThan((await worksPane.boundingBox())!.width);
await separator.dblclick();
await expect(separator).toHaveAttribute('aria-valuenow', '42');
await separator.press('ArrowRight');
await separator.press('Home');
await expect(separator).toHaveAttribute('aria-valuenow', '42');
const unsentDraft = '还没发送的想法:让咖啡机更有未来感';
await composer.fill(unsentDraft);
await composer.press('F6');
await expect(worksPane.getByRole('button', { name: '展开作品工作区' })).toBeFocused();
await page.keyboard.press('F6');
await expect(conversationPane.getByRole('button', { name: '展开创作对话' })).toBeFocused();
await conversationPane.getByRole('button', { name: '展开创作对话' }).click();
await expect(worksPane).toBeHidden();
await expect(creationCard).toHaveCount(1);
await expect(separator).toBeHidden();
expect((await conversationPane.boundingBox())!.width).toBeCloseTo(splitBounds.width, 0);
await expect(composer).toHaveValue(unsentDraft);
await page.keyboard.press('Escape');
await expect(worksPane).toBeVisible();
await expect(separator).toBeVisible();
await worksPane.getByRole('button', { name: '展开作品工作区' }).click();
await expect(conversationPane).toBeHidden();
await expect(conversation).toHaveCount(1);
await expect(creationCard).toBeVisible();
await worksPane.getByRole('button', { name: '恢复分栏' }).click();
await expect(composer).toHaveValue(unsentDraft);
await expect(separator).toHaveAttribute('aria-valuenow', '42');
await historyTab.click();
await expect(historyTab).toHaveAttribute('aria-pressed', 'true');
await expect(worksPane.getByText('还没有历史作品', { exact: true })).toBeVisible();
await expect(creationCard).toBeHidden();
await expect(creationCard).toHaveCount(1);
await currentTab.click();
await expect(currentTab).toHaveAttribute('aria-pressed', 'true');
await expect(creationCard).toBeVisible();
await expect(composer).toHaveValue(unsentDraft);
const desktopViewport = page.viewportSize();
await page.setViewportSize({ width: 375, height: 800 });
await expect(page.getByTestId('sidebar')).toBeHidden();
// The last explicitly expanded panel remains selected when the layout becomes compact.
await expect(worksPane).toBeVisible();
await page.getByRole('button', { name: '创作对话', exact: true }).click();
await expect(conversation).toBeVisible();
await expect(worksPane).toBeHidden();
await expect(separator).toBeHidden();
await expect(page.getByTestId('image-workspace-history-rail')).toHaveCount(1);
await expect(creationCard).toBeHidden();
await expect(composer).toHaveValue(unsentDraft);
await page.getByRole('button', { name: '作品与方案', exact: true }).click();
await expect(conversation).toBeHidden();
await expect(conversation).toHaveCount(1);
await expect(creationCard).toBeVisible();
await page.getByRole('combobox', { name: '画幅' }).selectOption('16:9');
await expect(page.getByRole('combobox', { name: '画幅' })).toHaveValue('16:9');
await historyTab.click();
await expect(worksPane.getByText('还没有历史作品', { exact: true })).toBeVisible();
await page.getByRole('button', { name: '创作对话', exact: true }).click();
await expect(composer).toHaveValue(unsentDraft);
await page.getByRole('button', { name: '项目', exact: true }).click();
await expect(page.getByTestId('image-workspace-works-rail')).toBeVisible();
await page.keyboard.press('Escape');
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
await expect(composer).toHaveValue(unsentDraft);
await page.setViewportSize({ width: 768, height: 800 });
await expect(conversation).toBeVisible();
await expect(page.getByRole('button', { name: '作品与方案', exact: true })).toBeVisible();
await expect(page.getByRole('button', { name: '项目', exact: true })).toBeVisible();
await page.getByRole('button', { name: '作品与方案', exact: true }).click();
await expect(historyTab).toHaveAttribute('aria-pressed', 'true');
await currentTab.click();
await expect(creationCard).toBeVisible();
await expect(page.getByRole('combobox', { name: '画幅' })).toHaveValue('16:9');
await page.getByRole('button', { name: '创作对话', exact: true }).click();
await expect(composer).toHaveValue(unsentDraft);
// The usable Canvas container selects one panel through 899 px and splits from 900 px.
await page.setViewportSize({ width: 899, height: 800 });
await expect(conversation).toBeVisible();
await expect(worksPane).toBeHidden();
await expect(separator).toBeHidden();
await page.getByRole('button', { name: '作品与方案', exact: true }).click();
await expect(creationCard).toBeVisible();
await expect(conversation).toBeHidden();
await page.getByRole('button', { name: '创作对话', exact: true }).click();
await expect(composer).toHaveValue(unsentDraft);
await page.setViewportSize({ width: 900, height: 800 });
await expect(conversation).toBeVisible();
await expect(worksPane).toBeVisible();
await expect(separator).toBeVisible();
await expect(page.getByRole('button', { name: '作品与方案', exact: true })).toHaveCount(0);
await expect(composer).toHaveValue(unsentDraft);
const boundarySplit = (await split.boundingBox())!;
const boundaryWorks = (await worksPane.boundingBox())!;
expect(boundarySplit.width).toBe(900);
expect(boundaryWorks.x + boundaryWorks.width).toBeLessThanOrEqual(900);
await page.setViewportSize(desktopViewport ?? { width: 1440, height: 900 });
await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible();
await expect(conversation).toBeVisible();
await expect(worksPane).toBeVisible();
await expect(separator).toBeVisible();
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
await expect(composer).toHaveValue(unsentDraft);
await expect(page.getByRole('combobox', { name: '画幅' })).toHaveValue('16:9');
const followUp = '主体更克制,留出品牌标题空间';
await page.getByLabel('告诉 AI 你想创作什么').fill(followUp);
await page.getByLabel('告诉 AI 你想创作什么').press('Enter');
await expect(page.getByTestId('image-workspace-conversation')).toContainText(followUp);
await expect(page.getByTestId('image-workspace-conversation'))
.toContainText('记住了:主体更简洁,也会为标题留出空间。');
await page.getByRole('button', { name: '准备制作方案', exact: true }).click();
await expect(page.getByRole('button', { name: '确认并开始制作' })).toBeVisible();
await expect(page.getByText(/预计使用/)).toContainText('12');
await expect(page.getByRole('textbox', { name: '创作提示词' })).toBeVisible();
await expect(page.getByText(/规格版本|编译器|生成方式|不可变/)).toHaveCount(0);
await page.getByRole('button', { name: '确认并开始制作' }).click();
await historyTab.click();
await expect(worksPane.getByTestId('design-plan-history-task-1')).toBeVisible();
await expect(conversation.getByTestId('design-plan-history-task-1')).toHaveCount(0);
await expect(worksPane.getByTestId('design-plan-history-task-1')).toContainText('等待制作');
await expect(page.getByTestId('image-workspace-header').getByRole('status')).toHaveText('等待制作');
await expect(page.getByTestId('youth-creation-card')).toHaveCount(0);
await expect(page.getByText('1200', { exact: true })).toHaveCount(0);
// Complete the existing task without a paid provider or native download.
await page.route('**/assets/result-1/content?*', (route) => route.fulfill({
contentType: 'image/svg+xml',
body: '<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="2400"><rect width="1600" height="2400" fill="#f4efe5"/><rect x="80" y="80" width="1440" height="2240" rx="80" fill="#d6e5dc"/><circle cx="800" cy="1050" r="450" fill="#47665b"/><text x="800" y="1850" text-anchor="middle" font-size="120" fill="#203b30">DESIGN PREVIEW</text></svg>',
}));
await app.evaluate(() => {
(globalThis as typeof globalThis & { __designE2eCompleteTask?: () => void }).__designE2eCompleteTask?.();
});
await page.reload();
await expect(page.getByTestId('canvas-current-work')).toBeVisible();
await expect(page.getByTestId('canvas-current-work').getByRole('img', { name: '当前图片作品' })).toBeVisible();
await expect(page.getByTestId('canvas-current-work').getByRole('button', { name: '保存到电脑' })).toBeEnabled();
await expect(currentTab).toHaveAttribute('aria-pressed', 'true');
await page.screenshot({ path: testInfo.outputPath('canvas-desktop-current-work.png'), animations: 'disabled' });
await historyTab.click();
const historyTask = worksPane.getByTestId('design-plan-history-task-1');
await expect(historyTask).toContainText('已完成');
await historyTask.locator('summary').click();
const previewButton = historyTask.getByRole('button', { name: '放大查看图片' });
await previewButton.click();
const previewDialog = page.getByRole('dialog', { name: '图片预览' });
const previewImage = previewDialog.getByRole('img', { name: 'AI 作品大图' });
await expect(previewDialog).toBeVisible();
await expect(previewDialog.getByRole('button', { name: '原始尺寸' })).toBeEnabled();
expect(await previewImage.evaluate((image: HTMLImageElement) => image.naturalWidth)).toBe(1600);
await expect(previewImage).toHaveCSS('object-fit', 'contain');
const previewBounds = await previewDialog.boundingBox();
expect(previewBounds!.height).toBeGreaterThan(400);
expect(previewBounds!.width).toBeGreaterThan(800);
await previewDialog.getByRole('button', { name: '原始尺寸' }).click();
expect((await previewImage.boundingBox())!.width).toBe(1600);
expect((await previewImage.boundingBox())!.height).toBe(2400);
const scrollArea = previewDialog.getByTestId('design-image-preview-viewport');
expect(await scrollArea.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true);
await previewDialog.getByRole('button', { name: '适应窗口' }).click();
await page.keyboard.press('Escape');
await expect(previewDialog).toHaveCount(0);
await expect(previewButton).toBeFocused();
// A historical result becomes the current large workspace preview without a download.
await historyTask.getByRole('button', { name: '在工作区查看' }).click();
await expect(currentTab).toHaveAttribute('aria-pressed', 'true');
await expect(historyTask).toBeHidden();
await expect(page.getByTestId('canvas-current-work')).toBeVisible();
const currentPreview = page.getByTestId('canvas-current-work').getByRole('button', { name: '放大查看图片' });
await currentPreview.click();
await expect(previewDialog).toBeVisible();
await previewDialog.getByRole('button', { name: '关闭图片预览' }).click();
await expect(currentPreview).toBeFocused();
await page.setViewportSize({ width: 375, height: 800 });
await expect(conversation).toBeVisible();
await expect(worksPane).toBeHidden();
await page.getByRole('button', { name: '作品与方案', exact: true }).click();
await expect(page.getByTestId('canvas-current-work')).toBeVisible();
await page.screenshot({ path: testInfo.outputPath('canvas-compact-current-work.png'), animations: 'disabled' });
await historyTab.click();
const compactTask = worksPane.getByTestId('design-plan-history-task-1');
await expect(compactTask).toBeVisible();
await expect(conversation.getByTestId('design-plan-history-task-1')).toHaveCount(0);
await compactTask.getByRole('button', { name: '放大查看图片' }).click();
await expect(previewDialog.getByRole('button', { name: '原始尺寸' })).toBeEnabled();
const compactBounds = await previewDialog.boundingBox();
expect(compactBounds!.x).toBeGreaterThanOrEqual(0);
expect(compactBounds!.x + compactBounds!.width).toBeLessThanOrEqual(375);
await previewDialog.getByRole('button', { name: '关闭图片预览' }).click();
await expect(previewDialog).toHaveCount(0);
expect(await app.evaluate(() => (
globalThis as typeof globalThis & { __designE2eDownloadCount?: number }
).__designE2eDownloadCount)).toBe(0);
await app.evaluate(() => {
(globalThis as typeof globalThis & { __designE2eOpenIncompleteVideo?: () => void })
.__designE2eOpenIncompleteVideo?.();
});
await page.reload();
await page.getByRole('button', { name: '作品与方案', exact: true }).click();
const duration = page.getByRole('combobox', { name: '时长' });
await expect(duration).toHaveValue('');
await expect(page.getByRole('button', { name: '增加时长' })).toHaveCount(0);
await expect(page.getByText('至少写一个视频画面。')).toHaveCount(0);
await duration.selectOption('6');
await expect(page.getByRole('button', { name: '增加时长' })).toBeEnabled();
await page.reload();
await page.getByRole('button', { name: '作品与方案', exact: true }).click();
await expect(page.getByRole('combobox', { name: '时长' })).toHaveCount(0);
await expect(page.getByText('6 秒', { exact: true })).toBeVisible();
} finally {
await closeElectronApp(app);
}
});
});