mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
test(e2e): AI provider dropdown switches the rendered config form
Locks in the B2 contract: /settings/ai exposes the active-provider Pulldown, switching the dropdown swaps the form below it (Codex → OpenAI → Anthropic → Local), and the fallback chain card surfaces all configured rows with their stable data-chain-row markers. The spec mocks every backend endpoint the section reads so the run needs no real Codex / OpenAI keys in CI. data-testid markers on each config form keep the assertions independent of the locale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
70206b9bf5
commit
3848b625af
1 file changed
+116
@@ -0,0 +1,116 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* v1.4.16 phase B2 — AI provider settings UX with single dropdown
|
||||
* driving the form below. Three things to lock in here:
|
||||
*
|
||||
* 1. /settings/ai exposes the active-provider Pulldown
|
||||
* (`data-testid="ai-active-provider-select"`).
|
||||
* 2. Switching the dropdown changes the rendered config form below
|
||||
* it: Codex form vs. OpenAI form vs. Anthropic form vs. Local
|
||||
* form. Each form carries a stable `data-testid` so we don't
|
||||
* lean on i18n labels.
|
||||
* 3. The fallback chain card surfaces with stable
|
||||
* `data-chain-row="<providerType>"` markers per row.
|
||||
*/
|
||||
test.describe("Settings → AI provider dropdown UX (B2)", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test("dropdown switch changes the rendered config form", async ({ page }) => {
|
||||
// Stable mocks for every endpoint the section reads. The chain
|
||||
// mirrors the default order (codex first) so the rows render
|
||||
// deterministically and the dropdown defaults to codex.
|
||||
await page.route("**/api/insights/settings", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
codexStatus: "disconnected",
|
||||
codexConnectedAt: null,
|
||||
hasAdminKey: false,
|
||||
codexOauthConfigured: true,
|
||||
privacyMode: "aggregated",
|
||||
lastInsightAt: null,
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route("**/api/user/ai-provider", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
provider: null,
|
||||
model: null,
|
||||
baseUrl: null,
|
||||
hasAnthropicKey: false,
|
||||
anthropicKeyPreview: null,
|
||||
hasLocalKey: false,
|
||||
hasOpenaiKey: false,
|
||||
openaiKeyPreview: null,
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route("**/api/insights/provider-chain", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
activeProvider: "codex",
|
||||
cachedActiveProvider: null,
|
||||
configuredChain: [
|
||||
{ providerType: "codex", available: true },
|
||||
{ providerType: "openai", available: true },
|
||||
{ providerType: "anthropic", available: true },
|
||||
{ providerType: "local", available: true },
|
||||
],
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto("/settings/ai", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// 1. Dropdown surfaces on the page.
|
||||
const select = page.getByTestId("ai-active-provider-select");
|
||||
await expect(select).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// 2a. Default → Codex form rendered.
|
||||
await expect(page.getByTestId("ai-provider-config-codex")).toBeVisible();
|
||||
await expect(page.getByTestId("ai-provider-config-openai")).toHaveCount(0);
|
||||
|
||||
// 2b. Switch to OpenAI → OpenAI form rendered, Codex form gone.
|
||||
await select.selectOption("openai");
|
||||
await expect(page.getByTestId("ai-provider-config-openai")).toBeVisible();
|
||||
await expect(page.getByTestId("ai-provider-config-codex")).toHaveCount(0);
|
||||
await expect(page.getByTestId("ai-openai-api-key")).toBeVisible();
|
||||
await expect(page.getByTestId("ai-openai-model")).toBeVisible();
|
||||
|
||||
// 2c. Switch to Anthropic → Anthropic form rendered.
|
||||
await select.selectOption("anthropic");
|
||||
await expect(page.getByTestId("ai-provider-config-anthropic")).toBeVisible();
|
||||
|
||||
// 2d. Switch to Local → Local form rendered.
|
||||
await select.selectOption("local");
|
||||
await expect(page.getByTestId("ai-provider-config-local")).toBeVisible();
|
||||
|
||||
// 3. Fallback chain card surfaces with the four chain rows.
|
||||
await expect(page.getByTestId("ai-fallback-chain")).toBeVisible();
|
||||
for (const slug of ["codex", "openai", "anthropic", "local"]) {
|
||||
await expect(
|
||||
page.locator(`[data-chain-row="${slug}"]`),
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user