Files
diegosouzapw--omniroute/tests/e2e/providers-bailian-coding-plan.spec.ts
2026-07-13 13:39:12 +08:00

241 lines
8.2 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { gotoDashboardRoute } from "./helpers/dashboardAuth";
const DEFAULT_BAILIAN_URL = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1";
test.describe("Bailian Coding Plan Provider", () => {
test.describe.configure({ mode: "serial" });
test("default URL visible and editable in Add API Key modal", async ({ page }) => {
const capturedPayloads: { createProvider?: Record<string, unknown> } = {};
await page.route("**/api/providers", async (route) => {
const method = route.request().method();
if (method === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ connections: [] }),
});
return;
}
if (method === "POST") {
const payload = route.request().postDataJSON();
capturedPayloads.createProvider = payload;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
connection: {
id: "conn-bailian-test",
provider: "bailian-coding-plan",
name: payload.name || "Test Connection",
testStatus: "active",
providerSpecificData: payload.providerSpecificData,
},
}),
});
return;
}
await route.fulfill({ status: 405 });
});
await page.route("**/api/providers/validate", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ valid: true }),
});
});
await page.route("**/api/provider-nodes", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ nodes: [] }),
});
});
await gotoDashboardRoute(page, "/dashboard/providers/bailian-coding-plan");
await page.waitForLoadState("domcontentloaded");
// Dismiss any pre-existing dialog/overlay that may appear on page load
const preExistingDialog = page.getByRole("dialog").first();
if (await preExistingDialog.isVisible({ timeout: 2000 }).catch(() => false)) {
await page.keyboard.press("Escape");
await preExistingDialog.waitFor({ state: "hidden", timeout: 3000 }).catch(() => {});
}
const addKeyButton = page.getByRole("button", {
name: /add.*api.*key|add.*key|add.*connection|connect|adicionar.*chave/i,
});
// Wait for the button to appear instead of immediately checking visibility
await addKeyButton.first().waitFor({ state: "visible", timeout: 15000 });
await expect(addKeyButton.first()).toBeEnabled({ timeout: 5000 });
await addKeyButton.first().click();
const dialog = page.getByRole("dialog").first();
await expect(dialog).toBeVisible({ timeout: 10000 });
const baseUrlInput = dialog
.getByLabel(/base.*url/i)
.or(dialog.locator("input").filter({ has: page.locator("..").getByText(/base.*url/i) }));
await expect(baseUrlInput).toBeVisible({ timeout: 15000 });
const inputValue = await baseUrlInput.inputValue();
expect(inputValue).toBe(DEFAULT_BAILIAN_URL);
const nameInput = dialog.getByLabel(/name/i).or(dialog.locator("input").first());
await nameInput.fill("Test Bailian Connection");
const apiKeyInput = dialog
.getByLabel(/api.*key/i)
.or(dialog.locator('input[type="password"]').first());
await apiKeyInput.fill("test-api-key-12345");
const customUrl = "https://custom.example.com/anthropic/v1";
await baseUrlInput.fill(customUrl);
const saveButton = dialog
.getByRole("button", {
name: /save|add|create|connect/i,
})
.last();
await expect(saveButton).toBeEnabled({ timeout: 15000 });
await saveButton.click();
await expect(dialog)
.toBeHidden({ timeout: 10000 })
.catch(() => undefined);
expect(capturedPayloads.createProvider).toBeDefined();
const payload = capturedPayloads.createProvider;
expect(payload?.providerSpecificData).toBeDefined();
expect((payload?.providerSpecificData as Record<string, unknown>)?.baseUrl).toBe(customUrl);
});
test("invalid URL blocks save with validation error", async ({ page }) => {
let createAttempted = false;
await page.route("**/api/providers", async (route) => {
const method = route.request().method();
if (method === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ connections: [] }),
});
return;
}
if (method === "POST") {
createAttempted = true;
await route.fulfill({
status: 400,
contentType: "application/json",
body: JSON.stringify({
message: "Invalid request",
details: [
{
field: "providerSpecificData.baseUrl",
message: "providerSpecificData.baseUrl must be a valid URL",
},
],
}),
});
return;
}
await route.fulfill({ status: 405 });
});
await page.route("**/api/providers/validate", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ valid: true }),
});
});
await page.route("**/api/provider-nodes", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ nodes: [] }),
});
});
await gotoDashboardRoute(page, "/dashboard/providers/bailian-coding-plan");
await page.waitForLoadState("domcontentloaded");
// Dismiss any pre-existing dialog/overlay that may appear on page load
const preExistingDialog = page.getByRole("dialog").first();
if (await preExistingDialog.isVisible({ timeout: 2000 }).catch(() => false)) {
await page.keyboard.press("Escape");
await preExistingDialog.waitFor({ state: "hidden", timeout: 3000 }).catch(() => {});
}
const addKeyButton = page.getByRole("button", {
name: /add.*api.*key|add.*key|add.*connection|connect|adicionar.*chave/i,
});
// Wait for the button to appear instead of immediately checking visibility
await addKeyButton.first().waitFor({ state: "visible", timeout: 15000 });
await expect(addKeyButton.first()).toBeEnabled({ timeout: 5000 });
await addKeyButton.first().click();
const dialog = page.getByRole("dialog").first();
await expect(dialog).toBeVisible({ timeout: 10000 });
const baseUrlInput = dialog
.getByLabel(/base.*url/i)
.or(dialog.locator("input").filter({ has: page.locator("..").getByText(/base.*url/i) }));
await expect(baseUrlInput).toBeVisible({ timeout: 15000 });
const nameInput = dialog.getByLabel(/name/i).or(dialog.locator("input").first());
await nameInput.fill("Test Invalid URL Connection");
const apiKeyInput = dialog
.getByLabel(/api.*key/i)
.or(dialog.locator('input[type="password"]').first());
await apiKeyInput.fill("test-api-key-12345");
await baseUrlInput.fill("not-a-url");
const saveButton = dialog
.getByRole("button", {
name: /save|add|create|connect/i,
})
.last();
await saveButton.click();
// Wait for React to process the validation and re-render
await page.waitForTimeout(1000);
// Check for the validation error scoped to the dialog to avoid strict-mode
// violations from broad selectors matching unrelated page elements.
const errorTextLocator = dialog
.locator("text=/invalid.*url|url.*invalid|must be a valid url|must use http/i")
.first();
const errorClassLocator = dialog.locator(".text-red-500").first();
let errorVisible =
(await errorTextLocator.isVisible().catch(() => false)) ||
(await errorClassLocator.isVisible().catch(() => false));
if (!errorVisible) {
// Fallback: if the dialog stays open after clicking save, it means the
// client-side validation prevented submission (which is the desired behavior).
await page.waitForTimeout(2000);
errorVisible = await dialog.isVisible().catch(() => false);
}
expect(errorVisible).toBe(true);
expect(createAttempted).toBe(false);
});
});