import { expect, type Page, test } from "@playwright/test"; import { activeBotId, captureScreenshot, completeOnboarding, rpc, signup } from "./helpers"; test("actions run by default while optional confirmations live in advanced user settings", async ({ page, }, testInfo) => { const stamp = Date.now(); await signup(page, `action-confirmations-${stamp}@rakazo.test`, "password12", "Approval UI"); await completeOnboarding(page, testInfo); await sendDestinationWrite(page, "write this to the destination crm as a note"); await waitForRunIdle(page); await expectComposerReady(page); await expect(page.getByRole("button", { name: "Allow once", exact: true })).toHaveCount(0); await captureScreenshot(page, testInfo, "50-actions-run-without-confirmation"); await page.getByTestId("bot-settings-trigger").click(); await expect(page.getByTestId("bot-settings")).toBeVisible(); await expect(page.getByTestId("bot-settings").getByText("Action confirmations")).toHaveCount(0); await page.getByRole("button", { name: "Close panel" }).click(); await openUserSettings(page); const settings = page.getByTestId("user-settings"); await expect(settings).toHaveAttribute("role", "dialog"); await expect(settings).toBeFocused(); await expect(settings.getByText("Optional controls most people never need")).toBeVisible(); await expect(settings.getByRole("heading", { name: "Action confirmations" })).not.toBeVisible(); await captureScreenshot(page, testInfo, "51-user-settings-advanced-collapsed"); await settings.getByText("Advanced", { exact: true }).click(); await expect(settings.getByRole("heading", { name: "Action confirmations" })).toBeVisible(); await expect(settings.getByText("No exceptions. Actions run automatically.")).toBeVisible(); await expect(settings.getByTestId("auto-review-toggle")).toBeVisible(); await expect(settings.getByTestId("auto-review-toggle")).not.toBeChecked(); await expect(settings.getByText("Flag unexpected actions")).toBeVisible(); await settings.getByRole("button", { name: "Ask before sending external email" }).click(); await expect(settings.getByText("Ask before email actions", { exact: true })).toBeVisible(); await captureScreenshot(page, testInfo, "52-advanced-action-confirmations"); await settings.getByRole("button", { name: "Close user settings" }).click(); await rpc(page, "approvalRules/set", { effect: "require_approval", matchKind: "connector", matchValue: "destination.write", }); await requestDestinationWrite(page, "write this to the destination crm as a note again"); await expect( page.getByRole("button", { name: "Always allow this tool", exact: true }), ).toBeVisible(); await expect(page.getByRole("button", { name: "Deny", exact: true })).toBeVisible(); await captureScreenshot(page, testInfo, "53-action-confirmation-pending"); await page.getByRole("button", { name: "Deny", exact: true }).click(); await expect(page.getByText("Denied", { exact: true })).toBeVisible(); await waitForRunIdle(page); await expectComposerReady(page); await captureScreenshot(page, testInfo, "54-action-confirmation-denied"); await requestDestinationWrite(page, "write this to the destination crm once more"); await page.getByRole("button", { name: "Allow once", exact: true }).click(); await expect(page.getByText("Allowed once", { exact: true })).toBeVisible(); await waitForRunIdle(page); await expectComposerReady(page); await captureScreenshot(page, testInfo, "55-action-confirmation-allowed-once"); await requestDestinationWrite(page, "write this to the destination crm one final time"); await page.getByRole("button", { name: "Always allow this tool", exact: true }).click(); await expect(page.getByText("Always allowed", { exact: true })).toBeVisible(); await waitForRunIdle(page); await expectComposerReady(page); await captureScreenshot(page, testInfo, "56-action-confirmation-always-allowed"); await sendDestinationWrite(page, "write this to the destination crm after always allow"); await waitForRunIdle(page); await expectComposerReady(page); await expect(page.getByRole("button", { name: "Allow once", exact: true })).toHaveCount(0); }); async function openUserSettings(page: Page) { await page.getByTestId("user-menu-trigger").click(); await page.getByRole("button", { name: "Settings", exact: true }).click(); await expect(page.getByTestId("user-settings")).toBeVisible(); } async function sendDestinationWrite(page: Page, prompt: string) { await expectComposerReady(page); const composer = page.getByPlaceholder(/Message/); await composer.fill(prompt); const sent = page.waitForResponse( (response) => response.url().includes("/rpc/threads/send") && response.ok(), ); await page.keyboard.press("Enter"); await sent; } async function expectComposerReady(page: Page) { const composer = page.getByPlaceholder(/Message/); await expect(composer).toBeVisible(); await expect(composer).toBeEnabled(); await expect(page.getByRole("button", { name: "Stop", exact: true })).toHaveCount(0); } async function requestDestinationWrite(page: Page, prompt: string) { await sendDestinationWrite(page, prompt); const botId = activeBotId(page); await expect .poll( async () => { const snapshot = await rpc<{ run?: { status: string } | null }>(page, "threads/get", { botId, }); return snapshot.run?.status ?? null; }, { timeout: 30_000 }, ) .toBe("waiting_input"); // threads/get can observe waiting_input before the shell realtime feed paints the ask card. if ((await page.getByRole("button", { name: "Allow once" }).count()) === 0) { await page.reload({ waitUntil: "domcontentloaded" }); await expect(page.getByPlaceholder(/Message/)).toBeVisible({ timeout: 15_000 }); } await expect(page.getByRole("button", { name: "Allow once" })).toBeVisible({ timeout: 15_000, }); } async function waitForRunIdle(page: Page) { const botId = activeBotId(page); await expect .poll( async () => { const snapshot = await rpc<{ run?: { status: string } | null }>(page, "threads/get", { botId, }); return snapshot.run?.status ?? null; }, { timeout: 30_000 }, ) .toBeNull(); }