Files
pleno-vue/tests/e2e/edge-gateways.smoke.spec.js
T

391 lines
18 KiB
JavaScript

import { expect, test } from "@playwright/test";
import { mockApi, primeMockSession } from "./support/network.js";
async function primeSuperuserSession(page) {
await primeMockSession(page, { token: "superuser-edge-gateway-token" });
}
test.describe("Edge gateway management smoke", () => {
test.describe.configure({ mode: "serial" });
test("@smoke shows technical request details for backend errors", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await page.route(/\/(?:modules\/)?edge-gateways(?:\?.*)?$/i, async (route, request) => {
if (request.method() !== "GET") {
await route.fallback();
return;
}
await route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({
success: false,
data: {
message: "Internal server error: SQLSTATE[42S22]: Column not found",
error_code: "EDGE_GATEWAY_VALIDATION_FAILED",
},
}),
});
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await expect(page.getByTestId("gateway-error-banner")).toContainText("Column not found");
await page.getByTestId("gateway-error-details").click();
await expect(page.getByTestId("gateway-error-details")).toContainText("Request:");
await expect(page.getByTestId("gateway-error-details")).toContainText("Status: HTTP 500");
});
test("@smoke saves module defaults on the canonical module workspace", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await expect(page.getByTestId("edge-gateway-module-config")).toBeVisible();
await page.getByTestId("gateway-module-release-channel").selectOption("canary");
await page.getByTestId("gateway-module-update-window").fill("03:00-05:00");
await page.getByTestId("gateway-module-save").click();
await expect(page.getByTestId("gateway-module-release-channel")).toHaveValue("canary");
await expect(page.getByTestId("gateway-module-update-window")).toHaveValue("03:00-05:00");
});
test("@smoke exposes a copy action for generated installer commands", async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: {
writeText: async (text) => {
window.__copiedInstallerCommand = text;
},
},
});
window.__copiedInstallerCommand = "";
});
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
edgeGateways: {
empty: true,
claimPollsRemaining: 99,
},
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await page.getByTestId("gateway-installer-department").selectOption("1");
await page.getByTestId("gateway-installer-label").fill("Copy Test Pi");
await page.getByTestId("gateway-installer-generate").click();
await expect(page.getByTestId("gateway-install-command")).toHaveValue(/install\.sh\?token=edge-install-token/);
await page.getByTestId("gateway-installer-copy").click();
await expect(page.locator("body")).toContainText("Installer command copied.");
await expect(page.getByTestId("gateway-installer-copy")).toContainText("Copied");
await expect
.poll(() => page.evaluate(() => window.__copiedInstallerCommand))
.toContain("install.sh?token=edge-install-token");
});
test("@smoke shows fleet usage statistics on the landing page", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await expect(page.getByTestId("gateway-fleet-usage")).toBeVisible();
await expect(page.getByTestId("gateway-fleet-usage-coverage")).toContainText("2 gateways");
await expect(page.getByTestId("gateway-fleet-usage-coverage")).toContainText("1 online");
await expect(page.getByTestId("gateway-fleet-usage-devices")).toContainText("1 online");
await expect(page.getByTestId("gateway-fleet-usage-devices")).toContainText("1 offline");
await expect(page.getByTestId("gateway-fleet-usage-bindings")).toContainText("1 overrides");
await expect(page.getByTestId("gateway-fleet-usage-bindings")).toContainText("1 cloud only");
await expect(page.getByTestId("gateway-fleet-usage-runtime")).toContainText("298 ms");
});
test("@smoke keeps watching long enough to open a delayed first gateway claim", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
edgeGateways: {
empty: true,
claimPollsRemaining: 8,
},
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await expect(page.getByTestId("gateway-fleet-landing")).toBeVisible();
await expect(page.getByTestId("gateway-fleet-landing").getByTestId("gateway-empty-state")).toContainText(
"No gateways"
);
await page.getByTestId("gateway-installer-department").selectOption("1");
await page.getByTestId("gateway-installer-label").fill("Canary Pi");
await page.getByTestId("gateway-installer-generate").click();
await expect(page.getByTestId("gateway-installer-status")).toBeVisible();
await expect.poll(() => page.getByTestId("gateway-installer-status-state").textContent()).toContain("Running");
await expect
.poll(async () => (await page.getByTestId("gateway-installer-status-step").textContent())?.trim())
.not.toBe("Waiting to start");
await expect(page).toHaveURL(/\/superuser\/configuration\/edgegateway\/703\/overview$/, { timeout: 20_000 });
await expect(page.getByTestId("gateway-detail-header")).toContainText("Canary Pi");
await expect(page.locator("body")).toContainText("Gateway connected.");
});
test("@smoke reopens an existing gateway when the installer reconnects it", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
edgeGateways: {
reuseClaimGatewayId: 701,
claimPollsRemaining: 8,
},
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await expect(page.getByTestId("gateway-fleet-landing")).toBeVisible();
await page.getByTestId("gateway-installer-department").selectOption("1");
await page.getByTestId("gateway-installer-label").fill("CPH Edge 01");
await page.getByTestId("gateway-installer-generate").click();
await expect(page.getByTestId("gateway-installer-status")).toBeVisible();
await expect.poll(() => page.getByTestId("gateway-installer-status-state").textContent()).toContain("Running");
await expect(page).toHaveURL(/\/superuser\/configuration\/edgegateway\/701\/overview$/, { timeout: 20_000 });
await expect(page.getByTestId("gateway-detail-header")).toContainText("CPH Edge 01");
await expect(page.locator("body")).toContainText("Gateway reconnected.");
});
test("@smoke surfaces pre-claim installer failures with diagnostics", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
edgeGateways: {
empty: true,
claimPollsRemaining: 8,
installSessionFailure: {
step: "START_STACK",
failurePoll: 4,
message:
"Job for truckwash-edge-gateway-stack.service failed because the control process exited with error code.",
diagnostics: [
{
name: "systemctl status",
output:
"Job for truckwash-edge-gateway-stack.service failed because the control process exited with error code.",
},
{
name: "journalctl",
output: "Compose rollout failed during build/startup",
},
],
},
},
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway");
await page.getByTestId("gateway-installer-department").selectOption("1");
await page.getByTestId("gateway-installer-label").fill("Broken Pi");
await page.getByTestId("gateway-installer-generate").click();
await expect(page.getByTestId("gateway-installer-status")).toBeVisible();
await expect
.poll(() => page.getByTestId("gateway-installer-status-state").textContent(), { timeout: 15_000 })
.toContain("Failed");
await expect(page.getByTestId("gateway-installer-status-step")).toContainText("Starting stack");
await expect(page.getByTestId("gateway-installer-status-error")).toContainText(
"Job for truckwash-edge-gateway-stack.service failed"
);
await page.getByTestId("gateway-installer-status-diagnostics").locator("summary").click();
await expect(page.getByTestId("gateway-installer-status-diagnostics")).toContainText("systemctl status");
await expect(page.getByTestId("gateway-installer-status-diagnostics")).toContainText(
"Compose rollout failed during build/startup"
);
await expect(page).toHaveURL(/\/superuser\/configuration\/edgegateway(?:\?.*)?$/);
await expect(page.getByTestId("gateway-fleet-landing").getByTestId("gateway-empty-state")).toContainText(
"No gateways"
);
});
test("@smoke updates metadata and rotates credentials from the settings view", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway/701/overview");
await expect(page.getByTestId("gateway-detail-header")).toContainText("CPH Edge 01");
await page.getByTestId("gateway-tab-settings").click();
await page.getByTestId("gateway-metadata-label").fill("CPH Edge Prime");
await page.getByTestId("gateway-metadata-save").click();
await expect(page.locator("body")).toContainText("Gateway metadata updated.");
await expect(page.getByTestId("gateway-detail-header")).toContainText("CPH Edge Prime");
await page.getByTestId("gateway-rotate-confirmation").fill("ROTATE");
await page.getByTestId("gateway-rotate-credentials").click();
await expect(page.getByTestId("gateway-credential-bundle")).toContainText("rotated-edge-agent-token");
await expect(page.getByTestId("gateway-credential-bundle")).toContainText("truckwash-edge-gateway-stack.service");
await expect(page.locator("body")).toContainText("Gateway credentials rotated.");
});
test("@smoke manages discovery, bindings, tasks, logs, statistics, uninstall, and delete", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway/701/inventory");
await page.getByTestId("gateway-discovery-trigger").click();
await expect(page.locator("body")).toContainText("Gateway operation DISCOVERY queued.");
await expect(page.getByTestId("gateway-inventory-table")).toContainText("shelly-plus-new", { timeout: 10_000 });
await page.getByTestId("gateway-binding-add").click();
await page.locator('[data-testid^="gateway-binding-relay-"]').last().fill("M-8");
await page.locator('[data-testid^="gateway-binding-device-"]').last().selectOption("shelly-plus-01");
await page.locator('[data-testid^="gateway-binding-channel-"]').last().selectOption("0");
await page.locator('[data-testid^="gateway-binding-fallback-"]').last().selectOption("CLOUD_ONLY");
await page.getByTestId("gateway-bindings-save").click();
await expect(page.locator("body")).toContainText("Relay bindings saved.");
await page.getByTestId("gateway-tab-tasks").click();
await page.getByTestId("gateway-update-target-version").fill("php-agent-v2.0");
await page.getByTestId("gateway-operation-update").click();
await expect(page.getByTestId("gateway-operations-list")).toContainText("UPDATE");
await expect(page.getByTestId("gateway-operation-events")).toContainText("Operation completed successfully");
await page.getByTestId("gateway-tab-logs").click();
await expect(page.getByTestId("gateway-logs-page")).toBeVisible();
await expect(page.getByTestId("gateway-logs-timeline")).toContainText("GATEWAY_OPERATION_QUEUED");
await page.getByTestId("gateway-tab-statistics").click();
await expect(page.getByTestId("gateway-statistics-page")).toBeVisible();
await expect(page.getByTestId("gateway-statistics-version")).toContainText("php-agent-v2.0");
await page.getByTestId("gateway-tab-overview").click();
await expect(page.getByTestId("gateway-overview-rollout")).toContainText("php-agent-v2.0");
await expect(page.getByTestId("gateway-overview-update-window")).toContainText("02:00-04:00");
await expect(page.getByTestId("gateway-overview-container-health")).toContainText("ONLINE");
await expect(page.getByTestId("gateway-overview-container-services")).toContainText("auto-updater");
await expect(page.getByTestId("gateway-overview-outbox")).toContainText("IN_SYNC");
await page.getByTestId("gateway-tab-tasks").click();
await page.getByTestId("gateway-operation-uninstall").click();
await expect(page.getByTestId("gateway-operations-list")).toContainText("UNINSTALL");
await page.getByTestId("gateway-tab-settings").click();
await page.getByTestId("gateway-delete-confirmation").fill("CPH Edge 01");
await page.getByTestId("gateway-delete").click();
await expect(page).toHaveURL(/\/superuser\/configuration\/edgegateway$/, { timeout: 8_000 });
await expect(page.locator("body")).toContainText("Gateway deleted.");
});
test("@smoke updates the integrated workspace after binding and scanner assignment changes", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/departments/1/gateways?tab=lanes");
await expect(page.getByTestId("department-hardware-panel-lanes")).toBeVisible();
await expect(page.getByTestId("department-lane-8")).toContainText("MISSING");
await page.getByTestId("department-hardware-tab-gateways").click();
await expect(page.getByTestId("edge-gateway-workspace")).toBeVisible();
await page.getByTestId("gateway-tab-inventory").click();
await page.getByTestId("gateway-binding-add").click();
await page.locator('[data-testid^="gateway-binding-relay-"]').last().fill("M-8");
await page.locator('[data-testid^="gateway-binding-device-"]').last().selectOption("shelly-plus-01");
await page.locator('[data-testid^="gateway-binding-channel-"]').last().selectOption("1");
await page.locator('[data-testid^="gateway-binding-fallback-"]').last().selectOption("PREFER_LOCAL");
await page.getByTestId("gateway-bindings-save").click();
await expect(page.locator("body")).toContainText("Relay bindings saved.");
await page.getByTestId("department-hardware-tab-lanes").click();
await page.getByTestId("department-hardware-refresh").click();
await expect(page.getByTestId("department-lane-8")).toContainText("READY");
await page.getByTestId("department-hardware-tab-scanners").click();
await expect(page.getByTestId("department-hardware-panel-scanners")).toBeVisible();
await page.getByTestId("department-scanner-lane-2").selectOption("8");
await page.getByTestId("department-scanner-save-2").click();
await expect(page.getByTestId("department-hardware-notice")).toContainText("lane assignment updated");
await expect(page.getByTestId("department-scanner-2")).toContainText("READY");
await page.getByTestId("department-scanner-rotate-2").click();
await expect(page.getByTestId("department-hardware-notice")).toContainText("API key rotated");
await expect(page.getByTestId("department-scanner-key-2")).toContainText("rotated-scanner-key-2");
});
test("@smoke cancels an in-progress gateway task so a replacement task can be queued", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway/701/tasks");
await page.getByTestId("gateway-operation-discovery").click();
await expect(page.getByTestId("gateway-operation-cancel")).toBeVisible();
await page.getByTestId("gateway-operation-cancel").click();
await expect(page.locator("body")).toContainText("DISCOVERY cancelled.");
await expect(page.getByTestId("gateway-operations-list")).toContainText("CANCELLED");
await page.getByTestId("gateway-update-target-version").fill("php-agent-v2.1");
await page.getByTestId("gateway-operation-update").click();
await expect(page.locator("body")).toContainText("Gateway operation UPDATE queued.");
});
test("@smoke opens the live terminal, streams output, and closes the shell session", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
await page.goto("/superuser/configuration/edgegateway/701/terminal");
await expect(page.getByTestId("gateway-terminal-page")).toBeVisible();
await expect(page.getByTestId("gateway-terminal-status")).toContainText(/connecting|open/i);
await expect(page.getByTestId("gateway-terminal-output")).toContainText("Connected to CPH Edge 01", {
timeout: 5_000,
});
await page.getByTestId("gateway-terminal-input").fill("pwd");
await page.getByTestId("gateway-terminal-send").click();
await expect(page.getByTestId("gateway-terminal-output")).toContainText("/opt/truckwash-edge-agent");
await page.getByTestId("gateway-terminal-close").click();
await expect(page.getByTestId("gateway-terminal-status")).toContainText(/closed|idle/i);
});
});