Files
pleno-vue/tests/e2e/userMyWashStartFlow.spec.ts
T

358 lines
14 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockApi, primeMockSession } from "./support/network.js";
async function suppressVueDevtoolsOverlay(page) {
await page.addInitScript(() => {
const appendStyle = () => {
const parent = document.documentElement || document.head || document.body;
if (!parent) {
document.addEventListener("DOMContentLoaded", appendStyle, { once: true });
return;
}
const style = document.createElement("style");
style.textContent =
"#__vue-devtools-container__, .vue-devtools__anchor-btn, .vue-devtools__panel-content { display: none !important; visibility: hidden !important; pointer-events: none !important; }";
parent.appendChild(style);
};
appendStyle();
});
}
async function seedGeolocation(context) {
await context.grantPermissions(["geolocation"]);
await context.setGeolocation({ latitude: 55.6415, longitude: 12.0803 });
}
function buildSavedProgress(overrides = {}) {
return {
washInProgress: false,
washLaneId: null,
washStartTime: null,
currentStep: 0,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: "12345679",
answers: {},
completedTasks: {},
nearestDepartmentId: 6,
forceNearestDepartmentEvaluationId: 0,
isForcingNearestDepartment: false,
savedAt: Date.now(),
...overrides,
};
}
async function seedSavedProgress(page, overrides = {}) {
await page.addInitScript((savedProgress) => {
window.localStorage.setItem("mywash_progress_v6", JSON.stringify(savedProgress));
}, buildSavedProgress(overrides));
}
async function fillRegistration(page, value = "ab12345") {
const registration = page.locator('input[placeholder*="registreringsnummer"]').first();
await registration.fill(value);
await expect(registration).toHaveValue(value.toUpperCase());
await registration.press("Escape");
}
async function selectVehicleType(page, vehicleTypeId = 2) {
await page.getByTestId(`self-serve-vehicle-type-${vehicleTypeId}`).click();
await expect(page.getByTestId("self-serve-vehicle-selector-description")).toContainText(/Truck|Van|Car/);
}
async function answerRequiredQuestions(page) {
await expect(page.getByTestId("self-serve-question-11")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-question-11-yes").click();
await expect(page.getByTestId("self-serve-nav-confirm")).toBeEnabled({ timeout: 10_000 });
}
async function completeGuidedWash(page) {
for (let index = 0; index < 5; index += 1) {
await page.getByTestId("self-serve-guided-next").click();
}
}
function collectSelfServeRequests(page) {
const requests = {
departments: [],
vehicleTypes: [],
customerVehicles: [],
allowed: [],
conditions: [],
commands: [],
relays: [],
summaries: [],
activeWash: [],
inProgress: [],
};
page.on("request", (request) => {
const url = new URL(request.url());
const entry = {
method: request.method(),
url,
body: request.method() === "GET" ? null : request.postDataJSON?.() || null,
};
if (url.pathname.endsWith("/guest/departments")) requests.departments.push(entry);
if (url.pathname.endsWith("/modules/xlvask/internal/vehicle-types")) requests.vehicleTypes.push(entry);
if (url.pathname.endsWith("/vehicles")) requests.customerVehicles.push(entry);
if (url.pathname.endsWith("/department/selfserve/vehicle/allowed")) requests.allowed.push(entry);
if (url.pathname.endsWith("/department/selfserve/vehicle/conditions")) requests.conditions.push(entry);
if (url.pathname.endsWith("/modules/self-serve/lane/command")) requests.commands.push(entry);
if (url.pathname.endsWith("/modules/self-serve/lane/relay/machine/enable")) requests.relays.push(entry);
if (url.pathname.endsWith("/department/selfserve/washes/summary")) requests.summaries.push(entry);
if (url.pathname.endsWith("/modules/self-serve/lane/wash/my-active-wash")) requests.activeWash.push(entry);
if (url.pathname.endsWith("/modules/self-serve/lane/wash/in-progress")) requests.inProgress.push(entry);
});
return requests;
}
test.describe("User MyWashStart mocked flow", () => {
test.describe.configure({ timeout: 120_000 });
test.skip(({ browserName }) => browserName !== "chromium", "MyWashStart flow coverage is maintained on Chromium.");
test.beforeEach(async ({ page, context }) => {
await suppressVueDevtoolsOverlay(page);
await seedGeolocation(context);
});
test("manual wash selects vehicle, answers questions, starts, stops, and shows completed step", async ({ page }) => {
const requests = collectSelfServeRequests(page);
await mockApi(page, {
authenticated: true,
permissions: ["user"],
sessionData: { customer_number: 12345679 },
selfServe: true,
});
await primeMockSession(page, { token: "user-my-wash-manual-token", bootPath: "/user/wash/start" });
await expect(page.getByTestId("self-serve-department-name")).toContainText("Roskilde", { timeout: 15_000 });
await fillRegistration(page);
await selectVehicleType(page, 2);
await page.getByTestId("self-serve-nav-next").click();
await answerRequiredQuestions(page);
await page.getByTestId("self-serve-nav-confirm").click();
await expect(page.getByTestId("self-serve-lane-step")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-lane-option-7").click();
await page.getByTestId("self-serve-wash-type-manual").click();
await page.getByTestId("self-serve-nav-confirm").click();
await expect(page.getByTestId("self-serve-tasks-step")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-task-9001-toggle").click();
await expect(page.getByTestId("self-serve-nav-confirm")).toBeEnabled();
await page.getByTestId("self-serve-nav-confirm").click();
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
await completeGuidedWash(page);
await expect(page.getByTestId("self-serve-nav-complete")).toBeVisible();
await page.getByTestId("self-serve-nav-complete").click();
await expect(page.getByTestId("self-serve-completed-step")).toBeVisible({ timeout: 15_000 });
expect(requests.departments[0]?.url.searchParams.get("include_lanes")).toBe("true");
expect(requests.vehicleTypes.length).toBeGreaterThan(0);
expect(requests.customerVehicles.length).toBeGreaterThan(0);
expect(requests.allowed.length).toBeGreaterThan(0);
expect(requests.conditions.some((request) => request.body?.question === 11 && request.body?.value === true)).toBe(
true
);
expect(requests.summaries.length).toBeGreaterThan(0);
expect(requests.activeWash.length).toBeGreaterThan(0);
expect(requests.inProgress.length).toBeGreaterThan(0);
expect(requests.commands.map((request) => request.body?.command)).toEqual(
expect.arrayContaining(["START", "STOP", "OPEN_PROPERTY_EXIT_GATE"])
);
});
test("machine wash enables the relay after START and rolls back when relay enable fails", async ({ page }) => {
const requests = collectSelfServeRequests(page);
await seedSavedProgress(page, {
currentStep: 2,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioLaneOption: 7,
radioWashType: "Machine",
customerNumberInput: "12345679",
answers: { 11: true },
});
await mockApi(page, {
authenticated: true,
permissions: ["user"],
sessionData: { customer_number: 12345679 },
selfServe: {
relayResponses: [{ success: true }, { success: false, data: { message: "Machine relay unavailable" } }],
},
});
await primeMockSession(page, { token: "user-my-wash-machine-token", bootPath: "/user/wash/start" });
await expect(page.getByTestId("self-serve-lane-step")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-wash-type-machine").click();
await page.getByTestId("self-serve-nav-confirm").click();
await expect(page.getByTestId("self-serve-tasks-step")).toBeVisible({ timeout: 10_000 });
expect(requests.commands.map((request) => request.body?.command)).toContain("START");
expect(requests.relays).toHaveLength(1);
const firstStartIndex = requests.commands.findIndex((request) => request.body?.command === "START");
expect(firstStartIndex).toBeGreaterThanOrEqual(0);
await page.getByTestId("self-serve-nav-previous").click();
await expect(page.getByTestId("self-serve-lane-step")).toBeVisible();
await page.getByTestId("self-serve-nav-confirm").click();
await expect(page.getByTestId("self-serve-action-error")).toContainText("Machine relay unavailable");
await expect(page.getByTestId("self-serve-live-elapsed")).toBeHidden();
expect(requests.relays).toHaveLength(2);
expect(requests.commands.map((request) => request.body?.command)).toEqual(
expect.arrayContaining(["START", "STOP"])
);
});
test("active wash restore reloads start route and resumes at WASH_IN_PROGRESS", async ({ page }) => {
const requests = collectSelfServeRequests(page);
await mockApi(page, {
authenticated: true,
permissions: ["user"],
sessionData: { customer_number: 12345679 },
selfServe: {
inProgressByLaneId: {
7: {
lane_id: 7,
in_progress: true,
session: {
id: 801,
lane_id: 7,
department_id: 6,
reg: "AB12345",
customer_number: 12345679,
vehicle_type_id: 2,
machine_relay_enabled: false,
wash_started_at: "2026-06-02 08:00:00",
status: "IN_PROGRESS",
},
customer: { customer_number: 12345679 },
vehicle: { reg: "AB12345", type: 2 },
},
},
summaryBySessionId: {
801: {
session: { id: 801, lane_id: 7, reg: "AB12345", status: "IN_PROGRESS", vehicle_type_id: 2 },
lane: { id: 7, name: "7", department: 6 },
questions: [],
conditions: [],
rules: [],
tasks: [],
events: [{ id: 8011, type: "STARTED", created_at: "2026-06-02T08:00:00.000Z" }],
},
},
},
});
await primeMockSession(page, { token: "user-my-wash-active-token", bootPath: "/user/wash/start" });
await page.reload({ waitUntil: "domcontentloaded" });
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 15_000 });
await expect(page.getByTestId("self-serve-department-name")).toContainText("Roskilde");
await expect(page.getByTestId("self-serve-vehicle-step")).toBeHidden();
expect(requests.activeWash.length).toBeGreaterThan(0);
expect(requests.commands).toHaveLength(0);
});
test("server-completed wash refresh moves local in-progress state to completed", async ({ page }) => {
await seedSavedProgress(page, {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 60_000,
currentStep: 4,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioLaneOption: 7,
radioWashType: "Manual",
customerNumberInput: "12345679",
});
await mockApi(page, {
authenticated: true,
permissions: ["user"],
sessionData: { customer_number: 12345679 },
selfServe: {
inProgressByLaneId: {
7: {
lane_id: 7,
in_progress: false,
session: null,
customer: null,
vehicle: null,
},
},
},
});
await primeMockSession(page, { token: "user-my-wash-server-completed-token", bootPath: "/user/wash/start" });
await expect(page.getByTestId("self-serve-completed-step")).toBeVisible({ timeout: 15_000 });
await expect(page.getByTestId("self-serve-live-elapsed")).toBeHidden();
});
test("answer-sync failure shows an error and blocks progression", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["user"],
sessionData: { customer_number: 12345679 },
selfServe: true,
});
await page.route(/\/department\/selfserve\/vehicle\/conditions$/i, async (route) => {
if (route.request().method() === "POST") {
await route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ message: "Answer sync failed" }),
});
return;
}
await route.fallback();
});
await primeMockSession(page, { token: "user-my-wash-answer-sync-token", bootPath: "/user/wash/start" });
await fillRegistration(page);
await selectVehicleType(page, 2);
await page.getByTestId("self-serve-nav-next").click();
await expect(page.getByTestId("self-serve-question-11")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-question-11-yes").click();
await expect(page.getByTestId("self-serve-answer-sync-error")).toContainText("Answer sync failed");
await expect(page.getByTestId("self-serve-nav-confirm")).toBeDisabled();
await expect(page.getByTestId("self-serve-lane-step")).toBeHidden();
});
test("geolocation unavailable still provides a department-selection fallback", async ({ browser }) => {
const context = await browser.newContext({ permissions: [] });
const page = await context.newPage();
await suppressVueDevtoolsOverlay(page);
const requests = collectSelfServeRequests(page);
await mockApi(page, {
authenticated: true,
permissions: ["user"],
sessionData: { customer_number: 12345679 },
selfServe: true,
});
await primeMockSession(page, { token: "user-my-wash-no-geolocation-token", bootPath: "/user/wash/start" });
await expect(page.getByTestId("self-serve-department-name")).toContainText("Roskilde", { timeout: 15_000 });
await fillRegistration(page);
await selectVehicleType(page, 2);
await expect(page.getByTestId("self-serve-nav-next")).toBeEnabled();
expect(requests.departments[0]?.url.searchParams.get("include_lanes")).toBe("true");
await context.close();
});
});