Files
buzz/desktop/tests/e2e/agent-provider-dropdowns.spec.ts
cls 9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

256 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Screenshot-regression spec for PR #1764: provider/model dropdown fixes.
*
* Three states that previously regressed on fresh OSS installs:
*
* 01 Global agent config provider select renders both Databricks v1 and v2
* options (they are always shown on OSS builds; the mock bridge returns an
* empty baked env, simulating an OSS install with no BUZZ_AGENT_PROVIDER).
*
* 02 Effort dropdown shows "Default (medium)" instead of bare "Inherit" when
* no effort is baked and the provider uses a known default effort level.
* (provider unset → effortDefault = "medium" → inheritFallbackLabel fires)
*
* 03 Edit dialog for a definition with runtime null auto-seeds the app-default
* runtime (buzz-agent) and model discovery runs → model combobox is non-empty.
* Previously, the seeding effect bailed in edit mode, leaving the runtime
* empty and the model dropdown silently blank.
*/
import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
import { waitForAnimations } from "../helpers/animations";
const SHOTS = "test-results/screenshots-dialogs";
/**
* Open Settings → Agents through the app UI and wait for the defaults card to
* finish loading. The CI static server does not provide SPA fallbacks for a
* direct `/settings` request.
*/
async function openAiDefaultsSettings(page: import("@playwright/test").Page) {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-settings").click();
await page.getByTestId("profile-popover-settings").click();
await expect(page.getByTestId("settings-view")).toBeVisible();
await page.getByTestId("settings-nav-agents").click();
await expect(page.getByTestId("settings-global-agent-config")).toBeVisible({
timeout: 10_000,
});
// The card shows a spinner during the async load effect; wait for it to clear.
await expect(page.locator(".animate-spin").first()).not.toBeVisible({
timeout: 5_000,
});
}
test.describe("agent provider dropdown screenshots", () => {
test.use({ viewport: { width: 1280, height: 900 } });
test.beforeEach(async ({ page }) => {
page.on("pageerror", (err) => {
console.error(
"PAGE ERROR:",
err.message,
err.stack?.split("\n").slice(0, 5).join("\n"),
);
});
});
// Shot 01: OSS provider dropdown includes both Databricks v1 and v2.
//
// The mock bridge returns get_baked_build_env_keys = [] (OSS), so no
// BUZZ_AGENT_PROVIDER is baked and hideProviderIds is empty → v1 appears.
test("01-provider-dropdown-oss", async ({ page }) => {
await installMockBridge(page);
await openAiDefaultsSettings(page);
const providerSelect = page.getByTestId("global-agent-provider");
await expect(providerSelect).toBeVisible({ timeout: 5_000 });
// Regression: both v1 and v2 must be present on OSS. The defaults editor
// renders the app's styled dropdown, so open it and assert option rows.
await providerSelect.click();
await expect(
page.getByTestId("global-agent-provider-option-databricks"),
).toHaveText("Databricks");
await expect(
page.getByTestId("global-agent-provider-option-databricks_v2"),
).toHaveText("Databricks v2");
await page.keyboard.press("Escape");
await expect(
page.getByTestId("global-agent-provider-option-databricks"),
).toHaveCount(0);
await waitForAnimations(page);
// Screenshot the card — provider select is closed but the assertion above
// proves both Databricks options are present in the option list.
await page
.getByTestId("settings-global-agent-config")
.screenshot({ path: `${SHOTS}/01-provider-dropdown-oss.png` });
});
// Shot 02: Effort dropdown shows "Default (medium)" (no baked effort,
// provider=databricks_v2 with no model → effortDefault = "medium").
//
// getProviderEffortConfig("databricks_v2", "") hits the blank-model branch:
// → { defaultValue: "medium" }
// inheritFallbackLabel = "Default (medium)", bakedEffort = null (OSS)
// → the zero-value option reads "Default (medium)" instead of bare "Inherit".
//
// Using databricks_v2 here (rather than no provider) makes this card
// visually distinct from shot 01 — the provider select shows "Databricks v2"
// as the selected value — so the two PNG hashes differ.
test("02-effort-default-label", async ({ page }) => {
await installMockBridge(page, {
globalAgentConfig: {
provider: "databricks_v2",
model: null,
env_vars: {},
},
});
await openAiDefaultsSettings(page);
const effortSelect = page.getByTestId(
"global-agent-thinking-effort-select",
);
await expect(effortSelect).toBeVisible({ timeout: 5_000 });
// Regression: the zero-value option must show the provider's default
// effort rather than a bare "Inherit". The styled dropdown renders the
// zero value as the closed trigger's label (nothing is persisted) and as
// the "empty" option row when opened.
await expect(effortSelect).toHaveText("Default (medium)");
await effortSelect.click();
await expect(
page.getByTestId("global-agent-thinking-effort-select-option-empty"),
).toHaveText("Default (medium)");
await page.keyboard.press("Escape");
await expect(
page.getByTestId("global-agent-thinking-effort-select-option-empty"),
).toHaveCount(0);
await waitForAnimations(page);
await page
.getByTestId("settings-global-agent-config")
.screenshot({ path: `${SHOTS}/02-effort-default-label.png` });
});
// Shot 03: Edit dialog for a definition with null runtime auto-seeds the
// default runtime (buzz-agent via getDefaultPersonaRuntime) and model
// discovery runs, producing a non-empty model combobox.
//
// Previously the seeding effect bailed in edit mode ("id" in initialValues),
// leaving runtime = "" → modelFieldVisible = false → discovery never ran →
// the model dropdown was silently empty.
test("03-builtin-edit-runtime-seeded", async ({ page }) => {
await installMockBridge(page, {
personas: [
{
displayName: "Null Runtime Agent",
systemPrompt: "An agent with no runtime configured.",
// runtime/provider/model not set → all null in the mock, so the
// edit dialog must auto-seed the app default runtime.
},
],
});
await page.goto("/");
await page.getByTestId("open-agents-view").click();
await expect(page.getByTestId("agents-library-personas")).toBeVisible({
timeout: 10_000,
});
// Open the persona's actions menu (visible for non-builtin personas).
const actionsBtn = page.getByRole("button", {
name: "Open actions for Null Runtime Agent",
});
await expect(actionsBtn).toBeVisible({ timeout: 8_000 });
await actionsBtn.click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const dialog = page.getByTestId("persona-dialog");
await expect(dialog).toBeVisible({ timeout: 10_000 });
await dialog.getByRole("tab", { name: "Customize for this agent" }).click();
// Regression: the runtime trigger must not be empty — the auto-seed effect
// must have run and selected the app default (buzz-agent in the mock catalog).
const runtimeTrigger = dialog.locator("#persona-runtime");
await expect(runtimeTrigger).toBeVisible({ timeout: 8_000 });
await expect(runtimeTrigger).not.toContainText("No preference", {
timeout: 8_000,
});
// Regression: the model combobox must appear (modelFieldVisible = true once
// runtime is non-empty) and model discovery must have run, populating it.
const modelCombobox = dialog.getByRole("combobox", { name: /model/i });
await expect(modelCombobox).toBeVisible({ timeout: 8_000 });
// Open the picker and assert that a known model from the mock discovery
// response is listed. The mock returns "Claude Opus 4.6" for all providers
// (see discover_agent_models in e2eBridge.ts). A zero-model discovery
// regression would leave the list empty and this assertion would fail.
// The picker is a searchable command popover portaled outside the dialog
// whose items render as buttons — query at page level.
await modelCombobox.click();
await expect(
page.getByRole("button", { name: /Claude Opus 4\.6/i }),
).toBeVisible({ timeout: 5_000 });
// Close the popover so the screenshot captures the dialog's resting state.
await page.keyboard.press("Escape");
await expect(
page.getByRole("button", { name: /Claude Opus 4\.6/i }),
).toBeHidden();
await waitForAnimations(page);
await dialog.screenshot({
path: `${SHOTS}/03-builtin-edit-runtime-seeded.png`,
});
});
test("04-codex-definition-exposes-model-without-global-provider-defaults", async ({
page,
}) => {
await installMockBridge(page, {
globalAgentConfig: {
provider: "databricks_v2",
model: "global-databricks-model",
env_vars: {},
},
personas: [
{
displayName: "Codex Definition",
systemPrompt: "A Codex-backed definition.",
runtime: "codex",
},
],
});
await page.goto("/");
await page.getByTestId("open-agents-view").click();
await page
.getByRole("button", { name: "Open actions for Codex Definition" })
.click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const dialog = page.getByTestId("persona-dialog");
await expect(dialog).toBeVisible({ timeout: 10_000 });
await expect(
dialog.getByRole("tab", { name: "Customize for this agent" }),
).toBeVisible();
await expect(
dialog.getByText("Harness default", { exact: true }),
).toBeVisible();
await expect(dialog.getByText(/Databricks/i)).toHaveCount(0);
await dialog.getByRole("tab", { name: "Customize for this agent" }).click();
await expect(
dialog.getByRole("combobox", { name: /model/i }),
).toBeVisible();
await expect(dialog.getByText("Model changes apply only")).toHaveCount(0);
});
});