Files
openworker/surfaces/gui/e2e/unattended.spec.ts
T
Rohit C PrasadandDevika 2b45018ffa OpenWorker: initial import
Imported from andrewyng/aisuite@1b4bbf303e
(contents of its platform/ directory, hoisted to the repo root).
Development history prior to this commit lives in that repository.

Co-authored-by: Devika <devikaverma11@gmail.com>
2026-07-21 11:09:41 -07:00

114 lines
5.1 KiB
TypeScript

// Unattended mode (item 8) — the "Send approvals to Inbox" toggle and its effect on approvals.
// Since §22 the toggle lives at the BOTTOM of the composer's Mode menu (who approves, and when —
// one mental model; the standalone InboxControl left the row). When a session is unattended, an
// approval PARKS to the Inbox instead of surfacing an inline card (the app suppresses the live
// card; the Inbox list itself is covered by inbox.spec.ts). The mocked /v1/sessions/:id/unattended
// is stateful so the toggle persists across a reload.
import { expect } from "@playwright/test";
import { test } from "./fixtures";
// The toggle sits inside the composer's Mode menu (§22).
async function openModeMenu(page) {
await page.getByRole("button", { name: "Mode", exact: true }).click();
await expect(page.getByTestId("mode-menu")).toBeVisible();
}
test("attended (default): a tool request surfaces the inline approval card", async ({ page }) => {
await page.goto("/");
const box = page.getByPlaceholder(/Ask the coworker/);
await box.fill("please run a tool");
await page.getByRole("button", { name: "Send" }).click();
await expect(page.getByText("The coworker wants to run a command.").first()).toBeVisible();
});
test("Send-to-Inbox toggle (in the Mode menu) flips and persists across a reload", async ({
page,
}) => {
await page.goto("/");
await openModeMenu(page);
const sw = page.getByRole("switch", { name: "Send approvals to the Inbox" });
await expect(sw).toHaveAttribute("aria-checked", "false");
await sw.click();
await expect(sw).toHaveAttribute("aria-checked", "true");
// Reload: the stateful endpoint returns the saved flag, so the toggle reads back on.
await page.reload();
await openModeMenu(page);
await expect(page.getByRole("switch", { name: "Send approvals to the Inbox" })).toHaveAttribute(
"aria-checked",
"true",
);
});
test("unattended: a tool request parks (no inline approval card)", async ({ page }) => {
await page.goto("/");
await openModeMenu(page);
await page.getByRole("switch", { name: "Send approvals to the Inbox" }).click();
// The menu's full-screen overlay closes it on any outside click.
await page.mouse.click(5, 5);
const box = page.getByPlaceholder(/Ask the coworker/);
await box.fill("please run a tool");
await page.getByRole("button", { name: "Send", exact: true }).click();
// The turn still starts, but the live approval card is suppressed — the prompt is parked to the
// Inbox instead. Give the (suppressed) card a beat to NOT appear.
await expect(page.getByText("Echo:").first()).toBeVisible().catch(() => {});
await expect(page.getByText("The coworker wants to run a command.")).toHaveCount(0);
});
test("answering the live approval never re-flashes its parked Inbox mirror", async ({ page }) => {
// Every live approval is ALSO parked as a per-session Inbox item (reconnect/remote resolution).
// Tester catch 2026-07-12: after "Allow once", the polled sessionInbox copy was still pending
// for up to a poll cycle, so the docked answer-in-context card flashed the SAME request again.
// Simulate the mirror: any per-session inbox fetch for the live session returns one pending
// approval until the decision lands (the fixtures' fixed items belong to other sessions).
// The real server resolves the mirror synchronously with the decision — only the CLIENT's
// polled copy is stale, which is exactly what this test pins.
let mirrorResolved = false;
await page.route(/\/v1\/inbox\?/, async (route) => {
const q = new URL(route.request().url()).searchParams;
const sid = q.get("session_id");
if (!sid || sid === "wp-3" || sid === "ops-1") return route.fallback();
return route.fulfill({
contentType: "application/json",
body: JSON.stringify({
items: mirrorResolved
? []
: [
{
id: "mirror-1",
session_id: sid,
kind: "approval",
title: "Run `run_shell`?",
body: "requires approval",
state: "pending",
resolution: null,
inbox: "default",
created_at: "2026-07-12 10:00:00",
resolved_at: null,
},
],
}),
});
});
await page.goto("/");
const box = page.getByPlaceholder(/Ask the coworker/);
await box.fill("please run a tool");
await page.getByRole("button", { name: "Send", exact: true }).click();
await expect(page.getByText("The coworker wants to run a command.").first()).toBeVisible();
mirrorResolved = true; // server side resolves with the decision; the stale client copy is the bug
await page.getByRole("button", { name: "Allow once" }).last().click();
// "Never appears" semantics: pre-fix the stale mirror rendered within a frame of the click and
// self-cleared a poll later — so a plain toHaveCount(0) would blink green. Watch the window.
const flashed = await page
.getByText("Run `run_shell`?")
.waitFor({ state: "visible", timeout: 700 })
.then(() => true)
.catch(() => false);
expect(flashed).toBe(false);
await expect(page.getByText("The command ran; 1 file found.")).toBeVisible();
});