Files
openclaw/ui/src/e2e/about.e2e.test.ts
Peter Steinberger 7fe004d852 feat: show build identity in About screens (#103595)
* feat: show build identity in About screens

* chore: leave root changelog to release automation

* fix: translate Control UI About build details
2026-07-10 10:42:36 +01:00

124 lines
4.8 KiB
TypeScript

// Control UI tests cover About artifact identity against a mocked Gateway.
import { chromium, type Browser } from "playwright";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
canRunPlaywrightChromium,
installMockGateway,
resolvePlaywrightChromiumExecutablePath,
startControlUiE2eServer,
type ControlUiE2eServer,
} from "../test-helpers/control-ui-e2e.ts";
const chromiumExecutablePath = resolvePlaywrightChromiumExecutablePath(chromium.executablePath());
const chromiumAvailable = canRunPlaywrightChromium(chromiumExecutablePath);
const allowMissingChromium = process.env.OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM === "1";
const describeControlUiE2e = chromiumAvailable || !allowMissingChromium ? describe : describe.skip;
const COMMIT = "0123456789abcdef0123456789abcdef01234567";
const BUILT_AT = "2026-07-10T12:34:56.000Z";
let browser: Browser;
let server: ControlUiE2eServer;
describeControlUiE2e("Control UI About mocked Gateway E2E", () => {
beforeAll(async () => {
if (!chromiumAvailable) {
throw new Error(`Playwright Chromium is unavailable at ${chromiumExecutablePath}`);
}
server = await startControlUiE2eServer();
browser = await chromium.launch({ executablePath: chromiumExecutablePath });
});
afterAll(async () => {
await browser?.close();
await server?.close();
});
it("shows and copies browser artifact identity, separately from the Gateway version", async () => {
const context = await browser.newContext({
locale: "en-US",
serviceWorkers: "block",
viewport: { height: 900, width: 1280 },
});
await context.addInitScript(() => {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: {
writeText: async (text: string) => {
(globalThis as typeof globalThis & { __openclawCopiedCommit?: string })[
"__openclawCopiedCommit"
] = text;
},
},
});
});
const page = await context.newPage();
await installMockGateway(page);
try {
const response = await page.goto(`${server.baseUrl}settings/about`);
expect(response?.status()).toBe(200);
await page.getByRole("heading", { name: "Settings" }).waitFor();
const aboutLink = page.getByRole("link", { name: "About", exact: true });
await expect.poll(() => aboutLink.getAttribute("aria-current")).toBe("page");
const strip = page.getByRole("group", { name: "Control UI build details" });
const items = strip.locator(":scope > .about-build-strip__item");
await expect.poll(() => items.count()).toBe(3);
await expect.poll(() => items.nth(0).textContent()).toContain("2026.7.10");
const commit = items.nth(1).locator("code");
await expect.poll(() => commit.textContent()).toBe(COMMIT.slice(0, 12));
await expect.poll(() => commit.getAttribute("title")).toBe(COMMIT);
const built = items.nth(2).locator("time");
await expect.poll(() => built.textContent()).toBe("Jul 10, 2026");
await expect.poll(() => built.getAttribute("datetime")).toBe(BUILT_AT);
await expect.poll(() => built.getAttribute("title")).toBe(BUILT_AT);
const gatewayRow = page.locator(".about-gateway-row");
await expect.poll(() => gatewayRow.textContent()).toContain("e2e");
await expect
.poll(() => gatewayRow.textContent())
.toContain("separate from this Control UI build");
const desktopTops = await items.evaluateAll((elements) =>
elements.map((element) => Math.round(element.getBoundingClientRect().top)),
);
expect(new Set(desktopTops).size).toBe(1);
const copyButton = strip.locator(".about-build-strip__copy");
await expect.poll(() => copyButton.getAttribute("aria-label")).toBe("Copy full commit hash");
await copyButton.click();
await expect.poll(() => copyButton.getAttribute("aria-label")).toBe("Commit hash copied");
await expect
.poll(() =>
page.evaluate(
() =>
(globalThis as typeof globalThis & { __openclawCopiedCommit?: string })[
"__openclawCopiedCommit"
],
),
)
.toBe(COMMIT);
await page.setViewportSize({ height: 812, width: 375 });
const mobileTops = await items.evaluateAll((elements) =>
elements.map((element) => Math.round(element.getBoundingClientRect().top)),
);
expect(new Set(mobileTops).size).toBe(3);
await expect
.poll(() =>
page.evaluate(
() => document.documentElement.scrollWidth - document.documentElement.clientWidth,
),
)
.toBeLessThanOrEqual(1);
const mobileScreenshot = await page.screenshot({ animations: "disabled", fullPage: true });
expect(mobileScreenshot.byteLength).toBeGreaterThan(1_000);
} finally {
await context.close();
}
});
});