* 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.
347 lines
12 KiB
TypeScript
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();
|
|
});
|
|
});
|