import { expect, test } from "@playwright/test"; import type { ComputerUpdate } from "@rakazo/contracts"; import { activeBotId, captureScreenshot, completeOnboarding, signup } from "./helpers"; test.afterEach(async ({ page }) => { await page.unrouteAll({ behavior: "wait" }); }); test("computer maintenance shows durable background progress and failure recovery", async ({ page, }, testInfo) => { await page.addInitScript(() => localStorage.setItem("rakazo.uiAppearance", "dark")); await signup(page, `computer-update-${Date.now()}@rakazo.test`, "password12", "Computer Update"); await completeOnboarding(page); const botId = activeBotId(page); const mock = { updates: [] as ComputerUpdate[] }; const updating: ComputerUpdate = { id: "update-example", botId, name: "Chief", mode: "team", action: "update", status: "running", stage: "restoring", }; const fulfillUpdates = async () => { await page.route("**/rpc/computer/updates", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ json: mock.updates }), }); }); }; await fulfillUpdates(); await page.route("**/rpc/computer/update", async (route) => { mock.updates = [updating]; await route.fulfill({ contentType: "application/json", body: JSON.stringify({ json: updating }), }); }); await page.route("**/rpc/computer/recover", async (route) => { mock.updates = [{ ...updating, id: "recovery-example", action: "recover", stage: "preparing" }]; await route.fulfill({ contentType: "application/json", body: JSON.stringify({ json: mock.updates[0] }), }); }); let releases = 0; await page.route("**/rpc/computer/releaseInterrupted", async (route) => { expect(route.request().postDataJSON()).toMatchObject({ json: { id: updating.id, workersStopped: true }, }); releases++; mock.updates = [{ ...updating, status: "failed" }]; await route.fulfill({ contentType: "application/json", body: JSON.stringify({ json: { ok: true } }), }); }); await page.getByTitle("Agent computer").click(); await expect(page.getByTestId("oo-computer-browser-viewport")).toBeVisible(); await page.getByTestId("computer-open").click(); await page.getByTestId("computer-more-button").click(); await page.getByRole("menuitem", { name: "Update computer", exact: true }).click(); const dialog = page.getByTestId("computer-update-dialog"); await expect(dialog).toBeVisible(); await expect(dialog.getByRole("heading")).toHaveText("Updating Team Computer"); await expect(dialog.locator('[aria-current="step"]')).toHaveText("Restoring your workspace"); await captureScreenshot(page, testInfo, "computer-update-progress"); await dialog.getByRole("button", { name: "Continue in Background" }).click(); await expect(dialog).not.toBeVisible(); await expect(page.getByTestId("computer-update-background-chip")).toBeVisible(); await captureScreenshot(page, testInfo, "computer-update-background"); const updatesListed = page.waitForResponse( (response) => response.url().includes("/rpc/computer/updates") && response.ok(), ); await page.reload({ waitUntil: "domcontentloaded" }); await expect(page.getByTestId("shell-root")).toHaveAttribute("data-ready", "true", { timeout: 60_000, }); await updatesListed; const chip = page.getByTestId("computer-update-background-chip"); await expect(chip).toBeVisible(); await chip.click(); await expect(dialog).toBeVisible(); mock.updates = [{ ...updating, status: "interrupted" }]; await expect( dialog.getByText("Recovery is unavailable until the previous operation has stopped."), ).toBeVisible(); await expect(dialog.getByRole("button", { name: "Recover computer" })).toHaveCount(0); await captureScreenshot(page, testInfo, "computer-update-interrupted"); mock.updates = [{ ...updating, status: "interrupted", canReleaseReservation: true }]; await dialog.getByRole("button", { name: "Release computer", exact: true }).click(); const confirmation = page.getByRole("alertdialog"); await captureScreenshot(page, testInfo, "computer-update-release-confirmation"); await confirmation.getByRole("button", { name: "Cancel", exact: true }).click(); expect(releases).toBe(0); await dialog.getByRole("button", { name: "Release computer", exact: true }).click(); await confirmation.getByRole("button", { name: "Nothing is still running", exact: true }).click(); await expect(dialog.getByRole("button", { name: "Recover computer", exact: true })).toBeVisible(); expect(releases).toBe(1); await expect(dialog.getByRole("heading")).toHaveText("Update failed"); await captureScreenshot(page, testInfo, "computer-update-failed"); await dialog.getByRole("button", { name: "Recover computer" }).click(); await expect(dialog.getByRole("heading")).toHaveText("Recovering Team Computer"); await captureScreenshot(page, testInfo, "computer-recovery-progress"); mock.updates = []; await expect(dialog).not.toBeVisible(); await expect(page.getByTestId("computer-update-background-chip")).toHaveCount(0); });