chore: run playwright tests in parallel (#13809)

This commit is contained in:
Michael Geers 2024-05-08 11:49:04 +02:00 • committed by GitHub
parent b9738abf15
commit f0e313c6d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 346 additions and 247 deletions

View file

@ -177,11 +177,6 @@ jobs:
integration:
name: Integration
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2]
shardTotal: [2]
steps:
- uses: actions/checkout@v4
@ -224,7 +219,7 @@ jobs:
run: npx playwright install --with-deps chromium
- name: Run tests
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
run: npx playwright test
# - name: Run tests
# uses: docker://mcr.microsoft.com/playwright:v1.34.3-jammy
@ -234,6 +229,6 @@ jobs:
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.shardIndex }}
name: playwright-report
path: playwright-report/
retention-days: 14

View file

@ -4,22 +4,21 @@ import { defineConfig, devices } from "@playwright/test";
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: "./tests",
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
timeout: 15000, // 15s (default 30s)
expect: { timeout: 2500 }, // 2.5s (default 5s)
workers: 1, // run testfiles serially to avoid port and database conflicts
reporter: "html",
use: {
baseURL: "http://127.0.0.1:7070",
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
testDir: "./tests",
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
timeout: 15000, // 15s (default 30s)
expect: { timeout: 2500 }, // 2.5s (default 5s)
workers: process.env.CI ? 3 : 8,
reporter: "html",
use: {
baseURL: "http://127.0.0.1:7070",
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeEach(async ({ page }) => {
await start("basics.evcc.yaml");

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start("basics.evcc.yaml", "password.sql");

View file

@ -0,0 +1,85 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorUrl, simulatorHost } from "./simulator";
const CONFIG_EMPTY = "config-empty.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start(CONFIG_EMPTY, "password.sql");
await startSimulator();
});
test.afterAll(async () => {
await stop();
await stopSimulator();
});
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
}
async function enableExperimental(page) {
await page
.getByTestId("generalconfig-experimental")
.getByRole("link", { name: "change" })
.click();
await page.getByLabel("Experimental 🧪").click();
await page.getByRole("button", { name: "Close" }).click();
}
test.describe("meters", async () => {
test("create, edit and remove battery meter", async ({ page }) => {
// setup test data for mock openems api
await page.goto(simulatorUrl());
await page.getByLabel("Battery Power").fill("-2500");
await page.getByLabel("Battery SoC").fill("75");
await page.getByRole("button", { name: "Apply changes" }).click();
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("battery")).toHaveCount(0);
// create #1
await page.getByRole("button", { name: "Add solar or battery" }).click();
const meterModal = page.getByTestId("meter-modal");
await meterModal.getByRole("button", { name: "Add battery meter" }).click();
await meterModal.getByLabel("Manufacturer").selectOption("OpenEMS");
await meterModal.getByLabel("IP address or hostname").fill(simulatorHost());
await expect(meterModal.getByRole("button", { name: "Validate & save" })).toBeVisible();
await meterModal.getByRole("link", { name: "validate" }).click();
await expect(meterModal.getByText("SoC: 75.0%")).toBeVisible();
await expect(meterModal.getByText("Power: -2.5 kW")).toBeVisible();
await meterModal.getByRole("button", { name: "Save" }).click();
await expect(page.getByTestId("battery")).toBeVisible(1);
await expect(page.getByTestId("battery")).toContainText("openems");
// edit #1
await page.getByTestId("battery").getByRole("button", { name: "edit" }).click();
await meterModal.getByLabel("Battery capacity in kWh").fill("20");
await meterModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("battery")).toBeVisible(1);
await expect(page.getByTestId("battery")).toContainText("openems");
await expect(page.getByTestId("battery").getByText("SoC: 75.0%")).toBeVisible();
await expect(page.getByTestId("battery").getByText("Power: -2.5 kW")).toBeVisible();
await expect(page.getByTestId("battery").getByText("Capacity: 20.0 kWh")).toBeVisible();
// restart and check in main ui
await restart(CONFIG_EMPTY);
await page.goto("/");
await page.getByTestId("visualization").click();
await expect(page.getByTestId("energyflow")).toContainText("Battery charging75%2.5 kW");
// delete #1
await page.goto("/#/config");
await page.getByTestId("battery").getByRole("button", { name: "edit" }).click();
await meterModal.getByRole("button", { name: "Delete" }).click();
await expect(page.getByTestId("battery")).toHaveCount(0);
});
});

