Files
pleno-vue/tests/e2e/self-serve-wash.spec.js
T
Jeppe Bundgaard b39b458f4f Add .prettierrc.json and refactor test files for improved formatting consistency:
- Introduced `.prettierrc.json` to enforce consistent code formatting across the project.
- Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
2026-04-13 09:13:27 +02:00

439 lines
15 KiB
JavaScript

import { expect, test } from "@playwright/test";
import { mockApi, seedAuthenticatedState } from "./support/network.js";
async function suppressVueDevtoolsOverlay(page) {
await page.addInitScript(() => {
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; }";
document.documentElement.appendChild(style);
});
}
async function primeSession(page, { token, permissions, sessionData = {} }) {
await seedAuthenticatedState(page, token);
await page.goto("/login");
await page.evaluate(
async ({ sessionToken, sessionPermissions, data }) => {
const sessionModule = await import("/src/components/session/token/SessionUser.vue");
window.localStorage.setItem("token", sessionToken);
sessionModule.SessionUser.token.value = sessionToken;
sessionModule.SessionUser.authenticated.value = true;
sessionModule.SessionUser.permissions.value = sessionPermissions;
sessionModule.SessionUser.initiated.value = true;
sessionModule.SessionUser.user.customer_number.value = data.customer_number ?? 12345;
sessionModule.SessionUser.user.display_name.value = data.display_name ?? "E2E User";
},
{
sessionToken: token,
sessionPermissions: permissions,
data: sessionData,
}
);
}
async function seedGeolocation(context) {
await context.grantPermissions(["geolocation"]);
await context.setGeolocation({
latitude: 55.6761,
longitude: 12.5683,
});
}
async function fillRegistration(page, value) {
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/);
}
function waitForLaneCommandRequest(page, command) {
return page.waitForRequest((request) => {
if (!request.url().includes("/modules/self-serve/lane/command")) {
return false;
}
if (request.method() !== "POST") {
return false;
}
const body = request.postDataJSON?.() || {};
return body.command === command;
});
}
function buildSavedProgress(overrides = {}) {
return {
washInProgress: false,
washLaneId: null,
washStartTime: null,
currentStep: 0,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: "12345",
answers: {},
completedTasks: {},
nearestDepartmentId: 1,
forceNearestDepartmentEvaluationId: 0,
isForcingNearestDepartment: false,
savedAt: Date.now(),
...overrides,
};
}
async function seedSavedProgress(page, overrides = {}) {
const payload = buildSavedProgress(overrides);
await page.addInitScript((savedProgress) => {
window.localStorage.setItem("mywash_progress_v6", JSON.stringify(savedProgress));
}, payload);
}
test.describe("Self-serve wash", () => {
test.skip(
({ browserName }) => browserName !== "chromium",
"Self-serve coverage is maintained on Chromium desktop/mobile."
);
test.beforeEach(async ({ page, context }) => {
await suppressVueDevtoolsOverlay(page);
await seedGeolocation(context);
});
test("@smoke landing route sorts departments, loads vehicle selection, and restores direct wash progress", async ({
page,
}) => {
await mockApi(page, {
authenticated: true,
permissions: ["user"],
selfServe: true,
});
await primeSession(page, {
token: "self-serve-user-token",
permissions: ["user"],
});
await page.goto("/user/wash");
await expect(page.getByTestId("self-serve-wash-home")).toBeVisible();
await expect(page.getByTestId("self-serve-home-nearest-name")).toContainText("Copenhagen");
const orderedDepartmentIds = await page
.locator('[data-testid^="self-serve-home-department-"]')
.evaluateAll((elements) => elements.map((element) => element.getAttribute("data-testid")));
expect(orderedDepartmentIds).toEqual(["self-serve-home-department-2", "self-serve-home-department-3"]);
await page.getByTestId("self-serve-home-start").click();
await expect(page).toHaveURL(/\/user\/wash\/start$/);
await expect(page.getByTestId("self-serve-department-name")).toContainText("Copenhagen");
await fillRegistration(page, "ab12345");
await selectVehicleType(page, 2);
await seedSavedProgress(page, {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 20_000,
currentStep: 4,
licensePlateInput: "ZZ00000",
answers: {
21: true,
},
});
await page.reload();
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-nav-complete").click();
await expect(page.getByTestId("self-serve-completed-step")).toBeVisible();
await page.getByTestId("self-serve-nav-close").click();
await expect(page.getByTestId("self-serve-completed-step")).toBeHidden();
await expect(page.getByTestId("self-serve-nav-complete")).toBeVisible();
});
test("superuser can force a department and clear the override", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["user", "superuser"],
selfServe: true,
});
await primeSession(page, {
token: "self-serve-superuser-token",
permissions: ["user", "superuser"],
});
await page.goto("/user/wash/start");
await expect(page.getByTestId("self-serve-department-name")).toContainText("Copenhagen");
await page.getByTestId("self-serve-department-name").click();
const departmentSearch = page.locator('input[placeholder*="afdelinger"]').first();
await departmentSearch.fill("Odense");
await page.getByRole("button", { name: "Odense" }).click();
await expect(page.getByTestId("self-serve-department-name")).toContainText("Odense");
await expect(page.getByTestId("self-serve-department-clear")).toBeVisible();
await page.getByTestId("self-serve-department-clear").click();
await expect(page.getByTestId("self-serve-department-name")).toContainText("Copenhagen");
});
test("lane flow gates invalid lanes, direct wash path auto-starts, and expired progress is discarded", async ({
page,
}) => {
await seedSavedProgress(page, {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 30_000,
currentStep: 4,
licensePlateInput: "STALE01",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: "12345",
savedAt: Date.now() - 3 * 60 * 60 * 1000,
});
await mockApi(page, {
authenticated: true,
permissions: ["user"],
selfServe: true,
});
await primeSession(page, {
token: "self-serve-direct-token",
permissions: ["user"],
});
await page.goto("/user/wash/start");
await expect(page.getByTestId("self-serve-vehicle-step")).toBeVisible();
await expect(page.getByTestId("self-serve-nav-complete")).toBeHidden();
await expect.poll(async () => page.evaluate(() => window.localStorage.getItem("mywash_progress_v6"))).toBeNull();
await seedSavedProgress(page, {
currentStep: 2,
licensePlateInput: "ZZ00000",
radioLaneOption: 8,
radioWashType: "Machine",
});
await page.reload();
await expect(page.getByTestId("self-serve-lane-step")).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole("radio", { name: /Maskine/i })).toBeDisabled();
await expect(page.getByTestId("self-serve-nav-confirm")).toBeDisabled();
await page.getByTestId("self-serve-lane-option-7").click();
await page.getByTestId("self-serve-wash-type-manual").click();
await expect(page.getByTestId("self-serve-nav-confirm")).toBeEnabled({ timeout: 10_000 });
await page.getByTestId("self-serve-nav-confirm").click();
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
});
test("tasks flow renders dynamic image, requires completion, and restores after reload", async ({ page }) => {
await seedSavedProgress(page, {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 30_000,
currentStep: 3,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioLaneOption: 7,
customerNumberInput: "12345",
completedTasks: {},
});
await mockApi(page, {
authenticated: true,
permissions: ["user"],
selfServe: {
previewByKey: {
"7:AB12345": {
allowed: true,
machine_available: true,
lane: { id: 7, name: "7" },
session: { id: 501, status: "IN_PROGRESS", allowed: true },
questions: [
{
id: 11,
question: "Is the tarp removed?",
description: "Required before machine wash.",
answer: true,
order_priority: 1,
},
],
conditions: [],
rules: [],
tasks: [
{
id: 9001,
task: "Prepare the truck",
description: "Complete the pre-wash checks.",
order_priority: 1,
services: ["MACHINE"],
buttons: [1],
},
],
allowed_services: ["MACHINE"],
},
},
summaryBySessionId: {
501: {
session: { id: 501, status: "IN_PROGRESS", allowed: true },
lane: { id: 7, name: "7" },
questions: [
{
id: 11,
question: "Is the tarp removed?",
description: "Required before machine wash.",
answer: true,
order_priority: 1,
},
],
conditions: [],
rules: [],
tasks: [
{
id: 9001,
task: "Prepare the truck",
description: "Complete the pre-wash checks.",
order_priority: 1,
services: ["MACHINE"],
buttons: [1],
},
],
events: [{ id: 3, type: "QUESTION_ANSWERED", created_at: "2026-01-01T10:01:00.000Z" }],
},
},
},
});
await primeSession(page, {
token: "self-serve-tasks-token",
permissions: ["user"],
});
await page.goto("/user/wash/start");
await expect(page.getByTestId("self-serve-tasks-step")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("self-serve-dynamic-image")).toBeVisible();
await expect(page.getByTestId("self-serve-task-9001-attachment-302")).toBeVisible();
await expect(page.getByTestId("self-serve-nav-confirm")).toBeDisabled();
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();
await seedSavedProgress(page, {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 45_000,
currentStep: 4,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioLaneOption: 7,
customerNumberInput: "12345",
});
await page.reload();
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
});
test("in-progress flow opens property access and exit gates via lane command endpoint", async ({ page }) => {
await seedSavedProgress(page, {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 30_000,
currentStep: 4,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioLaneOption: 7,
customerNumberInput: "12345",
});
await mockApi(page, {
authenticated: true,
permissions: ["user"],
selfServe: true,
});
await primeSession(page, {
token: "self-serve-property-gates-token",
permissions: ["user"],
});
await page.goto("/user/wash/start");
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
const accessCommandRequestPromise = waitForLaneCommandRequest(page, "OPEN_PROPERTY_ACCESS_GATE");
await page.getByTestId("self-serve-nav-open-property-access-gate").click();
const accessCommandRequest = await accessCommandRequestPromise;
expect(accessCommandRequest.postDataJSON?.()).toMatchObject({
lane_id: 7,
command: "OPEN_PROPERTY_ACCESS_GATE",
});
const exitCommandRequestPromise = waitForLaneCommandRequest(page, "OPEN_PROPERTY_EXIT_GATE");
await page.getByTestId("self-serve-nav-open-property-exit-gate").click();
const exitCommandRequest = await exitCommandRequestPromise;
expect(exitCommandRequest.postDataJSON?.()).toMatchObject({
lane_id: 7,
command: "OPEN_PROPERTY_EXIT_GATE",
});
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible();
});
test("mobile layout keeps primary controls usable", async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("mobile"), "Mobile sanity runs on the mobile project only.");
await mockApi(page, {
authenticated: true,
permissions: ["user"],
selfServe: true,
});
await primeSession(page, {
token: "self-serve-mobile-token",
permissions: ["user"],
});
await page.goto("/user/wash/start");
await expect(page.getByTestId("self-serve-start-page")).toBeVisible();
await fillRegistration(page, "ab12345");
await selectVehicleType(page, 2);
await expect(page.getByTestId("self-serve-nav-next")).toBeVisible();
await expect(page.getByTestId("self-serve-vehicle-selector-description")).toContainText("Truck");
});
test("admin preview modal reuses shared question/task rendering", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["admin", "department_access_1"],
selfServe: true,
});
await primeSession(page, {
token: "self-serve-admin-token",
permissions: ["admin", "department_access_1"],
});
await page.goto("/admin/1/modules/wash-lanes/7");
await expect(page.getByTestId("department-wash-lane-page")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("self-serve-admin-open-try-modal").first().click();
await expect(page.getByTestId("self-serve-try-modal")).toBeVisible();
await page.getByTestId("self-serve-try-reg").fill("ab12345");
await page.getByTestId("self-serve-try-refresh").click();
await expect(page.getByTestId("self-serve-question-11")).toBeVisible();
await page.getByTestId("self-serve-question-11-yes").click();
await expect(page.getByText("Prepare the truck")).toBeVisible();
await expect(page.getByText("QUESTION_ANSWERED")).toBeVisible({ timeout: 10_000 });
});
});