Compare commits

...

2 Commits

Author SHA1 Message Date
Aidan Timson e05a9fd12b Fix 2026-07-07 16:25:48 +01:00
Aidan Timson f17d91b1a4 Add panel route e2e coverage 2026-07-07 15:44:14 +01:00
3 changed files with 139 additions and 44 deletions
+8 -41
View File
@@ -7,6 +7,7 @@
import { test, expect, type Page } from "@playwright/test";
import type { MoreInfoView } from "../../src/dialogs/more-info/const";
import { PANEL_TIMEOUT, QUICK_TIMEOUT, SHELL_TIMEOUT } from "./helpers";
import { e2ePanelRouteAssertions } from "./app/src/ha-test-panels";
/**
* Each More info view renders one root element inside the dialog, plus one or
@@ -205,48 +206,14 @@ test.describe("App shell", () => {
// ---------------------------------------------------------------------------
test.describe("Panel navigation", () => {
test("navigates to lovelace dashboard", async ({ page }) => {
await goToPanel(page, "/lovelace");
await expect(
page.locator("ha-panel-lovelace, hui-root").first()
).toBeAttached({
timeout: PANEL_TIMEOUT,
for (const [path, element] of e2ePanelRouteAssertions) {
test(`renders registered panel ${path}`, async ({ page }) => {
await goToPanel(page, path);
await expect(page.locator(element).first()).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
});
test("navigates to energy panel", async ({ page }) => {
await goToPanel(page, "/energy");
await expect(
page.locator("ha-panel-energy, energy-view").first()
).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
test("navigates to map panel", async ({ page }) => {
await goToPanel(page, "/map");
await expect(
page.locator("ha-panel-lovelace, hui-root").first()
).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
test("navigates to history panel", async ({ page }) => {
await goToPanel(page, "/history");
await expect(
page.locator("ha-panel-history, history-panel").first()
).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
test("navigates to profile panel", async ({ page }) => {
await goToPanel(page, "/profile");
await expect(
page.locator("ha-panel-profile, ha-config-user-profile").first()
).toBeAttached({ timeout: PANEL_TIMEOUT });
});
}
});
// ---------------------------------------------------------------------------
+96 -2
View File
@@ -1,12 +1,25 @@
import type { Panels } from "../../../../src/types";
import type { PanelInfo } from "../../../../src/types";
export const e2eTestPanels: Panels = {
interface E2ETestPanelInfo extends PanelInfo {
testSelector?: string;
}
export const e2eTestPanels: Record<string, E2ETestPanelInfo> = {
home: {
component_name: "home",
icon: "mdi:home",
title: "home",
config: null,
url_path: "home",
testSelector: "ha-panel-home",
},
lovelace: {
component_name: "lovelace",
icon: "mdi:view-dashboard",
title: "home",
config: { mode: "storage" },
url_path: "lovelace",
testSelector: "ha-panel-lovelace, hui-root",
},
map: {
component_name: "lovelace",
@@ -14,6 +27,7 @@ export const e2eTestPanels: Panels = {
title: "map",
config: { mode: "storage" },
url_path: "map",
testSelector: "ha-panel-lovelace, hui-root",
},
energy: {
component_name: "energy",
@@ -21,6 +35,7 @@ export const e2eTestPanels: Panels = {
title: "energy",
config: null,
url_path: "energy",
testSelector: "ha-panel-energy, energy-view",
},
history: {
component_name: "history",
@@ -28,6 +43,70 @@ export const e2eTestPanels: Panels = {
title: "history",
config: null,
url_path: "history",
testSelector: "ha-panel-history, history-panel",
},
logbook: {
component_name: "logbook",
icon: "mdi:format-list-bulleted-type",
title: "logbook",
config: null,
url_path: "logbook",
testSelector: "ha-panel-logbook",
},
calendar: {
component_name: "calendar",
icon: "mdi:calendar",
title: "calendar",
config: null,
url_path: "calendar",
testSelector: "ha-panel-calendar",
},
todo: {
component_name: "todo",
icon: "mdi:clipboard-list",
title: "todo",
config: null,
url_path: "todo",
},
"media-browser": {
component_name: "media-browser",
icon: "mdi:play-box-multiple",
title: "media_browser",
config: null,
url_path: "media-browser",
testSelector: "ha-panel-media-browser",
},
light: {
component_name: "light",
icon: "mdi:lightbulb-group",
title: "light",
config: null,
url_path: "light",
testSelector: "ha-panel-light",
},
climate: {
component_name: "climate",
icon: "mdi:thermostat",
title: "climate",
config: null,
url_path: "climate",
testSelector: "ha-panel-climate",
},
maintenance: {
component_name: "maintenance",
icon: "mdi:wrench-clock",
title: "maintenance",
config: null,
url_path: "maintenance",
testSelector: "ha-panel-maintenance",
},
iframe: {
component_name: "iframe",
icon: "mdi:web",
title: "iframe",
config: { url: "/static/blank.html" },
url_path: "iframe",
testSelector: "ha-panel-iframe",
},
config: {
component_name: "config",
@@ -42,5 +121,20 @@ export const e2eTestPanels: Panels = {
title: null,
config: null,
url_path: "profile",
testSelector: "ha-panel-profile, ha-config-user-profile",
},
notfound: {
component_name: "notfound",
icon: null,
title: null,
config: null,
url_path: "notfound",
testSelector: "ha-panel-notfound",
},
};
export const e2ePanelRouteAssertions = new Map<string, string>(
Object.values(e2eTestPanels).flatMap((panel): [string, string][] =>
panel.testSelector ? [[`/${panel.url_path}`, panel.testSelector]] : []
)
);
+35 -1
View File
@@ -2,6 +2,7 @@ import { customElement } from "lit/decorators";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import { navigate } from "../../../../src/common/navigate";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import type { LogbookStreamMessage } from "../../../../src/data/logbook";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistantAppEl } from "../../../../src/layouts/home-assistant";
import type { HomeAssistant } from "../../../../src/types";
@@ -35,6 +36,17 @@ import { mockUpdate } from "../../../../demo/src/stubs/update";
import { e2eTestPanels } from "./ha-test-panels";
import { scenarios } from "./scenarios";
const MEDIA_BROWSER_ROOT = {
title: "Media",
media_content_id: "media-source://media_source",
media_content_type: "app",
media_class: "directory",
can_play: false,
can_expand: true,
can_search: false,
children: [],
};
declare global {
interface Window {
__mockHass: MockHomeAssistant;
@@ -102,9 +114,31 @@ export class HaTest extends HomeAssistantAppEl {
mockIcons(hass);
mockPersistentNotification(hass);
mockSearch(hass);
hass.mockWS("calendar/event/subscribe", (_msg, _currentHass, onChange) => {
onChange?.({ events: [] });
return () => undefined;
});
hass.mockWS("logbook/event_stream", (_msg, _currentHass, onChange) => {
const message: LogbookStreamMessage = { events: [] };
onChange?.(message);
return () => undefined;
});
hass.mockWS("config/auth/list", () => []);
hass.mockWS("trace/contexts", () => ({}));
hass.mockWS("media_source/browse_media", () => MEDIA_BROWSER_ROOT);
// Load default entities from the sections config
hass.addEntities(energyEntities());
hass.addEntities([
...energyEntities(),
{
entity_id: "todo.shopping_list",
state: "0",
attributes: {
friendly_name: "Shopping list",
supported_features: 15,
},
},
]);
Promise.all([Promise.resolve(demoSections), localizePromise]).then(
([conf, localize]) => {
hass.addEntities(conf.entities(localize));