aps-agent/apps/web/e2e/core-journey.spec.ts

98 lines
5.3 KiB
TypeScript
Raw Normal View History

// ============================================================
// 核心用户路径 E2E 冒烟(矩阵 118 · 前端 E2E)
// 登录(JMS)→ 跑一版排产(柔性)→ 结果块出现 → 视口切换(世界对比/柔性工作台)→ 命令面板
// 全部为中文断言;依赖后端(默认 127.0.0.1:8003)+ 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 || 'platform',
username: process.env.E2E_JMS_USER || 'zhangzhen',
password: process.env.E2E_JMS_PASSWORD || 'sz@zz@1116',
};
async function login(page: Page): Promise<void> {
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(/张振|zhangzhen/);
}
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();
});
});