// ============================================================ // 核心用户路径 E2E 冒烟(矩阵 118 · 前端 E2E) // 登录(JMS)→ 跑一版排产(柔性)→ 结果块出现 → 视口切换(世界对比/柔性工作台)→ 命令面板 // 全部为中文断言;依赖隔离后端 + JMS 网络可达,登录参数必须由环境变量注入。 // 设置 APS_E2E_SKIP_JMS=1 时跳过本文件(CI 无外网场景),只保留访客冒烟。 // 注意:默认 preview(生产构建)模式运行;dev 模式 StrictMode 结果捕获缺陷已于 round-42 // 修复并验证(E2E_MODE=dev 附加冒烟),preview 仍为默认门禁。 // ============================================================ import { test, expect, type Page } from '@playwright/test'; const SKIP_JMS = process.env.APS_E2E_SKIP_JMS === '1'; const JMS = { tenant: process.env.E2E_JMS_TENANT?.trim() || '', username: process.env.E2E_JMS_USER?.trim() || '', password: process.env.E2E_JMS_PASSWORD || '', }; const missingJmsConfig = Object.entries(JMS).filter(([, value]) => !value).map(([key]) => key); if (!SKIP_JMS && missingJmsConfig.length) { throw new Error( `[e2e] JMS 登录参数未配置:${missingJmsConfig.join(', ')};` + '请设置 E2E_JMS_TENANT/E2E_JMS_USER/E2E_JMS_PASSWORD,或设置 APS_E2E_SKIP_JMS=1。', ); } async function login(page: Page): Promise { await page.goto('/'); const trigger = page.locator('.guest-login-trigger'); await expect(trigger).toBeEnabled({ timeout: 30_000 }); await trigger.click(); const dialog = page.locator('.auth-login-dialog'); await expect(dialog).toBeVisible(); await dialog.locator('input[placeholder="请输入企业名称"]').fill(JMS.tenant); await dialog.locator('input[placeholder="请输入用户名"]').fill(JMS.username); await dialog.locator('input[placeholder="请输入密码"]').fill(JMS.password); await dialog.locator('.login-submit').click(); // 成功 → 弹窗关闭;若后端要求图形验证码则报清晰错误(本地 jms 未开启) await expect(dialog).toBeHidden({ timeout: 30_000 }).catch(async (error) => { if (await dialog.locator('.login-captcha-field').count()) { throw new Error('登录要求图形验证码,E2E 冒烟无法自动处理(请用预置测试账号或关闭验证码)'); } throw error; }); // 进入工作台:登录态下出现排产指令输入框 await expect(page.locator('textarea[placeholder^="输入排产指令"]')).toBeVisible({ timeout: 60_000 }); await expect(page.locator('.sidebar-brand')).toContainText('工业智核'); await expect(page.locator('.sidebar-account-area')).toContainText(JMS.username); } test.describe('核心用户路径(JMS)', () => { test.skip(SKIP_JMS, 'APS_E2E_SKIP_JMS=1:跳过需要外网 JMS 的核心路径'); test('登录 → 跑一版排产 → 结果块 → 视口切换 → 命令面板', async ({ page }) => { await login(page); // ---------- 跑一版排产(柔性,当前种子数据可直接出结果) ---------- const composer = page.locator('textarea[placeholder^="输入排产指令"]'); await composer.fill('跑一版柔性排产'); await composer.press('Enter'); // 智能体回复出现(SSE 流式正文非空,中文断言) await expect(page.locator('.msg.agent .bubble')).toContainText(/.+/, { timeout: 240_000 }); // 结果块出现:柔性排产结果卡(中文标题断言) const resultBlock = page.locator('.flex-block-title', { hasText: '柔性排产' }); await expect(resultBlock).toBeVisible({ timeout: 240_000 }); // 结果入口可用:右侧“会话结果/再点收起”按钮(preview 模式下结果捕获后自动打开视口) const resultButton = page.locator( '.right-rail button[data-tip="会话结果"], .right-rail button[data-tip="再点收起"]', ); await expect(resultButton).toBeEnabled({ timeout: 120_000 }); // ---------- 视口切换 ---------- // 结果捕获成功后视口已自动打开;未打开则点击“会话结果”兜底 const vpToolbar = page.locator('.vp-toolbar'); await expect(vpToolbar).toBeVisible({ timeout: 30_000 }).catch(async () => { await resultButton.click(); await expect(vpToolbar).toBeVisible({ timeout: 30_000 }); }); // 页签存在:世界对比 const diffTab = page.locator('.seg .tab', { hasText: '世界对比' }); await expect(diffTab).toBeVisible(); await diffTab.click(); await expect(page.locator('.world-diff')).toBeVisible(); // 右侧工具:柔性工作台(任务要求“柔性工作台页签存在”) const flexButton = page.locator('.right-rail button[data-tip="柔性工作台"]'); await expect(flexButton).toBeVisible(); await flexButton.click(); await expect(page.locator('.flex-bench')).toBeVisible(); // ---------- 命令面板 ---------- await page.keyboard.press('Control+K'); const paletteInput = page.locator('.palette input'); await expect(paletteInput).toBeVisible(); // 未过滤时面板条目可用(柔性工作台面板项) await expect(page.locator('.palette-item', { hasText: '柔性工作台' })).toBeVisible(); // 中文过滤:世界 → 两个世界对比视图命令 await paletteInput.fill('世界'); await expect(page.locator('.palette-item', { hasText: '两个世界对比' })).toBeVisible(); // 中文过滤:柔性 → 柔性工作台面板项 await paletteInput.fill('柔性'); await expect(page.locator('.palette-item', { hasText: '柔性工作台' })).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.locator('.palette')).toBeHidden(); }); });