import { test, expect } from "@playwright/test"; import { ADMIN_STORAGE_PATH, E2E_DELETE_KEY_ALIAS, E2E_REGENERATE_KEY_ALIAS, E2E_UPDATE_LIMITS_KEY_ALIAS, E2E_INTERNAL_USER_KEY_ALIAS, E2E_TEAM_CRUD_ALIAS, } from "../../constants"; import { Page } from "../../fixtures/pages"; import { navigateToPage, dismissFeedbackPopup } from "../../helpers/navigation"; test.describe("Proxy Admin - Keys", () => { test.use({ storageState: ADMIN_STORAGE_PATH }); test("Create a key in a team", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); // Click "+ Create New Key" button await page.getByRole("button", { name: /Create New Key/i }).click(); // Wait for the key creation modal await expect(page.getByText("Key Ownership")).toBeVisible({ timeout: 10_000 }); // Fill key name (has data-testid="base-input" in the built UI) const keyName = `e2e-admin-key-${Date.now()}`; await page.getByTestId("base-input").fill(keyName); // Select team — the team dropdown has placeholder "Search or select a team" const teamSelect = page.locator(".ant-select", { hasText: "Search or select a team" }); await teamSelect.click(); await page.keyboard.type(E2E_TEAM_CRUD_ALIAS); await page.locator(".ant-select-dropdown:visible").getByText(E2E_TEAM_CRUD_ALIAS).first().click(); // Select models await page.locator(".ant-select-selection-overflow").click(); await page.locator(".ant-select-dropdown:visible").getByText("All Team Models").click(); await page.keyboard.press("Escape"); // Submit await page.getByRole("button", { name: "Create Key", exact: true }).click(); // Success shows "Save your Key" in a second dialog await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 }); await page.keyboard.press("Escape"); // Verify the new key appears in the table await expect(page.getByText(keyName)).toBeVisible({ timeout: 10_000 }); }); test("Regenerate key", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); // Key IDs are rendered as buttons in the table const keyRow = page.locator("tr", { hasText: E2E_REGENERATE_KEY_ALIAS }); await expect(keyRow).toBeVisible({ timeout: 10_000 }); await keyRow.locator("button").first().click(); await expect(page.getByText("Back to Keys")).toBeVisible({ timeout: 10_000 }); await page.getByRole("button", { name: "Regenerate Key" }).click(); // Scope to the modal — the Regenerate button has an icon whose aria-label // ("sync") is concatenated into the button's accessible name, and the // "Regenerate Key" button is still in the DOM behind the modal. const modal = page.locator(".ant-modal:visible"); await modal.getByRole("button", { name: /Regenerate/ }).click(); // Success view shows a Copy button in the footer (text varies between modal versions) await expect(modal.getByRole("button", { name: /Copy.*Key/ })).toBeVisible({ timeout: 20_000 }); }); test("Update key TPM and RPM limits", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); const keyRow = page.locator("tr", { hasText: E2E_UPDATE_LIMITS_KEY_ALIAS }); await expect(keyRow).toBeVisible({ timeout: 10_000 }); await keyRow.locator("button").first().click(); await expect(page.getByText("Back to Keys")).toBeVisible({ timeout: 10_000 }); await page.getByRole("tab", { name: "Settings" }).click(); await page.getByRole("button", { name: "Edit Settings" }).click(); await page.getByRole("spinbutton", { name: "TPM Limit" }).fill("123"); await page.getByRole("spinbutton", { name: "RPM Limit" }).fill("456"); await page.getByRole("button", { name: "Save Changes" }).click(); await expect(page.getByRole("paragraph").filter({ hasText: "TPM: 123" })).toBeVisible({ timeout: 10_000 }); await expect(page.getByRole("paragraph").filter({ hasText: "RPM: 456" })).toBeVisible({ timeout: 10_000 }); }); test("Delete key", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); const keyRow = page.locator("tr", { hasText: E2E_DELETE_KEY_ALIAS }); await expect(keyRow).toBeVisible({ timeout: 10_000 }); await keyRow.locator("button").first().click(); await expect(page.getByText("Back to Keys")).toBeVisible({ timeout: 10_000 }); await page.getByRole("button", { name: "Delete Key" }).click(); const modal = page.locator(".ant-modal:visible"); await expect(modal).toBeVisible({ timeout: 5_000 }); await modal.locator("input").fill(E2E_DELETE_KEY_ALIAS); const deleteButton = modal.getByRole("button", { name: "Delete", exact: true }); await expect(deleteButton).toBeEnabled(); await deleteButton.click(); await expect(page.getByText(/Key deleted/i).first()).toBeVisible({ timeout: 10_000 }); }); test("See internal user keys in team", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS)).toBeVisible({ timeout: 10_000 }); }); test("Create a key with All Proxy Models (no team)", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); await page.getByRole("button", { name: /Create New Key/i }).click(); await expect(page.getByText("Key Ownership")).toBeVisible({ timeout: 10_000 }); const keyName = `e2e-admin-allproxy-${Date.now()}`; await page.getByTestId("base-input").fill(keyName); // No team selection — leave team dropdown empty so the key is owned by the admin user // Select models — open the multi-select and pick the all-models meta-option. // The Create Key modal labels this "All Team Models" even when no team is selected // (see src/components/organisms/create_key_button.tsx:944), unlike the team/user // settings screens which use "All Proxy Models". await page.locator(".ant-select-selection-overflow").click(); await page.locator(".ant-select-dropdown:visible").getByText("All Team Models").click(); await page.keyboard.press("Escape"); await page.getByRole("button", { name: "Create Key", exact: true }).click(); await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 }); await page.keyboard.press("Escape"); await expect(page.getByText(keyName)).toBeVisible({ timeout: 10_000 }); }); test("Create a key with a specific proxy model (no team)", async ({ page }) => { await navigateToPage(page, Page.ApiKeys); await dismissFeedbackPopup(page); await page.getByRole("button", { name: /Create New Key/i }).click(); await expect(page.getByText("Key Ownership")).toBeVisible({ timeout: 10_000 }); const keyName = `e2e-admin-specific-${Date.now()}`; await page.getByTestId("base-input").fill(keyName); // Open the model multi-select and pick a single specific model. Use // getByRole("option", ...) to avoid the strict-mode collision between // the option container and its inner text node. const modelName = "fake-openai-gpt-4"; await page.locator(".ant-select-selection-overflow").click(); const option = page.locator(".ant-select-dropdown:visible").getByRole("option", { name: modelName, exact: true }); await option.waitFor({ state: "attached" }); // Dispatch the click via the DOM — antd's dropdown can render the option // off-viewport during the open animation, which trips Playwright's // visibility/stability checks. The click handler fires regardless. await option.evaluate((el: HTMLElement) => el.click()); await page.keyboard.press("Escape"); await page.getByRole("button", { name: "Create Key", exact: true }).click(); await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 }); // Grab the new key from the success modal (rendered inside a
) and
// verify it can call /chat/completions for the model it was scoped to.
// The mock LLM server (fixtures/mock_llm_server/server.py) replies with
// a fixed "This is a mock response." body.
const apiKey = (await page.locator(".ant-modal:visible pre").innerText()).trim();
expect(apiKey).toMatch(/^sk-/);
const response = await page.request.post("/chat/completions", {
headers: { Authorization: `Bearer ${apiKey}` },
data: {
model: modelName,
messages: [{ role: "user", content: "ping" }],
},
});
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.choices?.[0]?.message?.content).toBe("This is a mock response.");
await page.keyboard.press("Escape");
await expect(page.getByText(keyName)).toBeVisible({ timeout: 10_000 });
});
});