chore: run playwright tests in parallel (#13809)
This commit is contained in:
parent
b9738abf15
commit
f0e313c6d4
19 changed files with 346 additions and 247 deletions
9
.github/workflows/default.yml
vendored
9
.github/workflows/default.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
85
tests/config-meters.spec.js
Normal file
85
tests/config-meters.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
132
tests/config-vehicles.spec.js
Normal file
132
tests/config-vehicles.spec.js
Normal 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/);
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"]);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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%");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue