Skip to content

Commit bc31c57

Browse files
test(e2e): cover add-MCP-server flow via discovery → custom form (BerriAI#29070)
* test(e2e): cover add-MCP-server flow via discovery → custom form The "Add MCP server" manual-QA step was uncovered. This adds a test that opens the discovery modal, jumps into the custom-server form, fills name + Streamable HTTP transport + a placeholder URL + None auth, submits, and verifies both the success toast and the new row. * test(e2e): apply greptile fixes to MCP add-server test - Anchor the auth-type Select via its enclosing Collapse panel ("Authentication") instead of the placeholder text. The Form.Item has no label prop, so the previous `hasText: /auth type/i` filter was matching via "Select auth type" placeholder copy — fragile. - Document the intentional lack of teardown, matching the pattern used in addModel.spec.ts: the e2e runner discards the DB per invocation. Addresses Greptile P2s on PR BerriAI#29070. * test(e2e): scope MCP row assertion to the servers table Scope the post-create row lookup to `table tbody` so the form modal's `server_name` input — which still holds the timestamped value during its close animation — can't satisfy the assertion before the server actually lands in the list. * docs(e2e): note MCP coverage scope and link to tracker This spec only smoke-tests the happy-path Streamable HTTP + None auth flow. Add a top-of-file comment pointing at E2E_COVERAGE.md so future contributors can see what's still uncovered (other transports, all auth types, edit/delete, BYOK, tool list/call, access groups).
1 parent 157e7a0 commit bc31c57

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { test, expect } from "@playwright/test";
2+
import { ADMIN_STORAGE_PATH } from "../../constants";
3+
import { navigateToPage } from "../../helpers/navigation";
4+
import { Page } from "../../fixtures/pages";
5+
6+
// Coverage scope: only the happy-path Streamable HTTP + None auth create flow.
7+
// See E2E_COVERAGE.md (#29 row) for the full list of uncovered MCP surfaces
8+
// — SSE / stdio / OpenAPI transports, API Key / Bearer / OAuth2 / Basic / Token
9+
// / AWS SigV4 auth, edit/delete, BYOK credentials, tool list/call (needs a real
10+
// or mocked MCP server in the e2e fixture stack), and access-group permissions.
11+
test.describe("MCP Servers", () => {
12+
test.use({ storageState: ADMIN_STORAGE_PATH });
13+
14+
test("Add a custom MCP server via the discovery → custom form", async ({ page }) => {
15+
await navigateToPage(page, Page.McpServers);
16+
17+
// Open the discovery modal, then drop into the custom-server form
18+
await page.getByRole("button", { name: /Add New MCP Server/i }).click();
19+
const discovery = page.locator(".ant-modal:visible").filter({ hasText: "Add MCP Server" });
20+
await expect(discovery).toBeVisible({ timeout: 5_000 });
21+
await discovery.getByRole("button", { name: /Custom Server/i }).click();
22+
23+
const formModal = page.locator(".ant-modal:visible").filter({ hasText: "MCP Server Name" });
24+
await expect(formModal).toBeVisible({ timeout: 5_000 });
25+
26+
// Name — no spaces or hyphens per validateMCPServerName
27+
const uniqueName = `e2e_mcp_${Date.now()}`;
28+
await formModal.locator('input[id="server_name"]').fill(uniqueName);
29+
30+
// Transport: Streamable HTTP — the only value the proxy actually accepts is "http"
31+
const transportField = formModal.locator(".ant-form-item", { hasText: "Transport Type" });
32+
await transportField.locator(".ant-select").click();
33+
await page.locator(".ant-select-dropdown:visible").getByText("Streamable HTTP").click();
34+
35+
// URL — use a fake URL; the form just persists it, it doesn't have to be reachable
36+
await formModal.locator('input[id="url"]').fill("https://e2e-fake-mcp.test.local/mcp");
37+
38+
// Authentication: None
39+
// The auth_type Form.Item has no label prop (create_mcp_server.tsx:795), so
40+
// it can't be anchored by label text. Scope via the enclosing Collapse
41+
// panel ("Authentication") instead — that anchor is stable even if the
42+
// placeholder copy changes.
43+
const authSection = formModal.locator(".ant-collapse-item", { hasText: /^Authentication/ });
44+
const authField = authSection.locator(".ant-form-item").first();
45+
await authField.locator(".ant-select").click();
46+
await page.locator(".ant-select-dropdown:visible").getByText("None", { exact: true }).click();
47+
48+
// Submit
49+
await formModal.getByRole("button", { name: /^Add MCP Server$/ }).click();
50+
51+
// No teardown needed — the e2e runner spins up a fresh DB per invocation.
52+
53+
// Success toast and the new row in the table. Scope the row lookup to
54+
// the MCP servers table so the form modal's `server_name` input — which
55+
// still holds the timestamped value during its close animation — can't
56+
// satisfy the assertion before the server actually lands in the list.
57+
await expect(page.getByText("MCP Server created successfully").first())
58+
.toBeVisible({ timeout: 15_000 });
59+
await expect(page.locator("table tbody").getByText(uniqueName).first())
60+
.toBeVisible({ timeout: 10_000 });
61+
});
62+
});

0 commit comments

Comments
 (0)