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>, recentChangeSet: { interaction_id: 'interaction-1', changes: [] }, }, turns: [{ turnId: 'turn-1', rawTurnSequence: 1, userMessage: '做一张便携咖啡机的小红书主视觉', assistantMessage: '明白了:你想做一张适合小红书的便携咖啡机图片。', }], tasks: [] as Array>, assets: [] as Array>, }; 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: 'DESIGN PREVIEW', })); 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); } }); });