Files
kvm/ui/e2e/ota-specific-version-unsigned.spec.ts
Adam Shiervani 41ea6b93ca fix(test): fix e2e flakiness from hook timeouts, EDID drops, and USB recovery (#1382)
OTA beforeAll/afterAll hooks were using the global 60s timeout while
performing multi-reboot sequences that need minutes. Add explicit
test.setTimeout(420000) inside each hook.

setEDID RPC can drop the WebSocket on some devices during HDMI re-link.
Tolerate RPC timeouts, reconnect page + WebRTC afterwards, and retry
agent resolution checks with a 15s polling loop.

EBUSY CDROM test used sysfs traversal that broke across kernel/host
variations. Switch to lsblk and skip gracefully when the host doesn't
enumerate USB mass storage as sr* devices.

USB recovery test left the device bricked (initUsbGadget crash loop)
when auto-recovery failed. Add try/catch that re-binds the UDC and
reboots on failure.

OTA reconnectAfterReboot retries increased from 15 to 30 (65s → 95s)
to handle slower devices.
2026-03-30 19:26:09 +02:00

93 lines
2.7 KiB
TypeScript

import { test, expect } from "@playwright/test";
import {
getCurrentVersion,
reconnectAfterReboot,
rebootDeviceViaSSH,
verifyHidAndVideo,
ensureLocalAuthMode,
createMockUpdateServer,
deployBinaryToDevice,
configureDeviceUpdateUrl,
restoreDeviceUpdateUrl,
setIncludePreRelease,
getOTAEnvVars,
type MockUpdateServer,
type OTAEnvVars,
} from "./helpers";
/**
* Verifies that custom/specific-version updates bypass GPG signature checks.
* Uses tryUpdateComponents with customVersionUpdate=true.
*/
test.describe("OTA Specific Version Unsigned", () => {
test.setTimeout(420000);
let mockServer: MockUpdateServer;
let env: OTAEnvVars;
test.beforeAll(async ({ browser }) => {
test.setTimeout(420000);
env = getOTAEnvVars();
const context = await browser.newContext({ baseURL: process.env.JETKVM_URL });
const page = await context.newPage();
try {
await ensureLocalAuthMode(page, { mode: "noPassword" });
} finally {
await page.close();
await context.close();
}
await deployBinaryToDevice(env.baselinePath);
await rebootDeviceViaSSH();
mockServer = await createMockUpdateServer({
binaryPath: env.releasePath,
version: env.releaseVersion,
});
});
test("specific-version update succeeds without signature", async ({ page }) => {
await test.step("Configure mock API and stable channel", async () => {
await configureDeviceUpdateUrl(mockServer.url);
await setIncludePreRelease(false);
await rebootDeviceViaSSH();
});
await test.step(`Custom version update to ${env.releaseVersion}`, async () => {
await page.goto(
`/settings/general/update?custom_app_version=${env.releaseVersion}&reset_config=false`,
);
await page.waitForLoadState("networkidle");
const initialVersion = await getCurrentVersion(page);
expect(initialVersion).not.toBeNull();
expect(initialVersion, "Baseline and target must differ").not.toBe(env.releaseVersion);
const updateButton = page.locator('[data-testid="update-now-button"]');
await expect(updateButton).toBeVisible({ timeout: 20000 });
await updateButton.click();
await expect(page.getByText(/downloading|verifying|installing|awaiting reboot/i)).toBeVisible(
{ timeout: 30000 },
);
await reconnectAfterReboot(page, 35000, 30);
const finalVersion = await getCurrentVersion(page);
expect(finalVersion).toBe(env.releaseVersion);
});
await test.step("Verify HID and video", async () => {
await verifyHidAndVideo(page);
});
});
test.afterAll(async () => {
test.setTimeout(420000);
await restoreDeviceUpdateUrl();
await mockServer?.close();
});
});