View file

@ -0,0 +1,132 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, cleanRestart, baseUrl } from "./evcc";
const CONFIG_EMPTY = "config-empty.evcc.yaml";
const CONFIG_WITH_VEHICLE = "config-with-vehicle.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start(CONFIG_EMPTY, "password.sql");
});
test.afterAll(async () => {
await stop();
});
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
}
async function enableExperimental(page) {
await page
.getByTestId("generalconfig-experimental")
.getByRole("link", { name: "change" })
.click();
await page.getByLabel("Experimental 🧪").click();
await page.getByRole("button", { name: "Close" }).click();
}
test.describe("vehicles", async () => {
test("create, edit and delete vehicles", async ({ page }) => {
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("vehicle")).toHaveCount(0);
const vehicleModal = page.getByTestId("vehicle-modal");
// create #1
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Green Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(1);
// create #2
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Yellow Van");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Green Car/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Yellow Van/);
// edit #1
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
await expect(vehicleModal.getByLabel("Title")).toHaveValue("Green Car");
await vehicleModal.getByLabel("Title").fill("Fancy Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Fancy Car/);
// delete #1
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
await vehicleModal.getByRole("button", { name: "Delete" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(1);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Yellow Van/);
// delete #2
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
await vehicleModal.getByRole("button", { name: "Delete" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(0);
});
test("config should survive restart", async ({ page }) => {
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("vehicle")).toHaveCount(0);
const vehicleModal = page.getByTestId("vehicle-modal");
// create #1 & #2
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Green Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Yellow Van");
await vehicleModal.getByLabel("car").click();
await vehicleModal.getByLabel("van").check();
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
// restart evcc
await restart(CONFIG_EMPTY);
await page.reload();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Green Car/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Yellow Van/);
});
test("mixed config (yaml + db)", async ({ page }) => {
await cleanRestart(CONFIG_WITH_VEHICLE, "password.sql");
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("vehicle")).toHaveCount(1);
const vehicleModal = page.getByTestId("vehicle-modal");
// create #1
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Green Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/YAML Bike/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Green Car/);
});
});

View file

