Files
openagent/dashboard/tests/ai-providers.spec.ts
Thomas Marchand 3d0b4d19b7 Th0rgal/update branding (#32)
* feat: chroots

* wip

* Update workspace templates and Playwright tests

* Fix thinking panel close button not working during active thinking

The auto-show useEffect was including showThinkingPanel in its dependency
array, causing the panel to immediately reopen when closed since the state
change would trigger the effect while hasActiveThinking was still true.

Changed to use a ref to track previous state and only auto-show on
transition from inactive to active thinking.

* wip

* wip

* wip

* Cleanup web search tool and remove hardcoded OAuth credentials

* Ralph iteration 1: work in progress

* Ralph iteration 2: work in progress

* Ralph iteration 3: work in progress

* Ralph iteration 4: work in progress

* Ralph iteration 5: work in progress

* Ralph iteration 6: work in progress

* Ralph iteration 1: work in progress

* Ralph iteration 2: work in progress

* Ralph iteration 3: work in progress

* Ralph iteration 4: work in progress

* Ralph iteration 5: work in progress

* Ralph iteration 6: work in progress

* Ralph iteration 7: work in progress

* Ralph iteration 1: work in progress

* Ralph iteration 2: work in progress

* improve readme

* fix: remove unused file

* feat: hero screenshot

* Update README with cleaner vision and hero screenshot

Simplified the vision section with "what if" framing, removed
architecture diagram, added hero screenshot showing mission view.
2026-01-12 14:45:05 -08:00

347 lines
12 KiB
TypeScript

import { test, expect } from "@playwright/test";
// Run tests serially to avoid provider cleanup conflicts
test.describe.configure({ mode: 'serial' });
let apiAvailable = false;
test.describe("AI Providers", () => {
test.beforeEach(async ({ page, request }) => {
apiAvailable = false;
// Clean up any existing test providers first
try {
const response = await request.get("http://127.0.0.1:3000/api/ai/providers");
if (response.ok()) {
apiAvailable = true;
const providers = await response.json();
for (const provider of providers) {
if (provider.name.includes("Test")) {
await request.delete(`http://127.0.0.1:3000/api/ai/providers/${provider.id}`);
}
}
}
} catch {
// Ignore cleanup errors
}
// Clear localStorage and set local API URL
await page.goto("/settings");
await page.evaluate(() => {
localStorage.clear();
localStorage.setItem(
"settings",
JSON.stringify({ apiUrl: "http://127.0.0.1:3000", libraryRepo: "" })
);
});
// Reload to pick up new settings
await page.reload();
// Wait for the page to load
await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible();
// Wait for providers to load
await page.waitForTimeout(1000);
});
test.afterEach(async ({ request }) => {
if (!apiAvailable) return;
// Clean up any test providers created
try {
const response = await request.get("http://127.0.0.1:3000/api/ai/providers");
if (response.ok()) {
const providers = await response.json();
for (const provider of providers) {
if (provider.name.includes("Test")) {
await request.delete(`http://127.0.0.1:3000/api/ai/providers/${provider.id}`);
}
}
}
} catch {
// Ignore cleanup errors
}
});
test("shows AI Providers section", async ({ page }) => {
// Check the AI Providers section exists
await expect(page.getByRole("heading", { name: "AI Providers" })).toBeVisible({ timeout: 10000 });
await expect(page.getByText("Configure inference providers for OpenCode")).toBeVisible();
});
test("shows empty state when no providers configured", async ({ page }) => {
// Check for empty state message (may or may not be visible depending on existing providers)
const emptyState = page.locator("text=No providers configured");
const providerList = page.locator('[class*="rounded-lg border p-3"]');
// Either empty state or provider list should be visible
const isEmpty = await emptyState.isVisible().catch(() => false);
if (isEmpty) {
await expect(emptyState).toBeVisible();
await expect(
page.locator("text=Add an AI provider to enable inference capabilities")
).toBeVisible();
} else {
// Providers exist
await expect(providerList.first()).toBeVisible();
}
});
test("can open add provider modal", async ({ page }) => {
// Click the Add Provider button
await page.click("text=Add Provider");
// Check modal appears
await expect(page.getByRole("heading", { name: "Add Provider" })).toBeVisible();
});
test("provider list shows common providers", async ({ page }) => {
test.skip(!apiAvailable, 'API not available');
// Click the Add Provider button
await page.click("text=Add Provider");
// Should list common providers
await expect(page.getByRole("button", { name: "Anthropic" })).toBeVisible();
await expect(page.getByRole("button", { name: "OpenAI" })).toBeVisible();
});
test("shows OAuth options for Anthropic provider", async ({ page }) => {
test.skip(!apiAvailable, 'API not available');
// Open modal
await page.click("text=Add Provider");
// Select Anthropic
await page.getByRole("button", { name: "Anthropic" }).click();
// Should show auth methods
await expect(page.getByRole("heading", { name: /Connect Anthropic/i })).toBeVisible();
await expect(page.getByRole("button", { name: /Claude Pro\/Max/i })).toBeVisible();
await expect(page.getByRole("button", { name: /Enter API Key/i })).toBeVisible();
});
test("shows OAuth options for OpenAI provider", async ({ page }) => {
test.skip(!apiAvailable, 'API not available');
// Open modal
await page.click("text=Add Provider");
// Select OpenAI
await page.getByRole("button", { name: "OpenAI" }).click();
// Should show auth methods
await expect(page.getByRole("heading", { name: /Connect OpenAI/i })).toBeVisible();
await expect(
page.getByRole("button", { name: /ChatGPT Plus\/Pro/i })
).toBeVisible();
await expect(page.getByRole("button", { name: /Enter API Key/i })).toBeVisible();
});
test("can cancel add provider modal", async ({ page }) => {
// Open modal
await page.click("text=Add Provider");
await expect(page.getByRole("heading", { name: "Add Provider" })).toBeVisible();
// Close with Escape
await page.keyboard.press('Escape');
await expect(page.getByRole("heading", { name: "Add Provider" })).not.toBeVisible();
});
test("validates required fields when adding provider", async ({ page }) => {
test.skip(!apiAvailable, 'API not available');
// Open modal and pick OpenAI
await page.click("text=Add Provider");
await page.getByRole("button", { name: "OpenAI" }).click();
// Select API key method
await page.getByRole("button", { name: /Enter API Key/i }).click();
// Should show API key field
await expect(page.getByPlaceholder("sk-...")).toBeVisible();
const addButton = page.getByRole("button", { name: "Add Provider" });
await expect(addButton).toBeDisabled();
// Fill API key
await page.getByPlaceholder("sk-...").fill("sk-test-key");
await expect(addButton).toBeEnabled();
});
test("can create an API key provider", async ({ page, request }) => {
test.skip(!apiAvailable, 'API not available');
// Create provider via API directly for reliability
await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "openai",
name: "Test OpenAI Provider",
api_key: "sk-test-key-12345",
},
});
// Reload to see the new provider
await page.reload();
await page.waitForTimeout(1000);
// The new provider should appear in the list
await expect(page.getByText("Test OpenAI Provider")).toBeVisible({ timeout: 10000 });
});
test("can create an OAuth provider", async ({ page, request }) => {
test.skip(!apiAvailable, 'API not available');
// Create OAuth provider via API directly
await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "anthropic",
name: "Test Anthropic Provider",
},
});
// Reload to see the new provider
await page.reload();
await page.waitForTimeout(1000);
// The new provider should appear in the list
await expect(page.getByText("Test Anthropic Provider")).toBeVisible({ timeout: 10000 });
});
test("shows Connect button for providers needing auth", async ({ page, request }) => {
test.skip(!apiAvailable, 'API not available');
// Create OAuth provider via API
await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "anthropic",
name: "Auth Test Provider",
},
});
// Reload to see the new provider
await page.reload();
await page.waitForTimeout(1000);
// Should see the provider first
const providerRow = page.locator('div').filter({ hasText: "Auth Test Provider" }).filter({
has: page.locator('button[title="Connect"]'),
}).first();
await expect(providerRow).toBeVisible({ timeout: 10000 });
await providerRow.hover();
await expect(providerRow.locator('button[title="Connect"]')).toBeVisible();
});
test("can edit a provider", async ({ page, request }) => {
test.skip(!apiAvailable, 'API not available');
// Create provider via API
await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "openai",
name: "Edit Test Provider",
api_key: "sk-test-key-edit",
},
});
// Reload to see the new provider
await page.reload();
await page.waitForTimeout(1000);
const providerRow = page.locator('div').filter({ hasText: "Edit Test Provider" }).filter({
has: page.locator('button[title="Edit"]'),
}).first();
await expect(providerRow).toBeVisible({ timeout: 10000 });
await providerRow.hover();
await providerRow.locator('button[title="Edit"]').click();
// Should see the edit form with Name placeholder
await expect(page.getByPlaceholder("Name")).toBeVisible({ timeout: 5000 });
// Should be able to save or cancel
await expect(page.getByRole("button", { name: "Save" })).toBeVisible();
await expect(page.getByRole("button", { name: "Cancel" }).first()).toBeVisible();
// Cancel the edit
await page.getByRole("button", { name: "Cancel" }).first().click();
});
test("can set a provider as default", async ({ page, request }) => {
test.skip(!apiAvailable, 'API not available');
// Create two providers via API
await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "openai",
name: "Default Test Provider 1",
api_key: "sk-test-key-default-1",
},
});
await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "groq",
name: "Default Test Provider 2",
api_key: "sk-test-key-default-2",
},
});
const listResponse = await request.get("http://127.0.0.1:3000/api/ai/providers");
const providers = await listResponse.json();
const candidates = providers.filter((provider: { name: string }) =>
provider.name.startsWith("Default Test Provider")
);
const target = candidates.find((provider: { is_default: boolean }) => !provider.is_default);
test.skip(!target, 'No non-default provider to update');
// Reload to see the providers
await page.reload();
await page.waitForTimeout(1000);
const providerRow = page.locator('div').filter({ hasText: target.name }).filter({
has: page.locator('button[title="Set as default"]'),
}).first();
await expect(providerRow).toBeVisible({ timeout: 10000 });
await providerRow.hover();
await providerRow.locator('button[title="Set as default"]').click();
// Should see the Default star indicator
await expect(providerRow.locator('svg.text-indigo-400')).toBeVisible({ timeout: 10000 });
});
test("can delete a provider", async ({ page, request }) => {
test.skip(!apiAvailable, 'API not available');
// Create provider via API
const response = await request.post("http://127.0.0.1:3000/api/ai/providers", {
data: {
provider_type: "openai",
name: "Delete Test Provider",
api_key: "sk-delete-test",
},
});
if (!response.ok()) {
const text = await response.text();
throw new Error(`Failed to create provider: ${text}`);
}
// Reload to see the new provider
await page.reload();
await page.waitForTimeout(1000);
const providerRow = page.locator('div').filter({ hasText: "Delete Test Provider" }).filter({
has: page.locator('button[title="Delete"]'),
}).first();
await expect(providerRow).toBeVisible({ timeout: 10000 });
await providerRow.hover();
await providerRow.locator('button[title="Delete"]').click();
await page.waitForTimeout(1000);
// Provider should be removed
await expect(page.getByText("Delete Test Provider")).not.toBeVisible();
});
});