198 lines
9.9 KiB
JavaScript
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!");
|