@ -1,9 +1,9 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, cleanRestart } from "./evcc";
import { startSimulator, stopSimulator, SIMULATOR_URL, SIMULATOR_HOST } from "./simulator";
import { start, stop, baseUrl } from "./evcc";
const CONFIG_EMPTY = "config-empty.evcc.yaml";
const CONFIG_WITH_VEHICLE = "config-with-vehicle.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start(CONFIG_EMPTY, "password.sql");
@ -42,172 +42,6 @@ test.describe("basics", async () => {
});
});
test.describe("vehicles", async () => {
test("create, edit and delete vehicles", async ({ page }) => {
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("vehicle")).toHaveCount(0);
const vehicleModal = page.getByTestId("vehicle-modal");
// create #1
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Green Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(1);
// create #2
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Yellow Van");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Green Car/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Yellow Van/);
// edit #1
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
await expect(vehicleModal.getByLabel("Title")).toHaveValue("Green Car");
await vehicleModal.getByLabel("Title").fill("Fancy Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Fancy Car/);
// delete #1
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
await vehicleModal.getByRole("button", { name: "Delete" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(1);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Yellow Van/);
// delete #2
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
await vehicleModal.getByRole("button", { name: "Delete" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(0);
});
test("config should survive restart", async ({ page }) => {
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("vehicle")).toHaveCount(0);
const vehicleModal = page.getByTestId("vehicle-modal");
// create #1 & #2
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Green Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Yellow Van");
await vehicleModal.getByLabel("car").click();
await vehicleModal.getByLabel("van").check();
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
// restart evcc
await restart(CONFIG_EMPTY);
await page.reload();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/Green Car/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Yellow Van/);
});
test("mixed config (yaml + db)", async ({ page }) => {
await cleanRestart(CONFIG_WITH_VEHICLE, "password.sql");
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("vehicle")).toHaveCount(1);
const vehicleModal = page.getByTestId("vehicle-modal");
// create #1
await page.getByTestId("add-vehicle").click();
await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
await vehicleModal.getByLabel("Title").fill("Green Car");
await vehicleModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/YAML Bike/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Green Car/);
});
});
test.describe("meters", async () => {
test.beforeAll(async () => {
await startSimulator();
});
test.afterAll(async () => {
await stopSimulator();
});
test("create, edit and remove battery meter", async ({ page }) => {
// setup test data for mock openems api
await page.goto(SIMULATOR_URL);
await page.getByLabel("Battery Power").fill("-2500");
await page.getByLabel("Battery SoC").fill("75");
await page.getByRole("button", { name: "Apply changes" }).click();
await page.goto("/#/config");
await login(page);
await enableExperimental(page);
await expect(page.getByTestId("battery")).toHaveCount(0);
// create #1
await page.getByRole("button", { name: "Add solar or battery" }).click();
const meterModal = page.getByTestId("meter-modal");
await meterModal.getByRole("button", { name: "Add battery meter" }).click();
await meterModal.getByLabel("Manufacturer").selectOption("OpenEMS");
await meterModal.getByLabel("IP address or hostname").fill(SIMULATOR_HOST);
await expect(meterModal.getByRole("button", { name: "Validate & save" })).toBeVisible();
await meterModal.getByRole("link", { name: "validate" }).click();
await expect(meterModal.getByText("SoC: 75.0%")).toBeVisible();
await expect(meterModal.getByText("Power: -2.5 kW")).toBeVisible();
await meterModal.getByRole("button", { name: "Save" }).click();
await expect(page.getByTestId("battery")).toBeVisible(1);
await expect(page.getByTestId("battery")).toContainText("openems");
// edit #1
await page.getByTestId("battery").getByRole("button", { name: "edit" }).click();
await meterModal.getByLabel("Battery capacity in kWh").fill("20");
await meterModal.getByRole("button", { name: "Validate & save" }).click();
await expect(page.getByTestId("battery")).toBeVisible(1);
await expect(page.getByTestId("battery")).toContainText("openems");
await expect(page.getByTestId("battery").getByText("SoC: 75.0%")).toBeVisible();
await expect(page.getByTestId("battery").getByText("Power: -2.5 kW")).toBeVisible();
await expect(page.getByTestId("battery").getByText("Capacity: 20.0 kWh")).toBeVisible();
// restart and check in main ui
await restart(CONFIG_EMPTY);
await page.goto("/");
await page.getByTestId("visualization").click();
await expect(page.getByTestId("energyflow")).toContainText("Battery charging75%2.5 kW");
// delete #1
await page.goto("/#/config");
await page.getByTestId("battery").getByRole("button", { name: "edit" }).click();
await meterModal.getByRole("button", { name: "Delete" }).click();
await expect(page.getByTestId("battery")).toHaveCount(0);
});
});
test.describe("general", async () => {
test("change site title", async ({ page }) => {
// initial value on main ui

View file

@ -2,13 +2,25 @@ import fs from "fs";
import waitOn from "wait-on";
import axios from "axios";
import { exec, execSync } from "child_process";
import playwrightConfig from "../playwright.config.js";
import os from "os";
import path from "path";
const BASE_URL = playwrightConfig.use.baseURL;
const DB_PATH = "./evcc.db";
const BINARY = "./evcc";
function port() {
const index = process.env.TEST_WORKER_INDEX * 1;
return 11000 + index;
}
export function baseUrl() {
return `http://localhost:${port()}`;
}
function dbPath() {
const file = `evcc-${port()}.db`;
return path.join(os.tmpdir(), file);
}
export async function start(config, sqlDumps) {
await _clean();
if (sqlDumps) {
@ -39,14 +51,17 @@ export async function cleanRestart(config, sqlDumps) {
async function _restoreDatabase(sqlDumps) {
const dumps = Array.isArray(sqlDumps) ? sqlDumps : [sqlDumps];
for (const dump of dumps) {
console.log("loading database", dump);
execSync(`sqlite3 ${DB_PATH} < tests/${dump}`);
console.log("loading database", dbPath(), dump);
execSync(`sqlite3 ${dbPath()} < tests/${dump}`);
}
}
async function _start(config) {
const configFile = config.includes("/") ? config : `tests/${config}`;
console.log("starting evcc", { config });
const instance = exec(`EVCC_DATABASE_DSN=${DB_PATH} ${BINARY} --config tests/${config}`);
const instance = exec(
`EVCC_NETWORK_PORT=${port()} EVCC_DATABASE_DSN=${dbPath()} ${BINARY} --config ${configFile}`
);
instance.stdout.pipe(process.stdout);
instance.stderr.pipe(process.stderr);
instance.on("exit", (code) => {
@ -54,20 +69,21 @@ async function _start(config) {
throw new Error("evcc terminated", code);
}
});
await waitOn({ resources: [BASE_URL] });
await waitOn({ resources: [baseUrl()] });
}
async function _stop() {
console.log("shutting down evcc");
const res = await axios.post(BASE_URL + "/api/auth/login", { password: "secret" });
const res = await axios.post(`${baseUrl()}/api/auth/login`, { password: "secret" });
console.log(res.status, res.statusText);
const cookie = res.headers["set-cookie"];
await axios.post(BASE_URL + "/api/system/shutdown", {}, { headers: { cookie } });
await axios.post(`${baseUrl()}/api/system/shutdown`, {}, { headers: { cookie } });
}
async function _clean() {
if (fs.existsSync(DB_PATH)) {
console.log("delete database", DB_PATH);
fs.unlinkSync(DB_PATH);
const db = dbPath();
if (fs.existsSync(db)) {
console.log("delete database", db);
fs.unlinkSync(db);
}
}

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start("heating.evcc.yaml", "password.sql");

View file

@ -1,8 +1,8 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { startSimulator, stopSimulator, SIMULATOR_URL } from "./simulator";
import { start, stop, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorUrl, simulatorConfig } from "./simulator";
const CONFIG = "simulator.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await startSimulator();
@ -12,9 +12,9 @@ test.afterAll(async () => {
});
test.beforeEach(async ({ page }) => {
await start(CONFIG, "password.sql");
await start(simulatorConfig(), "password.sql");
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByLabel("Grid Power").fill("500");
await page.getByTestId("vehicle0").getByLabel("SoC").fill("20");
await page.getByTestId("loadpoint0").getByText("B (connected)").click();
@ -39,7 +39,7 @@ test.describe("limitSoc", async () => {
});
test("can be set even if vehicle isn't connected yet", async ({ page }) => {
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByTestId("loadpoint0").getByText("A (disconnected)").click();
await page.getByRole("button", { name: "Apply changes" }).click();
@ -50,7 +50,7 @@ test.describe("limitSoc", async () => {
await page.getByTestId("limit-soc").getByRole("combobox").selectOption("50%");
await expect(page.getByTestId("limit-soc-value")).toHaveText("50%");
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByTestId("loadpoint0").getByText("B (connected)").click();
await page.getByRole("button", { name: "Apply changes" }).click();
@ -65,7 +65,7 @@ test.describe("limitSoc", async () => {
await expect(page.getByTestId("limit-soc-value")).toHaveText("50%");
// disconnect
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByTestId("loadpoint0").getByText("A (disconnected)").click();
await page.getByRole("button", { name: "Apply changes" }).click();
@ -74,7 +74,7 @@ test.describe("limitSoc", async () => {
await expect(page.getByTestId("limit-soc-value")).toHaveText("100%");
// connect
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByTestId("loadpoint0").getByText("B (connected)").click();
await page.getByRole("button", { name: "Apply changes" }).click();

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start("basics.evcc.yaml", "password.sql");

View file

@ -1,12 +1,13 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { startSimulator, stopSimulator } from "./simulator";
import { start, stop, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorConfig } from "./simulator";
const BASICS_CONFIG = "basics.evcc.yaml";
const SIMULATOR_CONFIG = "simulator.evcc.yaml";
const UI_ROUTES = ["/", "/#/sessions", "/#/config"];
test.use({ baseURL: baseUrl() });
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
@ -55,7 +56,7 @@ test.describe("Basics", async () => {
test.describe("Advanced", async () => {
test.beforeAll(async () => {
await start(SIMULATOR_CONFIG, "password.sql");
await start(simulatorConfig(), "password.sql");
await startSimulator();
});

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
const CONFIG = "plan.evcc.yaml";

View file

@ -1,5 +1,7 @@
import { test, expect, devices } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
const mobile = devices["iPhone 12 Mini"].viewport;
const desktop = devices["Desktop Chrome"].viewport;

View file

@ -1,15 +1,36 @@
import os from "os";
import path from "path";
import fs from "fs";
import waitOn from "wait-on";
import axios from "axios";
import { exec } from "child_process";
export const SIMULATOR_HOST = "localhost:7072";
export const SIMULATOR_URL = `http://${SIMULATOR_HOST}/`;
const HEALTH_URL = SIMULATOR_URL + "api/state";
const SHUTDOWN_URL = SIMULATOR_URL + "api/shutdown";
function port() {
const index = process.env.TEST_PARALLEL_INDEX * 1;
return 12000 + index;
}
export function simulatorHost() {
return `localhost:${port()}`;
}
export function simulatorUrl() {
return `http://${simulatorHost()}`;
}
export function simulatorConfig() {
const input = "./tests/simulator.evcc.yaml";
const content = fs.readFileSync(input, "utf8");
const result = content.replace(/localhost:7072/g, simulatorHost());
const resultName = "simulator.evcc.generated.yaml";
const resultPath = path.join(os.tmpdir(), resultName);
fs.writeFileSync(resultPath, result);
return resultPath;
}
export async function startSimulator() {
console.log("starting simulator");
const instance = exec("npm run simulator");
const instance = exec(`npm run simulator -- --port ${port()}`);
console.log("exec end");
instance.stdout.pipe(process.stdout);
instance.stderr.pipe(process.stderr);
@ -20,10 +41,10 @@ export async function startSimulator() {
}
});
console.log("waiton");
await waitOn({ resources: [HEALTH_URL], log: true });
await waitOn({ resources: [`${simulatorUrl()}/api/state`], log: true });
}
export async function stopSimulator() {
console.log("shutting down simulator");
await axios.post(SHUTDOWN_URL);
await axios.post(`${simulatorUrl()}/api/shutdown`);
}

View file

@ -1,11 +1,11 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { startSimulator, stopSimulator, SIMULATOR_URL } from "./simulator";
import { start, stop, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorUrl, simulatorConfig } from "./simulator";
const CONFIG = "simulator.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start(CONFIG, "password.sql");
await start(simulatorConfig(), "password.sql");
await startSimulator();
});
test.afterAll(async () => {
@ -14,7 +14,7 @@ test.afterAll(async () => {
});
test.beforeEach(async ({ page }) => {
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByLabel("PV Power").fill("6000");
await page.getByTestId("loadpoint0").getByLabel("Power").fill("6000");
await page.getByTestId("loadpoint0").getByText("C (charging)").click();

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start("statistics.evcc.yaml", ["password.sql", "statistics.sql"]);

View file

@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop } from "./evcc";
import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await start("vehicle-error.evcc.yaml", "password.sql");

View file

@ -1,8 +1,8 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart } from "./evcc";
import { startSimulator, stopSimulator, SIMULATOR_URL } from "./simulator";
import { start, stop, restart, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorUrl, simulatorConfig } from "./simulator";
const CONFIG = "simulator.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
await startSimulator();
@ -12,9 +12,9 @@ test.afterAll(async () => {
});
test.beforeEach(async ({ page }) => {
await start(CONFIG, "password.sql");
await start(simulatorConfig(), "password.sql");
await page.goto(SIMULATOR_URL);
await page.goto(simulatorUrl());
await page.getByLabel("Grid Power").fill("500");
await page.getByTestId("vehicle0").getByLabel("SoC").fill("20");
await page.getByTestId("loadpoint0").getByText("B (connected)").click();
@ -36,7 +36,7 @@ test.describe("minSoc", async () => {
await page.getByRole("combobox", { name: "Min. charge %" }).selectOption("20%");
await expect(page.getByText("charged to 20% in solar mode")).toBeVisible();
await restart(CONFIG);
await restart(simulatorConfig());
await page.reload();
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
@ -75,7 +75,7 @@ test.describe("limitSoc", async () => {
await page.getByRole("button", { name: "Close" }).click();
await expect(page.getByTestId("limit-soc-value")).toContainText("80%");
await restart(CONFIG);
await restart(simulatorConfig());
await page.reload();
await expect(page.getByTestId("limit-soc-value")).toContainText("80%");