From f289685bf311d32205a92bb762c10775c30d64ed Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 19 Aug 2026 21:39:26 -0700 Subject: [PATCH] fix(ui): stabilize settings and update recovery E2E (#126538) * test(ui): await settings takeover in app chrome E2E * test(ui): assert current build after update recovery --- ui/src/e2e/app-chrome-interaction.e2e.test.ts | 10 +++--- ui/src/e2e/service-worker-update.e2e.test.ts | 36 ++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/ui/src/e2e/app-chrome-interaction.e2e.test.ts b/ui/src/e2e/app-chrome-interaction.e2e.test.ts index 3a1bbad69cae..ed58b857e1ef 100644 --- a/ui/src/e2e/app-chrome-interaction.e2e.test.ts +++ b/ui/src/e2e/app-chrome-interaction.e2e.test.ts @@ -3,7 +3,10 @@ import { mkdir } from "node:fs/promises"; import path from "node:path"; import type { Locator, Page } from "playwright"; import { expect, it } from "vitest"; -import { installMockGateway } from "../test-helpers/control-ui-e2e.ts"; +import { + installMockGateway, + waitForControlUiSettingsTakeover, +} from "../test-helpers/control-ui-e2e.ts"; import { createControlUiE2eSuite } from "./control-ui-e2e-suite.test-support.ts"; const suite = createControlUiE2eSuite({ @@ -155,11 +158,10 @@ suite.define(() => { // 650px even against the mock gateway's tiny config fixture; General // became short enough to fit once the host panel moved to Gateway. await page.goto(`${suite.server.baseUrl}settings/appearance`); - const settingsSidebar = page.locator(".settings-sidebar"); + const { search: settingsSearch, sidebar: settingsSidebar } = + await waitForControlUiSettingsTakeover(page); const settingsTitle = settingsSidebar.locator(".settings-sidebar__title"); - const settingsSearch = settingsSidebar.locator(".settings-sidebar__search-input"); const content = page.locator(".content"); - await settingsSidebar.waitFor(); await expect .poll(() => content.evaluate((element) => element.scrollHeight)) .toBeGreaterThan(await content.evaluate((element) => element.clientHeight)); diff --git a/ui/src/e2e/service-worker-update.e2e.test.ts b/ui/src/e2e/service-worker-update.e2e.test.ts index a153348e1da8..706d77490fb9 100644 --- a/ui/src/e2e/service-worker-update.e2e.test.ts +++ b/ui/src/e2e/service-worker-update.e2e.test.ts @@ -448,10 +448,32 @@ describe("Control UI service-worker production update E2E", () => { "catalog" in request.params, ); expect(catalogOpensBeforeWorkerActivation.length).toBeLessThanOrEqual(1); + if (catalogOpensBeforeWorkerActivation.length > 0) { + const currentConnect = (await gateway.getRequests("connect")).at(-1); + expect(currentConnect?.params).toMatchObject({ client: { buildId: buildB } }); + } installGate.release(); await reloaded; await ensureControlledPage(page, pageErrors, buildB); - await expect.poll(() => readWorkerUpdateVersions(page)).toContain(buildB); + await expect + .poll(async () => (await gateway.getRequests("connect")).at(-1)?.params) + .toMatchObject({ client: { buildId: buildB } }); + await page.waitForFunction((expectedBuildId) => { + const controller = navigator.serviceWorker.controller; + return ( + controller?.state === "activated" && + new URL(controller.scriptURL).searchParams.get("v") === expectedBuildId + ); + }, buildB); + expect( + await page.evaluate(() => { + const controller = navigator.serviceWorker.controller; + return { + buildId: controller ? new URL(controller.scriptURL).searchParams.get("v") : null, + state: controller?.state ?? null, + }; + }), + ).toEqual({ buildId: buildB, state: "activated" }); const terminal = page.locator("openclaw-terminal-panel[embedded]"); await terminal.waitFor({ state: "attached" }); @@ -499,6 +521,18 @@ describe("Control UI service-worker production update E2E", () => { sha256: assetB.sha256, }); expect(refreshedAsset.sha256).not.toBe(initialAsset.sha256); + await expect + .poll(() => + page.evaluate( + async ({ assetPath, cacheName }) => { + const cache = await caches.open(cacheName); + const shell = await cache.match(new URL("./", window.location.origin)); + return shell ? (await shell.text()).includes(assetPath) : false; + }, + { assetPath: assetB.path, cacheName: `openclaw-control-${buildB}` }, + ), + ) + .toBe(true); if (captureUiProof) { await page.screenshot({