Files
Home-Assistant/custom_components/maintenance_supporter/frontend-src/e2e-test.mjs
T
2026-07-08 10:43:39 -04:00

198 lines
9.9 KiB
JavaScript

/**
* Comprehensive E2E test suite for the Maintenance Supporter panel.
*
* Prerequisites:
* docker compose up -d # ha-maint + playwright
* docker restart playwright-server # clean session
*
* Usage:
* cd custom_components/maintenance_supporter/frontend-src
* node e2e-test.mjs # auto-fetches refresh token
* HA_REFRESH_TOKEN=... node e2e-test.mjs # explicit token
*/
import { setup, ws, openEditDialog, readDialogSelects, readDialogFields, cleanup } from "./e2e-helpers.mjs";
let passed = 0, failed = 0, skipped = 0;
function check(name, ok) {
if (ok) { passed++; console.log(" PASS " + name); }
else { failed++; console.log(" FAIL " + name); }
}
async function runTest(name, fn) {
console.log("\n=== " + name + " ===");
try { await fn(); }
catch (e) { failed++; console.log(" ERROR: " + e.message); }
}
// ═══════════════════════════════════════════════════════════════
// DESKTOP TESTS
// ═══════════════════════════════════════════════════════════════
const { browser, ctx, page } = await setup();
const ENTRY = (await ws(page, { type: "maintenance_supporter/objects" })).objects[0].entry_id;
await runTest("Sort dropdown exists with 4 modes", async () => {
const selects = await page.evaluate(() => {
const ha = document.querySelector("home-assistant");
const main = ha?.shadowRoot?.querySelector("home-assistant-main");
const drawer = main?.shadowRoot?.querySelector("ha-drawer");
const resolver = drawer?.querySelector("partial-panel-resolver");
const custom = resolver?.querySelector("ha-panel-custom");
const panel = custom?.querySelector("maintenance-supporter-panel");
const sr = panel?.shadowRoot;
const filterBar = sr?.querySelector(".filter-bar");
if (!filterBar) return [];
return [...filterBar.querySelectorAll("select")].map(s => ({
options: [...s.options].map(o => o.value),
}));
});
const sortSelect = selects.find(s => s.options.includes("due_date"));
check("sort dropdown found", !!sortSelect);
check("has 4 sort options", sortSelect?.options.length === 4);
});
await runTest("All Objects view via KPI click", async () => {
const clicked = await page.evaluate(() => {
const ha = document.querySelector("home-assistant");
const sr = ha?.shadowRoot?.querySelector("home-assistant-main")?.shadowRoot?.querySelector("ha-drawer")?.querySelector("partial-panel-resolver")?.querySelector("ha-panel-custom")?.querySelector("maintenance-supporter-panel")?.shadowRoot;
const item = sr?.querySelector(".stat-item.clickable");
if (item) { item.click(); return true; }
return false;
});
check("KPI clickable", clicked);
await page.waitForTimeout(1000);
const cards = await page.evaluate(() => {
const ha = document.querySelector("home-assistant");
const sr = ha?.shadowRoot?.querySelector("home-assistant-main")?.shadowRoot?.querySelector("ha-drawer")?.querySelector("partial-panel-resolver")?.querySelector("ha-panel-custom")?.querySelector("maintenance-supporter-panel")?.shadowRoot;
return sr?.querySelectorAll(".object-card")?.length ?? 0;
});
check("object cards rendered", cards > 0);
});
// Reload panel to reset view state after All Objects test
await page.goto("http://homeassistant-dev:8123/maintenance-supporter");
await page.waitForTimeout(5000);
await runTest("Select dropdowns preserve values on edit", async () => {
const objs = await ws(page, { type: "maintenance_supporter/objects" });
let triggerTask = null, triggerEntry = null;
for (const o of objs.objects) {
for (const t of o.tasks) {
if (t.trigger_config?.type) { triggerTask = t; triggerEntry = o.entry_id; break; }
}
if (triggerTask) break;
}
if (!triggerTask) { skipped++; console.log(" SKIP: no sensor-trigger task"); return; }
await openEditDialog(page, triggerEntry, triggerTask);
const selects = await readDialogSelects(page);
for (const s of selects) {
check(s.label + " selected matches value", s.match);
}
await page.keyboard.press("Escape");
});
await runTest("Last performed field in task dialog", async () => {
const cr = await ws(page, {
type: "maintenance_supporter/task/create", entry_id: ENTRY,
name: "E2E LP Test", task_type: "service", schedule_type: "time_based",
interval_days: 30, last_performed: "2025-11-01", enabled: true,
});
// Reload so panel picks up the new task
await page.goto("http://homeassistant-dev:8123/maintenance-supporter");
await page.waitForTimeout(5000);
const obj = await ws(page, { type: "maintenance_supporter/object", entry_id: ENTRY });
const task = obj.tasks.find(t => t.id === cr.task_id);
await openEditDialog(page, ENTRY, task);
const fields = await readDialogFields(page);
console.log(" Fields found:", fields.length, fields.map(f => f.type + ":" + f.label?.slice(0, 20)).join(", "));
const dateField = fields.find(f => f.type === "date");
check("date field exists", !!dateField);
check("date value correct", dateField?.value === "2025-11-01");
await page.keyboard.press("Escape");
await ws(page, { type: "maintenance_supporter/task/delete", entry_id: ENTRY, task_id: cr.task_id });
});
await runTest("Task create with enabled field (Bug #14 regression)", async () => {
const r = await ws(page, {
type: "maintenance_supporter/task/create", entry_id: ENTRY,
name: "E2E Enabled Test", task_type: "cleaning", schedule_type: "time_based",
interval_days: 30, enabled: true,
});
check("create with enabled succeeds", !!r.task_id);
if (r.task_id) await ws(page, { type: "maintenance_supporter/task/delete", entry_id: ENTRY, task_id: r.task_id });
});
await runTest("Reset with date field (Bug #12 regression)", async () => {
const objs = await ws(page, { type: "maintenance_supporter/objects" });
const t = objs.objects[0].tasks[0];
try {
await ws(page, { type: "maintenance_supporter/task/reset", entry_id: ENTRY, task_id: t.id, date: "2026-04-01" });
check("reset with date succeeds", true);
} catch (e) {
check("reset with date succeeds", false);
}
});
await runTest("Desktop header bar hidden on overview", async () => {
await page.goto("http://homeassistant-dev:8123/maintenance-supporter");
await page.waitForTimeout(3000);
const headerVisible = await page.evaluate(() => {
const ha = document.querySelector("home-assistant");
const sr = ha?.shadowRoot?.querySelector("home-assistant-main")?.shadowRoot?.querySelector("ha-drawer")?.querySelector("partial-panel-resolver")?.querySelector("ha-panel-custom")?.querySelector("maintenance-supporter-panel")?.shadowRoot;
const header = sr?.querySelector(".header");
if (!header) return false;
const style = window.getComputedStyle(header);
return style.display !== "none" && header.offsetHeight > 0;
});
check("header NOT visible on desktop overview", !headerVisible);
});
await cleanup(browser, ctx);
// Reconnect for timezone + mobile tests
import { chromium } from "playwright";
const browser2 = await chromium.connect("ws://localhost:3000");
// ═══════════════════════════════════════════════════════════════
// TIMEZONE + MOBILE TESTS (reuse same browser connection)
// ═══════════════════════════════════════════════════════════════
await runTest("Date formatting in US timezone (Bug #21)", async () => {
const tzCtx = await browser2.newContext({ viewport: { width: 800, height: 600 }, locale: "en-US", timezoneId: "America/New_York" });
const tzPage = await tzCtx.newPage();
await tzPage.goto("about:blank");
const r = await tzPage.evaluate(() => ({
utc: new Date("2025-11-01").toLocaleDateString("en-US"),
local: new Date("2025-11-01T00:00:00").toLocaleDateString("en-US"),
}));
check("UTC parse shows wrong date (Oct 31)", r.utc.includes("10/31"));
check("Local parse shows correct date (Nov)", r.local.includes("11/"));
await tzCtx.close();
});
await runTest("Mobile hamburger menu button", async () => {
const mobCtx = await browser2.newContext({ viewport: { width: 375, height: 812 }, locale: "en-US", colorScheme: "dark", isMobile: true });
const mobPage = await mobCtx.newPage();
await mobPage.goto("http://homeassistant-dev:8123");
await mobPage.waitForTimeout(1000);
await mobPage.evaluate((r) => localStorage.setItem("hassTokens", JSON.stringify({
hassUrl: "http://homeassistant-dev:8123", clientId: "http://homeassistant-dev:8123/",
refresh_token: r, access_token: "", token_type: "Bearer", expires_in: 1800, expires: 0,
})), process.env.HA_REFRESH_TOKEN);
await mobPage.goto("http://homeassistant-dev:8123/maintenance-supporter");
await mobPage.waitForTimeout(7000);
const hasMenu = await mobPage.evaluate(() => {
const ha = document.querySelector("home-assistant");
const sr = ha?.shadowRoot?.querySelector("home-assistant-main")?.shadowRoot?.querySelector("ha-drawer")?.querySelector("partial-panel-resolver")?.querySelector("ha-panel-custom")?.querySelector("maintenance-supporter-panel")?.shadowRoot;
return !!sr?.querySelector("ha-menu-button");
});
check("ha-menu-button visible on mobile", hasMenu);
await mobCtx.close();
});
await cleanup(browser2, { close() {} });
// ═══════════════════════════════════════════════════════════════
console.log("\n" + "═".repeat(50));
console.log(`RESULTS: ${passed} passed, ${failed} failed, ${skipped} skipped`);
if (failed > 0) { console.log("SOME TESTS FAILED!"); process.exit(1); }
else console.log("ALL TESTS PASSED!");