feat: import Chinese-localized Buzz source snapshot
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
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
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* E2E spec for the create-agent "Run on" provider config fields.
|
||||
*
|
||||
* Pins the fix for the "Typewriter Eraser": WhereToRunSection's probe effect
|
||||
* used to depend on the whole draft, so every keystroke re-probed the
|
||||
* provider and every probe resolution reset providerConfig to schema
|
||||
* defaults — typing into a defaultless field (the k8s "Kubeconfig context")
|
||||
* looked completely dead, and the provider binary respawned in a loop.
|
||||
*
|
||||
* Covers:
|
||||
* - typing into a defaultless provider field sticks, and the provider is
|
||||
* probed exactly once for the selection (not once per keystroke or
|
||||
* Advanced disclosure toggle)
|
||||
* - the config form is gated on probe resolution (no half-rendered form),
|
||||
* and defaults prefill exactly once when a slow probe lands
|
||||
* - collapsing Advanced during an incomplete remote setup keeps the submit
|
||||
* blocker visible through the Required badge
|
||||
* - switching provider → local → provider re-probes and resets cleanly
|
||||
*
|
||||
* The stale-closure merge on probe resolution (defaults beneath in-flight
|
||||
* typing) is unreachable through this UI because the fields render only
|
||||
* after the probe resolves; it is pinned at the unit level in
|
||||
* whereToRunIntent.test.mjs (applyProbeResult).
|
||||
*/
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
type Page = import("@playwright/test").Page;
|
||||
|
||||
const PROVIDER = {
|
||||
id: "kubernetes",
|
||||
binaryPath: "/mock/buzz-backend-kubernetes",
|
||||
};
|
||||
|
||||
const PROBE_RESULT = {
|
||||
ok: true,
|
||||
name: "kubernetes",
|
||||
version: "0.0.0-mock",
|
||||
config_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
context: {
|
||||
type: "string",
|
||||
title: "Kubeconfig context",
|
||||
description: "Context from your kubeconfig.",
|
||||
},
|
||||
namespace: {
|
||||
type: "string",
|
||||
title: "Namespace",
|
||||
default: "buzz-agents-mock01",
|
||||
},
|
||||
},
|
||||
required: ["namespace"],
|
||||
},
|
||||
};
|
||||
|
||||
async function probeInvocations(page: Page): Promise<number> {
|
||||
return page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as Window & { __BUZZ_E2E_COMMANDS__?: string[] }
|
||||
).__BUZZ_E2E_COMMANDS__?.filter(
|
||||
(command) => command === "probe_backend_provider",
|
||||
).length ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
async function selectRunOnOption(
|
||||
page: Page,
|
||||
dialog: import("@playwright/test").Locator,
|
||||
optionName: string,
|
||||
) {
|
||||
const trigger = dialog.locator("#agent-run-on");
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
await trigger.press("Enter");
|
||||
|
||||
const option = page.getByRole("menuitemradio", {
|
||||
exact: true,
|
||||
name: optionName,
|
||||
});
|
||||
await expect(option).toBeVisible();
|
||||
// The shared PersonaDropdownField supports keyboard selection. Using it here
|
||||
// avoids racing the menu's open animation when this test changes locations
|
||||
// repeatedly.
|
||||
await option.press("Enter");
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/** Open Advanced in the create-agent dialog and select the mocked provider. */
|
||||
async function openCreateDialogOnProvider(page: Page) {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByTestId("open-agents-view").click();
|
||||
await page.getByTestId("new-agent-card").click();
|
||||
const dialog = page.getByTestId("persona-dialog");
|
||||
await expect(dialog).toBeVisible({ timeout: 10_000 });
|
||||
const advanced = dialog.getByRole("button", {
|
||||
name: "Advanced",
|
||||
exact: true,
|
||||
});
|
||||
await expect(advanced).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(dialog.locator("#agent-run-on")).toHaveCount(0);
|
||||
await advanced.click();
|
||||
await expect(advanced).toHaveAttribute("aria-expanded", "true");
|
||||
const respondTo = dialog.getByTestId("agent-respond-to");
|
||||
const runOn = dialog.locator("#agent-run-on");
|
||||
await expect(respondTo).toBeVisible();
|
||||
await expect(runOn).toBeVisible();
|
||||
expect(await respondTo.evaluate((element) => element.offsetTop)).toBeLessThan(
|
||||
await runOn.evaluate((element) => element.offsetTop),
|
||||
);
|
||||
await selectRunOnOption(page, dialog, PROVIDER.id);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
test("typing into a defaultless provider field sticks and probes only once", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
backendProviders: [PROVIDER],
|
||||
backendProviderProbeResult: PROBE_RESULT,
|
||||
});
|
||||
const dialog = await openCreateDialogOnProvider(page);
|
||||
|
||||
const contextField = dialog.locator("#provider-cfg-context");
|
||||
await expect(contextField).toBeVisible({ timeout: 10_000 });
|
||||
// Defaults prefilled from the schema; context has none.
|
||||
await expect(dialog.locator("#provider-cfg-namespace")).toHaveValue(
|
||||
"buzz-agents-mock01",
|
||||
);
|
||||
await expect(contextField).toHaveValue("");
|
||||
|
||||
await contextField.fill("prod-us-west");
|
||||
await expect(contextField).toHaveValue("prod-us-west");
|
||||
|
||||
// One selection, one probe — keystrokes and Advanced disclosure toggles
|
||||
// must not refire executable provider discovery after it has completed.
|
||||
expect(await probeInvocations(page)).toBe(1);
|
||||
const advanced = dialog.getByRole("button", {
|
||||
name: "Advanced",
|
||||
exact: true,
|
||||
});
|
||||
await advanced.click();
|
||||
await expect(advanced).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(dialog.locator("#agent-run-on")).toHaveCount(0);
|
||||
await advanced.click();
|
||||
await expect(advanced).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(dialog.locator("#provider-cfg-context")).toHaveValue(
|
||||
"prod-us-west",
|
||||
);
|
||||
expect(await probeInvocations(page)).toBe(1);
|
||||
});
|
||||
|
||||
test("config fields render only after a slow probe resolves, with defaults", async ({
|
||||
page,
|
||||
}) => {
|
||||
// The fields are gated on the probe result (draft.probedProvider), which is
|
||||
// what makes mid-flight typing unreachable through the UI — the stale-probe
|
||||
// merge seam (applyProbeResult) is pinned at the unit level instead. This
|
||||
// spec holds the gate: no half-rendered form before the probe lands, and
|
||||
// defaults appear exactly once when it does.
|
||||
await installMockBridge(page, {
|
||||
backendProviders: [PROVIDER],
|
||||
backendProviderProbeResult: PROBE_RESULT,
|
||||
backendProviderProbeDelayMs: 1_000,
|
||||
});
|
||||
const dialog = await openCreateDialogOnProvider(page);
|
||||
|
||||
// Pre-resolution: the security warning is up, the form is not.
|
||||
await expect(dialog.getByText("will receive your agent")).toBeVisible();
|
||||
await expect(dialog.locator("#provider-cfg-context")).toHaveCount(0);
|
||||
|
||||
// Post-resolution: fields render with schema defaults prefilled.
|
||||
await expect(dialog.locator("#provider-cfg-context")).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await expect(dialog.locator("#provider-cfg-namespace")).toHaveValue(
|
||||
"buzz-agents-mock01",
|
||||
);
|
||||
expect(await probeInvocations(page)).toBe(1);
|
||||
});
|
||||
|
||||
test("collapsed Advanced marks incomplete remote setup as required", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
backendProviders: [PROVIDER],
|
||||
backendProviderProbeResult: PROBE_RESULT,
|
||||
backendProviderProbeDelayMs: 10_000,
|
||||
});
|
||||
const dialog = await openCreateDialogOnProvider(page);
|
||||
const advanced = dialog.getByRole("button", {
|
||||
name: "Advanced",
|
||||
exact: true,
|
||||
});
|
||||
const submit = dialog.getByTestId("persona-dialog-submit");
|
||||
|
||||
await expect(submit).toBeDisabled();
|
||||
await advanced.click();
|
||||
await expect(advanced).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(dialog.locator("#agent-run-on")).toHaveCount(0);
|
||||
await expect(
|
||||
dialog.getByTestId("persona-advanced-required-badge"),
|
||||
).toHaveText("Required");
|
||||
await expect(submit).toBeDisabled();
|
||||
});
|
||||
|
||||
test("provider → local → provider re-probes and resets the config", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
backendProviders: [PROVIDER],
|
||||
backendProviderProbeResult: PROBE_RESULT,
|
||||
});
|
||||
const dialog = await openCreateDialogOnProvider(page);
|
||||
|
||||
const contextField = dialog.locator("#provider-cfg-context");
|
||||
await expect(contextField).toBeVisible({ timeout: 10_000 });
|
||||
await contextField.fill("stale-value");
|
||||
|
||||
await selectRunOnOption(page, dialog, "This computer");
|
||||
await expect(contextField).toHaveCount(0);
|
||||
|
||||
await selectRunOnOption(page, dialog, PROVIDER.id);
|
||||
await expect(dialog.locator("#provider-cfg-context")).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
// Fresh selection = fresh draft: the stale value must not leak back.
|
||||
await expect(dialog.locator("#provider-cfg-context")).toHaveValue("");
|
||||
expect(await probeInvocations(page)).toBe(2);
|
||||
});
|
||||
Reference in New Issue
Block a user