Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 35 additions & 30 deletions apps/web/e2e/agents-lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ function environmentTrigger(page: Page) {
}

function connectedLiveEvents(page: Page) {
return page.getByRole("status", { name: "Session live events: connected" });
return page.getByRole("status", { name: "Session live events: Live events" });
}

async function setDarkTheme(page: Page) {
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitemradio", { name: "Dark theme" }).click();
}

async function openEnvironmentDialog(page: Page) {
Expand Down Expand Up @@ -976,7 +981,7 @@ test("saves a reusable Environment Template and references it from a managed Ses
await expect(templateSelect).toBeDisabled();
await expect(dialog).toContainText("This Core exposes no saved Templates");

await dialog.getByRole("button", { name: "New Template" }).click();
await dialog.getByRole("button", { name: "New Environment Template" }).click();
await dialog.getByLabel("Template name").fill("Restricted outbound access");
await dialog.getByLabel("Template network access").selectOption("disabled");
await dialog.getByRole("button", { name: "Save Template" }).click();
Expand All @@ -985,7 +990,7 @@ test("saves a reusable Environment Template and references it from a managed Ses
await expect(templateSelect).toHaveValue(/^4/);
const selectedTemplateId = await templateSelect.inputValue();
await expect(dialog.getByLabel("Managed Environment network access"))
.toContainText("Inherit from Template (disabled)");
.toContainText("Inherit from Template (Disabled)");

const templateCreates = (await fixtureRequests(request)).filter((entry) => (
entry.method === "POST" && entry.path === "/v1/agents/environments/templates"
Expand Down Expand Up @@ -1029,7 +1034,7 @@ test("hides Template selection when the connected Core lacks the resource", asyn

await expect(dialog).toContainText("does not expose the Environment Template resource");
await expect(dialog.getByLabel("Reusable Environment Template")).toBeDisabled();
await expect(dialog.getByRole("button", { name: "New Template" })).toHaveCount(0);
await expect(dialog.getByRole("button", { name: "New Environment Template" })).toHaveCount(0);
await expect(dialog.getByRole("button", { name: "Create Session" })).toBeEnabled();
});

Expand Down Expand Up @@ -1184,9 +1189,9 @@ test("keeps managed Environment resource and terminal event states fail-closed",
await controlFixture(request, { environmentScenario: 8, environmentResourceStatus: "expired" });
await openSessionsFromHome(page);
let trigger = environmentTrigger(page);
await expect(trigger).toContainText("Managed Environment expired");
await expect(trigger).toContainText("Environment: Expired");
let opened = await openEnvironmentDialog(page);
await expect(opened.panel).toContainText("Managed Environment expired");
await expect(opened.panel).toContainText("Managed Environment Expired");
await expect(opened.panel.getByText("Add inline Workspace file", { exact: true })).toHaveCount(0);
await expect(opened.panel).not.toContainText("Connect Environment");
await opened.dialog.getByRole("button", { name: "Done" }).click();
Expand All @@ -1195,9 +1200,9 @@ test("keeps managed Environment resource and terminal event states fail-closed",
await expect(page.getByRole("table", { name: "Recent Sessions" })).toContainText("Managed hosted");
await page.getByRole("button", { name: "Sessions", exact: true }).click();
await controlFixture(request, { environmentResourceStatus: "pending", environmentResourceVariant: "wrong_type" });
await page.getByRole("button", { name: "Recover durable state" }).click();
await page.getByRole("button", { name: "Refresh Sessions" }).click();
trigger = environmentTrigger(page);
await expect(trigger).toContainText("Managed Environment unavailable");
await expect(trigger).toContainText("Environment: Unavailable");
opened = await openEnvironmentDialog(page);
await expect(opened.panel).toContainText("Durable managed Environment status is unavailable");
await expect(opened.panel.getByText("Add inline Workspace file", { exact: true })).toHaveCount(0);
Expand All @@ -1209,9 +1214,9 @@ test("keeps managed Environment resource and terminal event states fail-closed",
environmentEventCount: 1,
});
await page.reload();
await expect(environmentTrigger(page)).toContainText("Managed Environment failed");
await expect(environmentTrigger(page)).toContainText("Environment: Failed");
opened = await openEnvironmentDialog(page);
await expect(opened.panel).toContainText("Managed Environment failed");
await expect(opened.panel).toContainText("Managed Environment Failed");
await expect(opened.panel.getByText("Add inline Workspace file", { exact: true })).toHaveCount(0);
await expect(opened.panel).not.toContainText("Connect Environment");

Expand All @@ -1227,7 +1232,7 @@ test("keeps managed Environment resource and terminal event states fail-closed",
await expect(page.getByText('Failed to provision environment: script "setup_commands[0]" failed with exit code 3', { exact: true })).toBeVisible();
await expect(page.getByLabel("Message the Agent")).toBeDisabled();
opened = await openEnvironmentDialog(page);
await expect(opened.panel).toContainText("Managed Environment failed");
await expect(opened.panel).toContainText("Managed Environment Failed");
await expect(opened.panel.getByText("Add inline Workspace file", { exact: true })).toHaveCount(0);
await expect(opened.panel).not.toContainText("Connect Environment");

Expand Down Expand Up @@ -1407,7 +1412,7 @@ test("requires delete confirmation, preserves failures, and keeps Session snapsh
await expect(page.getByRole("button", { name: /^Edit Lifecycle Agent/ })).toHaveCount(0);

await page.getByRole("button", { name: "Sessions" }).click();
await expect(page.getByRole("button", { name: /idle Lifecycle Agent/ })).toBeVisible();
await expect(page.getByRole("button", { name: /Idle Lifecycle Agent/ })).toBeVisible();
requests = await fixtureRequests(request);
expect(requests.filter((entry) => entry.method === "DELETE" && entry.path === "/v1/agents/agent_a")).toHaveLength(2);
});
Expand Down Expand Up @@ -1498,7 +1503,7 @@ test("keeps the Agent card grid, setup, and delete confirmation usable at 390 px
await page.getByRole("button", { name: "Back to Agents" }).click();
await expect(globalCreate).toBeFocused();

await page.getByRole("button", { name: "Dark theme" }).click();
await setDarkTheme(page);
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await page.getByRole("button", { name: /^Edit Lifecycle Agent/ }).click();
const editSetup = page.locator(".agent-setup-page");
Expand Down Expand Up @@ -1696,7 +1701,7 @@ test("fences the filtered workspace across loading, errors, unavailable Agents,
await expect(page.locator(".conversation-header h2")).toHaveText("Second Agent");
const deletedAgent = await request.delete(`${fixtureBaseUrl}/v1/agents/agent_b`);
expect(deletedAgent.ok()).toBe(true);
await page.getByRole("button", { name: "Recover durable state" }).click();
await page.getByRole("button", { name: "Refresh Sessions" }).click();
await expect(filter).toHaveValue("agent_b");
await expect(filter.locator("option:checked")).toHaveText("Unavailable Agent (not loaded)");
await expect(page.locator(".conversation-header h2")).toHaveText("Second Agent");
Expand Down Expand Up @@ -2270,7 +2275,7 @@ test("aborts a pending manual recovery read after deleting the selected Session"
await controlFixture(request, { sessionRetrieveDelayMs: 5_000 });

await expectSelectedDeleteAbortsSessionRead(page, request, () => (
page.getByRole("button", { name: "Recover durable state" }).click()
page.getByRole("button", { name: "Refresh Sessions" }).click()
));
});

Expand All @@ -2279,7 +2284,7 @@ test("aborts a pending detail retry read after deleting the selected Session", a
await openSessionsFromHome(page);
await expect(connectedLiveEvents(page)).toBeVisible();
await controlFixture(request, { sessionRetrieveStatus: 503 });
await page.getByRole("button", { name: "Recover durable state" }).click();
await page.getByRole("button", { name: "Refresh Sessions" }).click();
const detailError = page.locator(".session-detail-error");
await expect(detailError).toBeVisible();
await controlFixture(request, { sessionRetrieveDelayMs: 5_000 });
Expand Down Expand Up @@ -2326,7 +2331,7 @@ test("keeps Session actions accessible and contained at 390 px in dark mode", as
await resetFixture(request);
await openSessionsFromHome(page);
await expect(connectedLiveEvents(page)).toBeVisible();
await page.getByRole("button", { name: "Dark theme" }).click();
await setDarkTheme(page);
const manage = page.locator(".conversation-session-action");
await manage.focus();
await page.keyboard.press("Enter");
Expand Down Expand Up @@ -3119,7 +3124,7 @@ test("renders self-hosted Environment and Workspace state safely across reconnec
await expect(connectedLiveEvents(page)).toBeVisible();

const { dialog, panel, trigger } = await openEnvironmentDialog(page);
await expect(trigger).toHaveAccessibleName("Environment pending");
await expect(trigger).toHaveAccessibleName("Environment: Pending");
await expect(panel).toContainText("Self-hosted Environment");
await expect(panel).toContainText("Pending");
await expect(panel).toContainText("environment_fixture");
Expand Down Expand Up @@ -3148,7 +3153,7 @@ test("renders self-hosted Environment and Workspace state safely across reconnec
await expect(dialog).toHaveCount(0);
await expect(trigger).toBeFocused();
await page.setViewportSize({ width: 390, height: 844 });
await page.getByRole("button", { name: "Dark theme" }).click();
await setDarkTheme(page);
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
const { panel: narrowPanel } = await openEnvironmentDialog(page);
const widths = await narrowPanel.evaluate((element) => {
Expand All @@ -3174,7 +3179,7 @@ test("renders self-hosted Environment and Workspace state safely across reconnec
await controlFixture(request, { environmentScenario: 2, environmentEventStatus: 0 });
await page.reload();
const { panel: unknown, trigger: unknownTrigger } = await openEnvironmentDialog(page);
await expect(unknownTrigger).toHaveAccessibleName("Environment unavailable");
await expect(unknownTrigger).toHaveAccessibleName("Environment: Unavailable");
await expect(unknown).toContainText("Environment unavailable");
await expect(unknown).toContainText("Unknown type");
await expect(unknown).not.toContainText("/must-not-render");
Expand Down Expand Up @@ -3235,7 +3240,7 @@ test("lists Workspace file metadata explicitly, paginates, fails closed, and fen
await fixtureRequests(request)
).filter((entry) => entry.path.endsWith("/files")).length).toBeGreaterThan(reads.length + 1);
await controlFixture(request, { environmentScenario: 2 });
await page.getByRole("button", { name: "Recover durable state" }).evaluate((button) => (
await page.getByRole("button", { name: "Refresh Sessions" }).evaluate((button) => (
button as HTMLButtonElement
).click());
await expect(page.getByRole("region", { name: "Workspace files" })).toHaveCount(0);
Expand All @@ -3253,7 +3258,7 @@ test("hydrates durable expired and unavailable Environment states without a writ
await expect(connectedLiveEvents(page)).toBeVisible();

let { panel, trigger } = await openEnvironmentDialog(page);
await expect(trigger).toHaveAccessibleName("Environment expired");
await expect(trigger).toHaveAccessibleName("Environment: Expired");
await expect(panel).toContainText("Expired");
await expect(panel).toContainText("Environment expired");
await expect(panel).toContainText("no API-managed files, plugins, or skills");
Expand All @@ -3278,7 +3283,7 @@ test("hydrates durable expired and unavailable Environment states without a writ
await controlFixture(request, { environmentRetrieveStatus: 503 });
await page.reload();
({ panel, trigger } = await openEnvironmentDialog(page));
await expect(trigger).toHaveAccessibleName("Environment unavailable");
await expect(trigger).toHaveAccessibleName("Environment: Unavailable");
await expect(panel).toContainText("Unavailable");
await expect(panel).toContainText("conversation remains usable");
await expect(panel).not.toContainText("Expired");
Expand All @@ -3291,7 +3296,7 @@ test("hydrates durable expired and unavailable Environment states without a writ
});
await page.reload();
({ panel, trigger } = await openEnvironmentDialog(page));
await expect(trigger).toHaveAccessibleName("Environment unavailable");
await expect(trigger).toHaveAccessibleName("Environment: Unavailable");
await expect(panel).toContainText("Unavailable");
await expect(page.getByLabel("Message the Agent")).toBeVisible();

Expand All @@ -3311,7 +3316,7 @@ test("hydrates durable Environment state even when the live stream is rejected",
await openSessionsFromHome(page);

const { panel, trigger } = await openEnvironmentDialog(page);
await expect(trigger).toHaveAccessibleName("Environment expired");
await expect(trigger).toHaveAccessibleName("Environment: Expired");
await expect(panel).toContainText("Expired");
await expect(panel).toContainText("Status comes from the durable Environment resource");
await expect(page.getByText("Events unavailable", { exact: true })).toBeVisible();
Expand All @@ -3334,7 +3339,7 @@ test("keeps canonical Environment UUID identity across Session and resource proj
await openSessionsFromHome(page);

const { panel, trigger } = await openEnvironmentDialog(page);
await expect(trigger).toHaveAccessibleName("Environment connected");
await expect(trigger).toHaveAccessibleName("Environment: Connected");
await expect(panel).toContainText("Connected");
await expect(panel).toContainText("Status comes from the durable Environment resource");
await expect(panel).not.toContainText("Durable Environment status is unavailable");
Expand All @@ -3359,7 +3364,7 @@ test("applies a buffered live Environment event after an earlier durable snapsho
});
await openSessionsFromHome(page);

await expect(environmentTrigger(page)).toHaveAccessibleName("Environment pending");
await expect(environmentTrigger(page)).toHaveAccessibleName("Environment: Pending");
const streamError = page.locator(".session-stream-error");
await expect(streamError.getByText("Couldn’t open live events")).toBeVisible();

Expand All @@ -3372,7 +3377,7 @@ test("applies a buffered live Environment event after an earlier durable snapsho
await streamError.getByRole("button", { name: "Retry" }).click();

const { panel, trigger } = await openEnvironmentDialog(page);
await expect(trigger).toHaveAccessibleName("Environment connected");
await expect(trigger).toHaveAccessibleName("Environment: Connected");
await expect(panel).toContainText("Connected");
await expect(panel).toContainText("last supported live event observed after the durable Environment snapshot");
await expect(panel).not.toContainText("Pending");
Expand Down Expand Up @@ -3431,7 +3436,7 @@ test("loads every Turn page, reconciles terminal events, and keeps diagnostics o
).filter((entry) => entry.method === "GET" && entry.path.endsWith("/turns")).length).toBeGreaterThan(readsBeforeTerminal.length);

await controlFixture(request, { turnsRetrieveStatus: 503 });
await page.getByRole("button", { name: "Recover durable state" }).click();
await page.getByRole("button", { name: "Refresh Sessions" }).click();
await expect(trace.locator(".trace-load-error").filter({ hasText: "Turn history is incomplete" })).toBeVisible();
await expect(timeline.locator(".turn-timeline-failure")).toContainText("Couldn’t load Turn history");
await expect(timeline).toContainText("last observed Turn timeline remains visible");
Expand Down Expand Up @@ -3655,7 +3660,7 @@ test("renders Parsar patches as accessible read-only diffs in desktop and narrow
await attachScreenshot(page, testInfo, "desktop-light-parsar-diff");

await page.setViewportSize({ width: 390, height: 844 });
await page.getByRole("button", { name: "Dark theme" }).click();
await setDarkTheme(page);
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
const widths = await viewer.evaluate((element) => {
const box = element.getBoundingClientRect();
Expand Down