140 lines
6.5 KiB
TypeScript
140 lines
6.5 KiB
TypeScript
|
|
// ============================================================
|
|||
|
|
// 计划员界面不出现开发/运维入口(矩阵 118 · 前端 E2E)
|
|||
|
|
// 规则:计划员在什么场景下会点它?答不上来的入口不进主界面。
|
|||
|
|
// 覆盖:多智能体编排 / 重生中心 / 算法异步任务与 MCP 插件 / 微观审计回放 /
|
|||
|
|
// 开发者工具菜单。默认隐藏,?devtools=1 时恢复(排查用)。
|
|||
|
|
// 全部离线:/api/** 一律本地桩,不连现场后端。
|
|||
|
|
// ============================================================
|
|||
|
|
import { test, expect, type Page } from '@playwright/test';
|
|||
|
|
import os from 'node:os';
|
|||
|
|
import path from 'node:path';
|
|||
|
|
|
|||
|
|
const SHOT_DIR = process.env.E2E_SHOT_DIR || os.tmpdir();
|
|||
|
|
|
|||
|
|
const json = (body: unknown, status = 200) => ({
|
|||
|
|
status, contentType: 'application/json', body: JSON.stringify(body),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 规划员壳层所需的最小桩:登录态 + 一个活动会话 + 一个可选中 Skill,其余一律 404。
|
|||
|
|
* 单个分发 handler 处理全部后端路径:dev 模式下源码路径也含 /src/api/,
|
|||
|
|
* 多 handler 会互相抢占,这里按 pathname 精确分发。
|
|||
|
|
*/
|
|||
|
|
async function stubApi(page: Page): Promise<void> {
|
|||
|
|
await page.route((url: URL) => url.pathname.startsWith('/api/'), route => {
|
|||
|
|
const path = new URL(route.request().url()).pathname;
|
|||
|
|
switch (path) {
|
|||
|
|
case '/api/health':
|
|||
|
|
return route.fulfill(json({ interfaceVersion: '1.0', authProvider: 'local', licenseProvider: 'local' }));
|
|||
|
|
case '/api/auth/me':
|
|||
|
|
return route.fulfill(json({
|
|||
|
|
user: {
|
|||
|
|
user_id: 'user-1', username: 'e2e', fullname: 'E2E', tenant_uuid: 'tenant-1',
|
|||
|
|
roles: ['admin'], auth_kind: 'user',
|
|||
|
|
},
|
|||
|
|
provider: 'local',
|
|||
|
|
license: null,
|
|||
|
|
}));
|
|||
|
|
case '/api/features':
|
|||
|
|
return route.fulfill(json({ version: 1, source: 'default', path: '', error: null, unknown: [], features: {} }));
|
|||
|
|
case '/api/workspace':
|
|||
|
|
return route.fulfill(json({
|
|||
|
|
projects: [],
|
|||
|
|
sessions: [{
|
|||
|
|
id: 'session-1', projectId: '__personal__', title: 'E2E', status: 'running',
|
|||
|
|
updatedAt: '2026-09-16 10:00:00', scope: 'personal',
|
|||
|
|
}],
|
|||
|
|
files: [],
|
|||
|
|
messages: { 'session-1': [] },
|
|||
|
|
activeProjectId: '__personal__',
|
|||
|
|
activeSessionId: 'session-1',
|
|||
|
|
}));
|
|||
|
|
case '/api/skills':
|
|||
|
|
return route.fulfill(json({
|
|||
|
|
skills: [{ skill_id: 'algo.demo', name: '示例算法', track: 'flex', enabled: true }],
|
|||
|
|
skillsDir: '',
|
|||
|
|
mode: 'web',
|
|||
|
|
}));
|
|||
|
|
case '/api/skills/health':
|
|||
|
|
return route.fulfill(json({ health: [] }));
|
|||
|
|
default:
|
|||
|
|
return route.fulfill(json({ detail: 'e2e stub' }, 404));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 打开设置覆盖层并进入指定分区 */
|
|||
|
|
async function openSettingsSection(page: Page, label: string): Promise<void> {
|
|||
|
|
await page.locator('.app-menubar > button').filter({ hasText: '设置' }).first().click();
|
|||
|
|
await expect(page.locator('.gov-shell')).toBeVisible();
|
|||
|
|
await page.locator('.gov-nav-item').filter({ hasText: label }).first().click();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test('计划员默认界面不含开发入口', async ({ page }) => {
|
|||
|
|
await stubApi(page);
|
|||
|
|
await page.goto('/');
|
|||
|
|
await expect(page.locator('.right-rail')).toBeVisible();
|
|||
|
|
|
|||
|
|
// 右侧工具条:不出现多智能体编排
|
|||
|
|
await expect(page.locator('button[data-tip="多智能体编排"]')).toHaveCount(0);
|
|||
|
|
// 生产订单 / 制造主数据 等计划员入口仍在(反向哨兵,避免误删)
|
|||
|
|
await expect(page.locator('button[data-tip="生产订单"]')).toBeVisible();
|
|||
|
|
await page.screenshot({ path: path.join(SHOT_DIR, 'planner-view-main.png'), fullPage: false });
|
|||
|
|
|
|||
|
|
// 顶部视图菜单:不出现开发者工具
|
|||
|
|
await page.locator('.app-menu-top').filter({ hasText: '视图' }).click();
|
|||
|
|
await expect(page.locator('.app-menu-dropdown')).toBeVisible();
|
|||
|
|
await expect(page.locator('.app-menu-dropdown')).not.toContainText('开发者工具');
|
|||
|
|
await page.keyboard.press('Escape');
|
|||
|
|
|
|||
|
|
// 命令面板:不出现重生中心
|
|||
|
|
await page.keyboard.press('Control+k');
|
|||
|
|
await expect(page.locator('.palette')).toBeVisible();
|
|||
|
|
await expect(page.locator('.palette')).not.toContainText('重生中心');
|
|||
|
|
await expect(page.locator('.palette')).toContainText('生产订单');
|
|||
|
|
await page.keyboard.press('Escape');
|
|||
|
|
await expect(page.locator('.palette')).toBeHidden();
|
|||
|
|
|
|||
|
|
// 计划追溯:不出现微观层(Plan 节点 / ALGO_RUN / 审计证据)
|
|||
|
|
await page.locator('.right-rail button[data-tip="排产结构"]').click();
|
|||
|
|
await expect(page.locator('.timeline')).toBeVisible();
|
|||
|
|
await expect(page.locator('.timeline')).not.toContainText('微观层');
|
|||
|
|
await expect(page.locator('.timeline')).not.toContainText('ALGO_RUN');
|
|||
|
|
await page.screenshot({ path: path.join(SHOT_DIR, 'planner-view-timeline.png'), fullPage: false });
|
|||
|
|
|
|||
|
|
// 设置:不出现重生中心分组,算法 Skill 下不出现异步任务
|
|||
|
|
await openSettingsSection(page, '算法 Skill');
|
|||
|
|
await expect(page.getByRole('tab', { name: '概览' })).toBeVisible();
|
|||
|
|
await expect(page.getByRole('tab', { name: '异步任务' })).toHaveCount(0);
|
|||
|
|
await expect(page.getByRole('tab', { name: 'MCP 插件' })).toHaveCount(0);
|
|||
|
|
await expect(page.locator('.gov-nav')).not.toContainText('重生中心');
|
|||
|
|
await expect(page.locator('.gov-nav')).not.toContainText('黄金测试');
|
|||
|
|
|
|||
|
|
await page.screenshot({ path: path.join(SHOT_DIR, 'planner-view-default.png'), fullPage: false });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('?devtools=1 时开发入口恢复,排查用', async ({ page }) => {
|
|||
|
|
await stubApi(page);
|
|||
|
|
await page.goto('/?devtools=1');
|
|||
|
|
await expect(page.locator('.right-rail')).toBeVisible();
|
|||
|
|
|
|||
|
|
await expect(page.locator('button[data-tip="多智能体编排"]')).toBeVisible();
|
|||
|
|
await page.locator('.app-menu-top').filter({ hasText: '视图' }).click();
|
|||
|
|
await expect(page.locator('.app-menu-dropdown')).toContainText('开发者工具');
|
|||
|
|
await page.keyboard.press('Escape');
|
|||
|
|
|
|||
|
|
await page.keyboard.press('Control+k');
|
|||
|
|
await expect(page.locator('.palette')).toContainText('重生中心');
|
|||
|
|
await page.keyboard.press('Escape');
|
|||
|
|
|
|||
|
|
await page.locator('.right-rail button[data-tip="排产结构"]').click();
|
|||
|
|
await expect(page.locator('.timeline')).toContainText('微观层');
|
|||
|
|
|
|||
|
|
await openSettingsSection(page, '算法 Skill');
|
|||
|
|
await expect(page.getByRole('tab', { name: '异步任务' })).toBeVisible();
|
|||
|
|
await expect(page.locator('.gov-nav')).toContainText('重生中心');
|
|||
|
|
await expect(page.locator('.gov-nav')).toContainText('黄金测试');
|
|||
|
|
|
|||
|
|
await page.screenshot({ path: path.join(SHOT_DIR, 'planner-view-devtools.png'), fullPage: false });
|
|||
|
|
});
|