deer-flow/frontend/tests/e2e/capability-center.spec.ts
Wenchao An a246c928e9
feat(frontend): move capability management out of Settings (#5468)
* feat(frontend): move capability management out of settings

* fix(frontend): address capability center review feedback
2026-09-16 18:39:24 +08:00

263 lines
8.8 KiB
TypeScript
Raw 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.

import { mkdir } from "node:fs/promises";
import path from "node:path";
import { expect, test, type Page } from "@playwright/test";
import { mockLangGraphAPI } from "./utils/mock-api";
const skills = [
{
name: "deep-research",
description:
"Search, cross-check sources, and write a detailed research report.",
},
{
name: "data-analysis",
description: "Analyze structured data and create charts.",
},
{
name: "academic-paper-review",
description:
"Review research papers, methods, contributions, and limitations.",
},
{
name: "ppt-generation",
description: "Create presentations from ideas and reference materials.",
},
{
name: "frontend-design",
description: "Build polished frontend interfaces.",
},
{
name: "image-generation",
description: "Generate images from a written description.",
},
{
name: "podcast-generation",
description: "Create podcast scripts and audio.",
},
{
name: "skill-creator",
description: "Create reusable skills for new tasks.",
},
{
name: "skill-reviewer",
description: "Review skill quality and report potential issues.",
},
].map((skill) => ({
...skill,
category: "public",
enabled: true,
license: "MIT",
}));
async function mockCatalog(page: Page) {
mockLangGraphAPI(page, { skills, threads: [] });
await page.route("**/api/mcp/config", (route) =>
route.fulfill({
json: {
mcp_servers: {
GitHub: {
description:
"搜索代码与仓库,查看 Issue 和 Pull Request协助推进开发工作。",
enabled: true,
type: "http",
url: "https://example.test/github",
},
Notion: {
description: "搜索工作空间里的笔记和文档,整理资料,创建新的页面。",
enabled: true,
type: "http",
url: "https://example.test/notion",
},
"Brave Search": {
description: "搜索互联网上的信息,为研究、写作和决策补充最新资料。",
enabled: true,
command: "example-search",
},
Filesystem: {
description: "访问已授权的文件夹,读取文件内容并整理本地工作资料。",
enabled: true,
command: "example-files",
},
PostgreSQL: {
description: "查询数据库中的业务数据,探索表结构,辅助数据分析。",
enabled: false,
command: "example-database",
},
},
},
}),
);
}
async function screenshot(page: Page, name: string) {
const directory = process.env.CAPABILITY_SCREENSHOT_DIR;
if (!directory) return;
await mkdir(directory, { recursive: true });
await page.screenshot({ path: path.join(directory, name), fullPage: true });
}
test("catalog navigation, search, details, and migrated settings", async ({
page,
baseURL,
}) => {
test.setTimeout(90_000);
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.setViewportSize({ width: 1512, height: 1060 });
await page
.context()
.addCookies([{ name: "locale", value: "zh-CN", url: baseURL! }]);
await mockCatalog(page);
await page.goto("/workspace/capabilities");
await expect(
page.getByRole("heading", { name: "能力中心", exact: true }),
).toBeVisible();
await expect(page.locator("article")).toHaveCount(6);
await expect(page.locator("a[href='/workspace/capabilities']")).toBeVisible();
await screenshot(page, "capability-center-plugins.png");
await page
.getByRole("textbox", { name: "搜索插件名称或用途" })
.fill("Notion");
await expect(page.locator("article")).toHaveCount(1);
await page.getByRole("button", { name: "编辑 Notion", exact: true }).click();
await expect(page.getByRole("dialog")).toBeVisible();
await expect(
page.getByRole("textbox", { name: "MCP 服务器 JSON 定义" }),
).toHaveValue(/example.test\/notion/);
await page.keyboard.press("Escape");
await page.getByRole("tab", { name: "技能", exact: true }).click();
await expect(page.locator("article")).toHaveCount(9);
await expect(
page.getByRole("button", { name: "查看详情 深度研究", exact: true }),
).toBeVisible();
await page.setViewportSize({ width: 1512, height: 1270 });
await screenshot(page, "capability-center-skills.png");
await page
.getByRole("button", { name: "查看详情 深度研究", exact: true })
.click();
await expect(
page.getByRole("dialog").getByText(skills[0]!.description),
).toBeVisible();
await page.keyboard.press("Escape");
await page.getByRole("tab", { name: "社区", exact: true }).click();
await expect(page.getByText("从社区带来新的技能")).toBeVisible();
await page.goto("/workspace/capabilities?settings=appearance");
const settings = page.getByRole("dialog", { name: "设置", exact: true });
await expect(settings).toBeVisible();
for (const name of ["工具", "集成", "技能"]) {
await expect(
settings.getByRole("button", { name, exact: true }),
).toHaveCount(0);
}
expect(errors).toEqual([]);
});
test("the skills catalog fits a mobile viewport", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await mockCatalog(page);
await page.goto("/workspace/capabilities?tab=skills");
await expect(page).toHaveURL(/workspace\/capabilities\?tab=skills/);
await expect(page.locator("article")).toHaveCount(9);
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= innerWidth,
),
).toBe(true);
await page
.getByRole("button", { name: "Toggle Sidebar", exact: true })
.click();
await expect(
page.getByRole("link", { name: "Capability Center" }),
).toBeVisible();
});
test("MCP access errors preserve the independently available Lark integration", async ({
page,
}) => {
await mockCatalog(page);
await page.route("**/api/mcp/config", (route) =>
route.fulfill({ status: 403, json: { detail: "Admin only" } }),
);
await page.goto("/workspace/capabilities");
await expect(
page.getByRole("alert").filter({ hasText: "Admin privileges" }),
).toBeVisible();
await expect(
page.locator("article").filter({ hasText: "Lark / Feishu" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Add MCP plugin" }),
).toHaveCount(0);
});
test("Community search gives feedback and clearing it restores import guidance", async ({
page,
}) => {
await mockCatalog(page);
await page.goto("/workspace/capabilities?tab=skills");
await expect(page.locator("article")).toHaveCount(9);
const search = page.getByRole("textbox", {
name: "Search skills by name or purpose",
});
await search.fill("nonexistent-query");
await expect(
page.getByText("No matches found", { exact: true }),
).toBeVisible();
await page.getByRole("tab", { name: "Community", exact: true }).click();
await expect(search).toHaveValue("nonexistent-query");
await expect(
page.getByText("No matches found", { exact: true }),
).toBeVisible();
await expect(
page.getByText("Bring a skill from the community", { exact: true }),
).toHaveCount(0);
await search.fill(" ");
await expect(
page.getByText("Bring a skill from the community", { exact: true }),
).toBeVisible();
await search.fill("");
await page.getByRole("tab", { name: "Built-in", exact: true }).click();
await expect(page.locator("article")).toHaveCount(9);
});
test("plugin filters remain usable after an MCP refetch fails", async ({
page,
}) => {
await mockCatalog(page);
let failRead = false;
await page.route("**/api/mcp/config", async (route) => {
if (route.request().method() === "PATCH") {
failRead = true;
return route.fulfill({ json: { mcp_servers: {} } });
}
if (failRead)
return route.fulfill({ status: 403, json: { detail: "Admin only" } });
return route.fallback();
});
await page.goto("/workspace/capabilities");
await expect(page.locator("article")).toHaveCount(6);
const installed = page.getByRole("tab", { name: "Installed", exact: true });
await installed.click();
await expect(page.locator("article")).toHaveCount(5);
await page
.getByRole("switch", { name: "Enabled GitHub", exact: true })
.click();
await expect(page.getByRole("alert")).toBeVisible();
await expect(installed).toHaveAttribute("aria-selected", "true");
await expect(
page.getByRole("button", { name: "Add MCP plugin" }),
).toHaveCount(0);
await page.getByRole("tab", { name: "All plugins", exact: true }).click();
await expect(
page.locator("article").filter({ hasText: "Lark / Feishu" }),
).toBeVisible();
await page
.getByRole("button", { name: "Configure Lark / Feishu", exact: true })
.click();
await expect(page.getByRole("dialog")).toBeVisible();
});