aps-agent/apps/web/e2e/planner-view-no-dev-tools.s...

140 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// 计划员界面不出现开发/运维入口(矩阵 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 });
});