chore: e2e stability vehicle settings, fatal (#22848)
This commit is contained in:
parent
c4407c290d
commit
4f3beb150b
5 changed files with 76 additions and 96 deletions
|
|
@ -208,6 +208,7 @@ This file provides guidance to AI coding agents when working with code in this r
|
|||
- Use `expectModalVisible()` and `expectModalHidden()` helpers
|
||||
- Test configuration persistence across application restarts
|
||||
- Standard structure: import `{ start, stop, baseUrl }` from `./evcc`, use `test.afterEach(stop)`
|
||||
- Never use fixed timeouts, use existance of elements or wait for network idle
|
||||
|
||||
## Device Integration & Configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -30,80 +30,61 @@
|
|||
</LabelAndValue>
|
||||
|
||||
<Teleport to="body">
|
||||
<div
|
||||
<GenericModal
|
||||
:id="modalId"
|
||||
ref="modal"
|
||||
class="modal fade text-dark modal-xl"
|
||||
data-bs-backdrop="true"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-hidden="true"
|
||||
:title="modalTitle"
|
||||
size="xl"
|
||||
data-testid="charging-plan-modal"
|
||||
@open="modalVisible"
|
||||
@closed="modalInvisible"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ $t("main.chargingPlan.modalTitle")
|
||||
}}<span v-if="socBasedPlanning && vehicle"
|
||||
>: {{ vehicle.title }}</span
|
||||
>
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body pt-2">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: departureTabActive }"
|
||||
href="#"
|
||||
@click.prevent="showDeatureTab"
|
||||
>
|
||||
{{ $t("main.chargingPlan.departureTab") }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: arrivalTabActive }"
|
||||
href="#"
|
||||
@click.prevent="showArrivalTab"
|
||||
>
|
||||
{{ $t("main.chargingPlan.arrivalTab") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="isModalVisible">
|
||||
<ChargingPlansSettings
|
||||
v-if="departureTabActive"
|
||||
v-bind="chargingPlansSettingsProps"
|
||||
@static-plan-updated="updateStaticPlan"
|
||||
@static-plan-removed="removeStaticPlan"
|
||||
@repeating-plans-updated="updateRepeatingPlans"
|
||||
/>
|
||||
<ChargingPlanArrival
|
||||
v-if="arrivalTabActive"
|
||||
v-bind="chargingPlanArrival"
|
||||
@minsoc-updated="setMinSoc"
|
||||
@limitsoc-updated="setLimitSoc"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-2">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: departureTabActive }"
|
||||
href="#"
|
||||
@click.prevent="showDeatureTab"
|
||||
>
|
||||
{{ $t("main.chargingPlan.departureTab") }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: arrivalTabActive }"
|
||||
href="#"
|
||||
@click.prevent="showArrivalTab"
|
||||
>
|
||||
{{ $t("main.chargingPlan.arrivalTab") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="isModalVisible">
|
||||
<ChargingPlansSettings
|
||||
v-if="departureTabActive"
|
||||
v-bind="chargingPlansSettingsProps"
|
||||
@static-plan-updated="updateStaticPlan"
|
||||
@static-plan-removed="removeStaticPlan"
|
||||
@repeating-plans-updated="updateRepeatingPlans"
|
||||
/>
|
||||
<ChargingPlanArrival
|
||||
v-if="arrivalTabActive"
|
||||
v-bind="chargingPlanArrival"
|
||||
@minsoc-updated="setMinSoc"
|
||||
@limitsoc-updated="setLimitSoc"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GenericModal>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Modal from "bootstrap/js/dist/modal";
|
||||
import GenericModal from "../Helper/GenericModal.vue";
|
||||
import LabelAndValue from "../Helper/LabelAndValue.vue";
|
||||
import PlansSettings from "./PlansSettings.vue";
|
||||
import Arrival from "./Arrival.vue";
|
||||
|
|
@ -121,6 +102,7 @@ const ONE_MINUTE = 60 * 1000;
|
|||
export default defineComponent({
|
||||
name: "ChargingPlan",
|
||||
components: {
|
||||
GenericModal,
|
||||
LabelAndValue,
|
||||
ChargingPlansSettings: PlansSettings,
|
||||
ChargingPlanArrival: Arrival,
|
||||
|
|
@ -154,7 +136,6 @@ export default defineComponent({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
modal: null as Modal | null,
|
||||
isModalVisible: false,
|
||||
activeTab: "departure",
|
||||
targetTimeLabel: "",
|
||||
|
|
@ -210,6 +191,13 @@ export default defineComponent({
|
|||
modalId(): string {
|
||||
return `chargingPlanModal_${this.id}`;
|
||||
},
|
||||
modalTitle(): string {
|
||||
const baseTitle = this.$t("main.chargingPlan.modalTitle");
|
||||
if (this.socBasedPlanning && this.vehicle) {
|
||||
return `${baseTitle}: ${this.vehicle.title}`;
|
||||
}
|
||||
return baseTitle;
|
||||
},
|
||||
departureTabActive(): boolean {
|
||||
return this.activeTab === "departure";
|
||||
},
|
||||
|
|
@ -251,38 +239,15 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
mounted(): void {
|
||||
const ref = this.$refs["modal"];
|
||||
if (ref) {
|
||||
this.modal = Modal.getOrCreateInstance(ref);
|
||||
ref.addEventListener("show.bs.modal", this.modalVisible);
|
||||
ref.addEventListener("hidden.bs.modal", this.modalInvisible);
|
||||
ref.addEventListener("hide.bs.modal", this.checkUnsavedOnClose);
|
||||
}
|
||||
this.interval = setInterval(this.updateTargetTimeLabel, ONE_MINUTE);
|
||||
this.updateTargetTimeLabel();
|
||||
},
|
||||
unmounted(): void {
|
||||
const ref = this.$refs["modal"];
|
||||
if (ref) {
|
||||
ref.removeEventListener("show.bs.modal", this.modalVisible);
|
||||
ref.removeEventListener("hidden.bs.modal", this.modalInvisible);
|
||||
ref.removeEventListener("hide.bs.modal", this.checkUnsavedOnClose);
|
||||
}
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkUnsavedOnClose(): void {
|
||||
const applyButton = this.$refs["modal"]?.querySelector<HTMLElement>(
|
||||
"[data-testid=plan-apply]"
|
||||
);
|
||||
if (applyButton) {
|
||||
if (confirm(this.$t("main.chargingPlan.unsavedChanges"))) {
|
||||
applyButton.click();
|
||||
}
|
||||
}
|
||||
},
|
||||
modalVisible(): void {
|
||||
this.isModalVisible = true;
|
||||
},
|
||||
|
|
@ -291,7 +256,8 @@ export default defineComponent({
|
|||
},
|
||||
openModal(): void {
|
||||
this.showDeatureTab();
|
||||
this.modal?.show();
|
||||
const modalRef = this.$refs["modal"] as any;
|
||||
modalRef?.open();
|
||||
},
|
||||
openPlanModal(arrivalTab = false) {
|
||||
if (arrivalTab) {
|
||||
|
|
@ -299,7 +265,8 @@ export default defineComponent({
|
|||
} else {
|
||||
this.showDeatureTab();
|
||||
}
|
||||
this.modal?.show();
|
||||
const modalRef = this.$refs["modal"] as any;
|
||||
modalRef?.open();
|
||||
},
|
||||
updateTargetTimeLabel(): void {
|
||||
if (!this.effectivePlanTime) return;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ test.describe("fatal config handling", async () => {
|
|||
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();
|
||||
|
|
@ -107,6 +108,7 @@ test.describe("fatal config handling", async () => {
|
|||
await expectModalHidden(meterModal);
|
||||
await lpModal.getByRole("button", { name: "Save" }).click();
|
||||
await expectModalHidden(lpModal);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// restart and verify
|
||||
await restart();
|
||||
|
|
|
|||
|
|
@ -599,6 +599,7 @@ test.describe("repeating", async () => {
|
|||
await plan.getByTestId("repeating-plan-active").click();
|
||||
await expect(modal.getByTestId("plan-preview-title")).toHaveText("Next plan #2");
|
||||
await expect(modal.getByTestId("target-text")).toContainText("09:20");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await restart(CONFIG);
|
||||
await page.goto("/");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { start, stop, restart, baseUrl } from "./evcc";
|
||||
import { expectModalVisible, expectModalHidden } from "./utils";
|
||||
import {
|
||||
startSimulator,
|
||||
stopSimulator,
|
||||
|
|
@ -36,11 +37,16 @@ test.describe("minSoc", async () => {
|
|||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await expectModalVisible(modal);
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
|
||||
await expect(page.getByText("charged to x in solar mode")).toBeVisible();
|
||||
await page.getByRole("combobox", { name: "Min. charge %" }).selectOption("20%");
|
||||
await expect(page.getByText("charged to 20% in solar mode")).toBeVisible();
|
||||
await expect(modal).toContainText("charged to x in solar mode");
|
||||
await modal.getByRole("combobox", { name: "Min. charge %" }).selectOption("20%");
|
||||
await expect(modal).toContainText("charged to 20% in solar mode");
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalHidden(modal);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await restart(simulatorConfig());
|
||||
await page.reload();
|
||||
|
|
@ -75,10 +81,13 @@ test.describe("limitSoc", async () => {
|
|||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await expectModalVisible(modal);
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
|
||||
await page.getByRole("combobox", { name: "Default limit" }).selectOption("80%");
|
||||
await page.getByRole("button", { name: "Close" }).click();
|
||||
await modal.getByRole("combobox", { name: "Default limit" }).selectOption("80%");
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalHidden(modal);
|
||||
await expect(page.getByTestId("limit-soc-value")).toContainText("80%");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue