* fix(e2e): restore computer-update progress chip after reload Harden the computer maintenance Playwright flow so mocked update state survives a full page reload: stable background-chip test id, explicit aria-label, wait for shell readiness and the updates RPC, and explicit JSON route fulfillment. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(ci): bump Expo SDK 57 patches and harden flaky web e2e Sync mobile Expo packages for frozen-lockfile typecheck and read RPC fetch metadata before response bodies in Playwright route helpers. Stabilize empty-project Starten, onboarding model catalog, and secret-card reload specs. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(e2e): stabilize RPC intercepts and project Starten flow Fulfill patched RPC routes with string bodies, ignore benign route races, target project Starten by test id, and wait for thread hydration after reload. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(e2e): drop threads/get wait after secret-card reload Reload does not always refetch threads/get; poll the saved ask card instead. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(e2e): reserve unreachable setup port per desktop smoke run Avoid CI races where a port reserved in beforeAll is rebound before the unreachable-address check runs. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retry product checks after runner OOM on web e2e Previous Web E2E job exited 137 during the Playwright suite; no failing spec was reported. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(e2e): wait for shell before opening user settings After reload, the user menu is not interactive until bootstrap finishes. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
113 lines
5.1 KiB
TypeScript
113 lines
5.1 KiB
TypeScript
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("computer-preview")).toBeVisible();
|
|
await page.getByTestId("computer-preview").hover();
|
|
await page.getByTestId("computer-preview-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);
|
|
});
|