From 7cae5dc08a0412bb0a1e33025bdabda0aba9bcc3 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Wed, 27 May 2026 14:45:22 -0700 Subject: [PATCH] test(ui): e2e cover team model edit + admin identity in navbar (#28652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(ui): e2e cover team model edit + admin identity in navbar Adds two Playwright tests as part of the manual-QA → e2e migration: "Edit team model selection" exercises the Settings tab Models multi-select + Save Changes flow on a seeded team, and the existing login test now opens the User dropdown and asserts the role and User ID render — guarding against regressions where login succeeds but the auth context is empty. Resolves LIT-3093 * test(ui): restore seeded models in team-edit test so retries don't fail The 'Edit team model selection' test removed fake-anthropic-claude from E2E_TEAM_CRUD_ID without restoring it. CI runs with retries: 2 and the seed script runs once before the suite, so a flake on this test would fail the retry at the "tag is visible" assertion. Wrap the test in try/finally and restore the seeded models via /team/update before and after. * test(e2e): fail loudly if team/update restore call fails Surfaces the real cause when the master key is wrong or the proxy is unreachable, instead of silently leaving the team in a stale state and failing later on the visibility assertion. * fix(e2e): match navbar account button by aria-label, not non-existent "User" text The previous trigger filter (hasText: /^User$/) didn't match the rendered UserDropdown button — its text is the displayName ("Account" for the master-key admin, an email for SSO users), never "User". The evaluate call then timed out after 15s in CI. Use the stable aria-label prefix the component always emits, and click directly since the dropdown is configured trigger=["click"] (the synthetic hover was unnecessary). --- .../e2e_tests/tests/login/login.spec.ts | 17 +++++++ .../e2e_tests/tests/proxy-admin/teams.spec.ts | 45 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts index 5d4b250844..994d211cc1 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts @@ -10,4 +10,21 @@ test("user can log in", async ({ page }) => { await expect(loginButton).toBeEnabled(); await loginButton.click(); await expect(page.getByText("Virtual Keys")).toBeVisible(); + + // Match the navbar account button by its stable aria-label (UserDropdown.tsx + // emits "Account menu — — signed in as "). Earlier this used + // `hasText: /^User$/`, which never matched the rendered button (text is + // displayName = "Account" for the master-key admin), so the trigger evaluate + // would time out in CI. + const userTrigger = page.locator('button[aria-label^="Account menu"]').first(); + await userTrigger.click(); + + // Filter by the popupRender wrapper class to disambiguate from other + // ant-dropdown popups. + const popup = page.locator(".ant-dropdown:visible").filter({ + has: page.locator(".bg-white.rounded-lg.shadow-lg"), + }).first(); + await expect(popup).toBeVisible({ timeout: 5_000 }); + await expect(popup.getByText("Admin", { exact: true })).toBeVisible({ timeout: 5_000 }); + await expect(popup.getByText("default_user_id", { exact: true })).toBeVisible({ timeout: 5_000 }); }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts index a1864b22a4..6f6e837339 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts @@ -131,4 +131,49 @@ test.describe("Proxy Admin - Teams", () => { await expect(page.getByText(/updated|success/i).first()).toBeVisible({ timeout: 10_000 }); }); + + test("Edit team model selection", async ({ page, request }) => { + // Restore the seeded models via API in case a prior run (or a CI retry) + // left this team mutated — the assertion below requires fake-anthropic-claude + // to be present. + const masterKey = process.env.LITELLM_MASTER_KEY || "sk-1234"; + const seededModels = ["fake-openai-gpt-4", "fake-anthropic-claude"]; + const restore = async () => { + const res = await request.post("http://localhost:4000/team/update", { + headers: { Authorization: `Bearer ${masterKey}` }, + data: { team_id: E2E_TEAM_CRUD_ID, models: seededModels }, + }); + expect(res.ok(), `restore failed: ${res.status()} ${await res.text()}`).toBeTruthy(); + }; + await restore(); + + try { + await navigateToPage(page, Page.Teams); + await dismissFeedbackPopup(page); + + await clickTeamId(page, E2E_TEAM_CRUD_ID); + + await page.getByRole("tab", { name: "Settings" }).click(); + await page.getByRole("button", { name: "Edit Settings" }).click(); + + // Remove the anthropic tag — other tests against this team use "All Team + // Models" so they pick up whatever remains. + const modelsSelect = page.locator("[data-testid='models-select']"); + await expect(modelsSelect).toBeVisible({ timeout: 10_000 }); + + const anthropicTag = modelsSelect + .locator(".ant-select-selection-item") + .filter({ hasText: "fake-anthropic-claude" }); + await expect(anthropicTag).toBeVisible({ timeout: 5_000 }); + await anthropicTag.locator(".ant-select-selection-item-remove").click(); + + await page.getByRole("button", { name: "Save Changes" }).click(); + + await expect(page.getByText(/Team settings updated|updated successfully/i).first()) + .toBeVisible({ timeout: 10_000 }); + } finally { + // Leave the team in its seeded state for any subsequent test or rerun. + await restore(); + } + }); });