import { test, expect } from "@playwright/test"; import { start, stop, restart, baseUrl } from "./evcc"; import { startSimulator, stopSimulator, simulatorHost } from "./simulator"; import { expectModalVisible, expectModalHidden, addDemoCharger, newLoadpoint } from "./utils"; test.use({ baseURL: baseUrl() }); test.afterEach(async () => { await stop(); }); test.describe("fatal config handling", async () => { test("broken pv meter (using Shelly simulator)", async ({ page }) => { await startSimulator(); await start(); await page.goto("/#/config"); // create meter await page.getByRole("button", { name: "Add solar or battery" }).click(); const meterModal = page.getByTestId("meter-modal"); await meterModal.getByRole("button", { name: "Add solar meter" }).click(); await meterModal.getByLabel("Title").fill("North Roof"); await meterModal.getByLabel("Manufacturer").selectOption("Shelly 1PM"); await meterModal.getByLabel("IP address or hostname").fill(simulatorHost()); await meterModal.getByRole("button", { name: "Validate & save" }).click(); await expectModalHidden(meterModal); await expect(page.getByTestId("pv")).toBeVisible(); await expect(page.getByTestId("pv")).toContainText("North Roof"); // break meter await stopSimulator(); await restart(); await page.reload(); // remove meter await expect(page.getByTestId("fatal-error")).toBeVisible(); // dismiss hides the banner; reload restores it while the error persists await page.getByRole("button", { name: "Dismiss" }).click(); await expect(page.getByTestId("fatal-error")).not.toBeVisible(); await page.reload(); await expect(page.getByTestId("fatal-error")).toBeVisible(); await expect(page.getByTestId("pv")).toBeVisible(); await page.getByTestId("pv").getByRole("button", { name: "edit" }).click(); await expectModalVisible(meterModal); await meterModal.getByRole("button", { name: "Delete" }).click(); await expectModalHidden(meterModal); await expect(page.getByTestId("pv")).toHaveCount(0); // restart and check again await restart(); await page.reload(); await expect(page.getByTestId("pv")).toHaveCount(0); await expect(page.getByTestId("fatal-error")).not.toBeVisible(); }); test("broken loadpoint meter (using Shelly simulator)", async ({ page }) => { await startSimulator(); await start(); await page.goto("/#/config"); const lpModal = page.getByTestId("loadpoint-modal"); // create loadpoint with demo charger and shelly meter that will break await newLoadpoint(page, "Test Carport"); await addDemoCharger(page); // add shelly meter await lpModal.getByRole("link", { name: "Advanced configuration" }).click(); await lpModal.getByRole("button", { name: "Add dedicated energy meter" }).click(); const meterModal = page.getByTestId("meter-modal"); await expectModalVisible(meterModal); await meterModal.getByLabel("Manufacturer").selectOption("Shelly 1PM"); await meterModal.getByLabel("IP address or hostname").fill(simulatorHost()); await meterModal.getByRole("button", { name: "Validate & save" }).click(); await expectModalHidden(meterModal); await expectModalVisible(lpModal); await lpModal.getByRole("button", { name: "Save" }).click(); await expectModalHidden(lpModal); await expect(page.getByTestId("loadpoint")).toContainText("Test Carport"); await page.waitForLoadState("networkidle"); // break meter await stopSimulator(); await restart(); await page.reload(); // verify loadpoint still visible with error await expect(page.getByTestId("fatal-error")).toBeVisible(); await expect(page.getByTestId("fatal-error")).toContainText( /meter: .+? cannot create template .+?: cannot create meter type 'template:shelly-1pm': cannot create meter type 'shelly'/ ); await expect(page.getByTestId("fatal-error")).toContainText( /loadpoint: .+? missing charge meter instance/ ); await expect(page.getByTestId("loadpoint")).toBeVisible(); // open modal and delete meter await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click(); await expectModalVisible(lpModal); await expect(lpModal.getByText("Shelly 1PM")).toBeVisible(); await lpModal.getByText("Shelly 1PM").click(); await expectModalVisible(meterModal); await meterModal.getByRole("button", { name: "Delete" }).click(); await expectModalHidden(meterModal); await expectModalVisible(lpModal); await lpModal.getByRole("button", { name: "Save" }).click(); await expectModalHidden(lpModal); await page.waitForLoadState("networkidle"); // restart and verify await restart(); await page.reload(); await expect(page.getByTestId("loadpoint")).toBeVisible(); await expect(page.getByTestId("fatal-error")).not.toBeVisible(); // error should be gone }); test("broken grid meter (using Shelly simulator)", async ({ page }) => { // setup test data for mock api await startSimulator(); await start(); await page.goto("/#/config"); // create grid meter await page.getByRole("button", { name: "Add grid meter" }).click(); const meterModal = page.getByTestId("meter-modal"); await meterModal.getByLabel("Manufacturer").selectOption("Shelly 1PM"); await meterModal.getByLabel("IP address or hostname").fill(simulatorHost()); await meterModal.getByRole("button", { name: "Validate & save" }).click(); await expectModalHidden(meterModal); await expect(page.getByTestId("grid")).toBeVisible(); // break meter await stopSimulator(); await restart(); await page.reload(); // verify grid meter still visible with error await expect(page.getByTestId("fatal-error")).toBeVisible(); await expect(page.getByTestId("grid")).toBeVisible(); // open modal and delete meter await page.getByTestId("grid").getByRole("button", { name: "edit" }).click(); await expectModalVisible(meterModal); await meterModal.getByRole("button", { name: "Delete" }).click(); await expectModalHidden(meterModal); await expect(page.getByTestId("grid")).toHaveCount(0); // restart and verify await restart(); await page.reload(); await expect(page.getByTestId("grid")).toHaveCount(0); await expect(page.getByTestId("fatal-error")).not.toBeVisible(); // error should be gone }); });