Files
buzz/desktop/tests/e2e/agent-numeric-tuning.spec.ts
T
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

373 lines
13 KiB
TypeScript

/**
* Playwright regression tests for the numeric tuning fields (max output tokens,
* context limit, max rounds) on both the global Agent Defaults surface and the
* per-agent Advanced section.
*
* Covers:
* 1. Global defaults Advanced shows numeric inputs for buzz-agent.
* 2. Global defaults Advanced hides numeric inputs for non-capable runtimes.
* 3. Per-agent Goose: saving a max-tokens value globally surfaces as
* Inherit (<value>) placeholder in the per-agent edit dialog.
* 4. Delayed catalog: while loading, saved tuning env vars stay visible
* as generic rows (not silently dropped); structured controls appear
* once the catalog settles.
* 5. Failed catalog: when discovery errors, saved tuning env vars remain
* visible as generic rows (never the "unsupported" empty state).
*/
import { expect, test } from "@playwright/test";
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
// ── Helpers ────────────────────────────────────────────────────────────────
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,
});
await expect(page.locator(".animate-spin").first()).not.toBeVisible({
timeout: 5_000,
});
}
async function openEditAgentDialog(
page: import("@playwright/test").Page,
agentName: string,
) {
await page.goto("/");
await page.getByTestId("open-agents-view").click();
const agentButton = page.getByRole("button", {
name: `${agentName} agent profile`,
});
await expect(agentButton).toBeVisible({ timeout: 10_000 });
await agentButton.click();
await expect(page.getByTestId("user-profile-panel")).toBeVisible({
timeout: 10_000,
});
await page.getByTestId("user-profile-edit-agent").click();
await expect(page.getByTestId("edit-agent-dialog")).toBeVisible({
timeout: 10_000,
});
}
// ── Tests ──────────────────────────────────────────────────────────────────
test("global_advanced_buzz_agent_shows_all_numeric_controls", async ({
page,
}) => {
// The mock bridge's withMockRuntimeConfigMetadata injects the numeric env var
// fields for buzz-agent. When buzz-agent is selected and Advanced is opened,
// all three numeric inputs must be visible.
await installMockBridge(page, {
acpRuntimesCatalog: [
{
id: "buzz-agent",
label: "Buzz Agent",
avatar_url: "",
availability: "available",
command: "buzz-agent",
binary_path: "/usr/local/bin/buzz-agent",
default_args: [],
mcp_command: null,
install_hint: "Ships with the Buzz desktop app.",
install_instructions_url: "https://github.com/block/buzz",
can_auto_install: false,
underlying_cli_path: null,
auth_status: { status: "not_applicable" },
},
],
globalAgentConfig: {
env_vars: {},
provider: "anthropic",
model: null,
preferred_runtime: "buzz-agent",
},
});
await openAiDefaultsSettings(page);
// Open the Advanced section. The settings card uses disclosure="full" (no
// animation wrapper), so we click the toggle and wait for content directly.
await page.getByTestId("global-agent-advanced-toggle").click();
// All three numeric inputs must be present for buzz-agent.
await expect(page.getByTestId("numeric-max-output-tokens-input")).toBeVisible(
{ timeout: 5_000 },
);
await expect(page.getByTestId("numeric-context-limit-input")).toBeVisible();
await expect(page.getByTestId("numeric-max-rounds-input")).toBeVisible();
});
test("global_advanced_non_capable_runtime_hides_numeric_controls", async ({
page,
}) => {
// Claude has no numeric tuning env vars (contextLimitEnvVar = null etc.).
// After selecting Claude, the Advanced section must show no numeric inputs.
await installMockBridge(page, {
acpRuntimesCatalog: [
{
id: "claude",
label: "Claude Code",
avatar_url: "",
availability: "available",
command: "/usr/local/bin/claude-agent",
binary_path: "/usr/local/bin/claude-agent",
default_args: ["acp"],
mcp_command: null,
install_hint: "Install via npm.",
install_instructions_url: "https://example.com",
can_auto_install: true,
underlying_cli_path: "/usr/local/bin/claude",
auth_status: { status: "logged_in" },
},
],
globalAgentConfig: {
env_vars: {},
provider: null,
model: null,
preferred_runtime: "claude",
},
});
await openAiDefaultsSettings(page);
await page.getByTestId("global-agent-advanced-toggle").click();
// No numeric inputs must render for a non-capable runtime.
await expect(page.getByTestId("numeric-max-output-tokens-input")).toHaveCount(
0,
);
await expect(page.getByTestId("numeric-context-limit-input")).toHaveCount(0);
await expect(page.getByTestId("numeric-max-rounds-input")).toHaveCount(0);
});
test("goose_per_agent_advanced_max_tokens_shows_inherited_global_placeholder", async ({
page,
}) => {
// Save GOOSE_MAX_TOKENS = 16384 in the global Agent Defaults settings via
// the UI, then open a Goose agent's edit dialog. The max-output-tokens input
// must show "Inherit (16384)" — the globally-saved value surfaced via the
// inherited placeholder.
await installMockBridge(page, {
globalAgentConfig: {
env_vars: { ANTHROPIC_API_KEY: "sk-ant-test-key" },
provider: "anthropic",
model: "claude-opus-4-5",
preferred_runtime: "goose",
},
managedAgents: [
{
pubkey: TEST_IDENTITIES.tyler.pubkey,
name: "Tyler Agent",
runtime: "goose",
status: "stopped",
channelNames: ["agents"],
},
],
});
// Step 1: open global defaults, expand Advanced, enter the max-tokens value.
await openAiDefaultsSettings(page);
await page.getByTestId("global-agent-advanced-toggle").click();
await expect(page.getByTestId("numeric-max-output-tokens-input")).toBeVisible(
{ timeout: 5_000 },
);
await page.getByTestId("numeric-max-output-tokens-input").click();
await page
.getByTestId("numeric-max-output-tokens-input")
.pressSequentially("16384");
// Blur to ensure React's change event fires for the number input.
await page.keyboard.press("Tab");
// Step 2: save the global defaults.
await expect(page.getByRole("button", { name: "Save defaults" })).toBeEnabled(
{ timeout: 5_000 },
);
await page.getByRole("button", { name: "Save defaults" }).click();
// Wait for the save to complete: the button returns to disabled (dirty resets).
await expect(
page.getByRole("button", { name: "Save defaults" }),
).toBeDisabled({ timeout: 5_000 });
// Step 3: navigate back and open the per-agent edit dialog for the Goose
// agent. We use the app's Back link rather than page.goto("/") to preserve
// the in-memory mock state (page.goto causes a full reload that resets it).
await page.getByRole("button", { name: "Back to app" }).click();
await page.getByTestId("open-agents-view").click();
const agentButton = page.getByRole("button", {
name: "Tyler Agent agent profile",
});
await expect(agentButton).toBeVisible({ timeout: 10_000 });
await agentButton.click();
await expect(page.getByTestId("user-profile-panel")).toBeVisible({
timeout: 10_000,
});
await page.getByTestId("user-profile-edit-agent").click();
await expect(page.getByTestId("edit-agent-dialog")).toBeVisible({
timeout: 10_000,
});
// Wait for the provider field — signals the catalog and dialog have settled.
await expect(page.locator("#edit-agent-llm-provider")).toBeVisible({
timeout: 10_000,
});
// Open the Advanced section.
await page.getByRole("button", { name: "Advanced", exact: true }).click();
await expect(page.getByTestId("numeric-max-output-tokens-input")).toBeVisible(
{ timeout: 5_000 },
);
// The placeholder must reflect the globally-saved value.
await expect(
page.getByTestId("numeric-max-output-tokens-input"),
).toHaveAttribute("placeholder", "Inherit (16384)");
});
test("delayed_catalog_per_agent_saved_tuning_values_visible_then_structured_controls_appear", async ({
page,
}) => {
// Scenario: catalog takes 5 seconds to respond (simulates slow discovery).
// The per-agent edit dialog opens. While the catalog is still in flight,
// saved tuning env vars must not be dropped from view — they appear as
// generic env rows (no hiddenKeys applied yet). Once the catalog settles,
// the structured numeric controls replace the generic rows.
await installMockBridge(page, {
acpRuntimesCatalog: [
{
id: "buzz-agent",
label: "Buzz Agent",
avatar_url: "",
availability: "available",
command: "buzz-agent",
binary_path: "/usr/local/bin/buzz-agent",
default_args: [],
mcp_command: null,
install_hint: "Ships with the Buzz desktop app.",
install_instructions_url: "https://github.com/block/buzz",
can_auto_install: false,
underlying_cli_path: null,
auth_status: { status: "not_applicable" },
},
],
// 5-second delay: generous enough that the dialog opens and Advanced is
// expanded while the catalog query is still in-flight (navigation takes
// ~1-2 s), but short enough to keep the test under 30 s.
acpRuntimesDelayMs: 5000,
globalAgentConfig: {
env_vars: {},
provider: "anthropic",
model: null,
preferred_runtime: "buzz-agent",
},
managedAgents: [
{
pubkey: TEST_IDENTITIES.tyler.pubkey,
name: "Tyler Agent",
runtime: "buzz-agent",
status: "stopped",
channelNames: ["agents"],
envVars: {
BUZZ_AGENT_MAX_OUTPUT_TOKENS: "4096",
BUZZ_AGENT_MAX_ROUNDS: "25",
},
},
],
});
await openEditAgentDialog(page, "Tyler Agent");
// Open Advanced before the catalog has settled (the dialog opens quickly;
// the catalog query fires when the dialog opens and takes ~5 seconds).
await page.getByRole("button", { name: "Advanced", exact: true }).click();
// While loading: structured numeric controls must NOT be visible yet —
// the catalog-settling gate withholds them.
await expect(page.getByTestId("numeric-max-output-tokens-input")).toHaveCount(
0,
);
// The saved tuning env vars must be visible as generic rows (not hidden)
// while the catalog hasn't settled: BUZZ_AGENT_MAX_OUTPUT_TOKENS and
// BUZZ_AGENT_MAX_ROUNDS should appear in the env-vars editor.
await expect(
page.locator(
'input[data-testid="env-vars-key"][value="BUZZ_AGENT_MAX_OUTPUT_TOKENS"]',
),
).toBeVisible();
await expect(
page.locator(
'input[data-testid="env-vars-key"][value="BUZZ_AGENT_MAX_ROUNDS"]',
),
).toBeVisible();
// After the catalog settles (allow up to 8 s — 5 s delay + margin):
// structured controls appear, replacing the generic rows.
await expect(page.getByTestId("numeric-max-output-tokens-input")).toBeVisible(
{ timeout: 8_000 },
);
await expect(page.getByTestId("numeric-max-rounds-input")).toBeVisible({
timeout: 8_000,
});
});
test("failed_catalog_per_agent_saved_tuning_values_remain_visible_as_generic_rows", async ({
page,
}) => {
// Scenario: catalog discovery fails (network error / IPC rejection).
// The per-agent edit dialog opens. The saved tuning env vars must remain
// visible as generic rows — the error state must never produce the
// "unsupported" no-controls state that would hide persisted values.
await installMockBridge(page, {
acpRuntimesError: true,
globalAgentConfig: {
env_vars: {},
provider: "anthropic",
model: null,
preferred_runtime: "buzz-agent",
},
managedAgents: [
{
pubkey: TEST_IDENTITIES.tyler.pubkey,
name: "Tyler Agent",
runtime: "buzz-agent",
status: "stopped",
channelNames: ["agents"],
envVars: {
BUZZ_AGENT_MAX_OUTPUT_TOKENS: "8192",
BUZZ_AGENT_MAX_ROUNDS: "10",
},
},
],
});
await openEditAgentDialog(page, "Tyler Agent");
// Open Advanced after a brief wait (query has had time to fail).
await page.waitForTimeout(500);
await page.getByRole("button", { name: "Advanced", exact: true }).click();
// Structured numeric controls must NOT render (catalog errored — no runtime).
await expect(page.getByTestId("numeric-max-output-tokens-input")).toHaveCount(
0,
);
// Saved tuning values must still be visible as generic env rows — the error
// state must never hide persisted values with no editor to replace them.
await expect(
page.locator(
'input[data-testid="env-vars-key"][value="BUZZ_AGENT_MAX_OUTPUT_TOKENS"]',
),
).toBeVisible({ timeout: 5_000 });
await expect(
page.locator(
'input[data-testid="env-vars-key"][value="BUZZ_AGENT_MAX_ROUNDS"]',
),
).toBeVisible();
});