Files
pleno-vue/tests/e2e/edge-gateways.smoke.spec.js
T
Jeppe Bundgaard 6c4b9cb599 Add and improve e2e tests for Edge Gateway flows and configure pre-commit hooks:
- Introduced live smoke tests for Edge Gateways, verifying gateway routes and destructive-action prevention.
- Added e2e scenarios for deep-link navigation, unavailable gateway recovery, token rotation, and background page refresh.
- Refactored test helpers for streamlined functional validation in gateway scenarios.
- Updated `EdgeGatewayTerminal.vue` with test IDs for enhanced testability.
- Added and configured Husky pre-commit hooks for automated test file formatting and validation.
2026-04-16 10:52:56 +02:00

187 lines
8.8 KiB
JavaScript

import { expect, test } from "@playwright/test";
import { mockApi, primeMockSession } from "./support/network.js";
async function primeSuperuserSession(page) {
const token = "superuser-edge-gateway-token";
await primeMockSession(page, { token });
}
test.describe("Edge gateway management smoke", () => {
test("@smoke manages installers, bindings, queued updates, and shell approval", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/gateways");
await expect(page).toHaveURL(/\/superuser\/gateways$/);
await expect(page.locator("body")).toContainText("Edge gateways");
await expect(page.locator("body")).toContainText("Valgt gateway");
await expect(page.locator("body")).toContainText("CPH Edge 01");
await expect(page.locator("body")).toContainText("Shelly Plus 2PM");
await expect(page.locator("body")).toContainText("Latens");
await expect(page.locator("body")).toContainText("184 ms");
await expect(page.locator("body")).toContainText("CPU");
await expect(page.locator("body")).toContainText("27%");
await expect(page.locator("body")).toContainText("RAM");
await expect(page.locator("body")).toContainText("61%");
await expect(page.locator("body")).toContainText("Disk");
await expect(page.locator("body")).toContainText("58%");
const bindingRows = page.locator(".edge-table--editor tbody tr");
const primaryBindingRow = bindingRows.nth(0);
const legacyBindingRow = bindingRows.nth(1);
const primaryDeviceSelect = primaryBindingRow.locator("select").first();
const primaryChannelSelect = primaryBindingRow.locator("select").nth(1);
const primaryIpInput = primaryBindingRow.locator('input[type="text"]').nth(1);
const primaryOnlineOption = primaryDeviceSelect.locator("option").nth(1);
await expect(primaryOnlineOption).toContainText("Shelly Plus 2PM");
await expect(primaryOnlineOption).toContainText("shelly-plus-01");
await expect(primaryOnlineOption).toContainText("10.1.0.31");
await expect(primaryOnlineOption).toContainText("2 kanaler");
await expect(primaryOnlineOption).toContainText("Gen 2");
const primaryOfflineOption = primaryDeviceSelect.locator("option").nth(2);
await expect(primaryOfflineOption).toContainText("Shelly Mini 1");
await expect(primaryOfflineOption).toContainText("shelly-mini-offline");
await expect(primaryOfflineOption).toContainText("10.1.0.34");
await expect(primaryOfflineOption).toContainText("1 kanal");
await expect(primaryOfflineOption).toContainText("Gen 3");
await expect(primaryOfflineOption).toContainText("Offline");
const staleOption = legacyBindingRow.locator("select").first().locator("option").last();
await expect(staleOption).toContainText("Ukendt Shelly-device");
await expect(staleOption).toContainText("shelly-missing-legacy");
await expect(staleOption).toContainText("10.1.0.99");
await expect(staleOption).toContainText("2 kanaler");
await expect(staleOption).toContainText("Mangler i discovery");
await primaryChannelSelect.selectOption("1");
await primaryDeviceSelect.selectOption("shelly-mini-offline");
await expect(primaryIpInput).toHaveValue("10.1.0.34");
await expect(primaryChannelSelect).toHaveValue("0");
const relayBindingInput = primaryBindingRow.locator('input[type="text"]').first();
await relayBindingInput.fill("M-7-CANARY");
await page.getByRole("button", { name: "Gem bindinger" }).click();
await expect(relayBindingInput).toHaveValue("M-7-CANARY");
await expect(primaryDeviceSelect).toHaveValue("shelly-mini-offline");
await expect(primaryIpInput).toHaveValue("10.1.0.34");
await expect(primaryChannelSelect).toHaveValue("0");
await page.getByLabel("Installer afdeling").selectOption("1");
await page.locator('input[placeholder="F.eks. Roskilde Pi 01"]').fill("Canary Pi");
await page.getByRole("button", { name: /installer/i }).click();
await expect(page.getByLabel("Installationskommando")).toHaveValue(/curl -fsSL/);
await page.getByRole("button", { name: /discovery/i }).click();
await expect(page.locator("body")).toContainText("Discovery er");
await expect(page.locator("body")).toContainText("Afventer");
await expect(page.locator("body")).toContainText("shelly-plus-new");
await page.locator('input[placeholder="F.eks. 1.3.0"]').fill("1.3.0");
await page.getByRole("button", { name: /opdatering/i }).click();
await expect(page.locator("body")).toContainText("Opdatering er");
const latestUpdate = page.getByTestId("gateway-update-item").first();
await expect(latestUpdate).toContainText("1.3.0");
await expect(latestUpdate).toContainText("Verificerer genstart");
await expect(latestUpdate).toContainText("Fuldført");
await page.getByRole("button", { name: /root shell/i }).click();
await page.getByRole("textbox", { name: /begrundelse/i }).fill("Investigate offline relay");
await page.getByRole("button", { name: "Godkend root shell" }).click();
const terminalBuffer = page.locator('[aria-label="Root shell buffer"]');
await expect(page.locator("body")).toContainText("Live");
await expect(terminalBuffer).toContainText("[Live shell forbundet]");
await expect(terminalBuffer).toContainText("root@pi:~#");
const terminalSurface = page.locator('[aria-label="Root shell terminal"]');
await terminalSurface.focus();
await terminalSurface.press("l");
await terminalSurface.press("s");
await terminalSurface.press("Enter");
await expect(terminalBuffer).toContainText("agent.mjs");
await page.getByRole("button", { name: "Terminate session" }).click();
await expect(page.locator("body")).toContainText("Lukket");
await expect(terminalBuffer).toContainText("Session lukket");
});
test("@smoke shows a shell connection error when the API-polled shell closes before open", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
edgeGateways: {
shellSocket: {
mode: "fail-before-open",
},
},
});
await primeSuperuserSession(page);
await page.goto("/superuser/gateways");
await page.getByRole("button", { name: /root shell/i }).click();
await page.getByRole("textbox", { name: /begrundelse/i }).fill("Test broken shell");
await page.getByRole("button", { name: "Godkend root shell" }).click();
await expect(page.locator("body")).toContainText("Fejl");
await expect(page.getByRole("button", { name: "Terminate session" })).toBeDisabled();
await expect(page.locator('[aria-label="Root shell buffer"]')).toContainText("Gateway-shell blev lukket");
});
test("@smoke applies department cutover mode from department page", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/departments/1/gateways");
await expect(page).toHaveURL(/\/superuser\/departments\/1\/gateways$/);
await expect(page.locator("body")).toContainText("Gateway for afdelingen");
await page.getByLabel("Transporttilstand").selectOption("cloud");
await page.getByRole("button", { name: "Anvend cutover" }).click();
await expect(page.getByLabel("Transporttilstand")).toHaveValue("cloud");
await expect(page.locator("body")).toContainText("Cloud fallback");
});
test("@smoke shows API polling as the control channel while discovery still works", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/gateways");
await expect(page.locator("body")).toContainText("API polling");
await page.getByRole("button", { name: /discovery/i }).click();
await expect(page.locator("body")).toContainText("Discovery er");
await expect(page.locator("body")).toContainText("shelly-plus-new");
});
test("@smoke shows a rolled-back update when restart verification fails", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/gateways/701");
await page.locator('input[placeholder="F.eks. 1.3.0"]').fill("1.3.0-rollback");
await page.getByRole("button", { name: /opdatering/i }).click();
const latestUpdate = page.getByTestId("gateway-update-item").first();
await expect(latestUpdate).toContainText("1.3.0-rollback");
await expect(latestUpdate).toContainText("Verificerer genstart");
await expect(latestUpdate).toContainText("Rullet tilbage");
await expect(latestUpdate).toContainText("Startup verification timed out");
});
});