test(e2e): cover add-MCP-server flow via discovery → custom form (#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 #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).
This commit is contained in:
parent
157e7a0f20
commit
bc31c570f0
62
ui/litellm-dashboard/e2e_tests/tests/mcp/mcpServers.spec.ts
Normal file
62
ui/litellm-dashboard/e2e_tests/tests/mcp/mcpServers.spec.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { ADMIN_STORAGE_PATH } from "../../constants";
|
||||
import { navigateToPage } from "../../helpers/navigation";
|
||||
import { Page } from "../../fixtures/pages";
|
||||
|
||||
// Coverage scope: only the happy-path Streamable HTTP + None auth create flow.
|
||||
// See E2E_COVERAGE.md (#29 row) for the full list of uncovered MCP surfaces
|
||||
// — SSE / stdio / OpenAPI transports, API Key / Bearer / OAuth2 / Basic / Token
|
||||
// / AWS SigV4 auth, edit/delete, BYOK credentials, tool list/call (needs a real
|
||||
// or mocked MCP server in the e2e fixture stack), and access-group permissions.
|
||||
test.describe("MCP Servers", () => {
|
||||
test.use({ storageState: ADMIN_STORAGE_PATH });
|
||||
|
||||
test("Add a custom MCP server via the discovery → custom form", async ({ page }) => {
|
||||
await navigateToPage(page, Page.McpServers);
|
||||
|
||||
// Open the discovery modal, then drop into the custom-server form
|
||||
await page.getByRole("button", { name: /Add New MCP Server/i }).click();
|
||||
const discovery = page.locator(".ant-modal:visible").filter({ hasText: "Add MCP Server" });
|
||||
await expect(discovery).toBeVisible({ timeout: 5_000 });
|
||||
await discovery.getByRole("button", { name: /Custom Server/i }).click();
|
||||
|
||||
const formModal = page.locator(".ant-modal:visible").filter({ hasText: "MCP Server Name" });
|
||||
await expect(formModal).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
// Name — no spaces or hyphens per validateMCPServerName
|
||||
const uniqueName = `e2e_mcp_${Date.now()}`;
|
||||
await formModal.locator('input[id="server_name"]').fill(uniqueName);
|
||||
|
||||
// Transport: Streamable HTTP — the only value the proxy actually accepts is "http"
|
||||
const transportField = formModal.locator(".ant-form-item", { hasText: "Transport Type" });
|
||||
await transportField.locator(".ant-select").click();
|
||||
await page.locator(".ant-select-dropdown:visible").getByText("Streamable HTTP").click();
|
||||
|
||||
// URL — use a fake URL; the form just persists it, it doesn't have to be reachable
|
||||
await formModal.locator('input[id="url"]').fill("https://e2e-fake-mcp.test.local/mcp");
|
||||
|
||||
// Authentication: None
|
||||
// The auth_type Form.Item has no label prop (create_mcp_server.tsx:795), so
|
||||
// it can't be anchored by label text. Scope via the enclosing Collapse
|
||||
// panel ("Authentication") instead — that anchor is stable even if the
|
||||
// placeholder copy changes.
|
||||
const authSection = formModal.locator(".ant-collapse-item", { hasText: /^Authentication/ });
|
||||
const authField = authSection.locator(".ant-form-item").first();
|
||||
await authField.locator(".ant-select").click();
|
||||
await page.locator(".ant-select-dropdown:visible").getByText("None", { exact: true }).click();
|
||||
|
||||
// Submit
|
||||
await formModal.getByRole("button", { name: /^Add MCP Server$/ }).click();
|
||||
|
||||
// No teardown needed — the e2e runner spins up a fresh DB per invocation.
|
||||
|
||||
// Success toast and the new row in the table. Scope the row lookup to
|
||||
// the MCP servers table 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.
|
||||
await expect(page.getByText("MCP Server created successfully").first())
|
||||
.toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.locator("table tbody").getByText(uniqueName).first())
|
||||
.toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user