- Replaced repetitive department module URL logic with `buildDepartmentModulePath` utility in `DepartmentModulesDisplay.vue`. - Enhanced layouts and responsive behavior across components, including `PosDepartmentStep1MobileTransactionHistory.vue` and `DepartmentDailyReport.vue`. - Removed unused `SuperuserInvoicingLocalStore.vue` and refactored to `SuperuserInvoicingLocalStore.ts`. - Updated media query handling and card layout adjustments in `DepartmentDailyReport.vue`. - Added new Playwright configurations (`playwright.prod.config.ts` and `playwright.live.config.ts`) for e2e release workflows. - Extended e2e and unit test coverage for mobile POS and department modules.
211 lines
6.6 KiB
TypeScript
211 lines
6.6 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { loginAsUser } from "../fixtures/authHelpers";
|
|
import { createPosFixture, mockApi, seedAuthenticatedState } from "../support/network.js";
|
|
import {
|
|
attachPageHealthGuards,
|
|
expectBodyHasContent,
|
|
expectOneVisible,
|
|
settlePage,
|
|
} from "./helpers";
|
|
|
|
const LOCAL_PROD_BASE_URL = "http://127.0.0.1:4173";
|
|
const LOGIN_URL = /\/login(?:\?.*)?$/;
|
|
const POS_PERMISSIONS = [
|
|
"admin",
|
|
"department_access_12",
|
|
"edit_order_items",
|
|
"get_user",
|
|
"list_customer_notes",
|
|
"list_customer_attributes",
|
|
"get_custom_prices_other",
|
|
];
|
|
|
|
async function gotoHealthyRoute(page, path: string) {
|
|
await page.goto(path);
|
|
await settlePage(page);
|
|
await expectBodyHasContent(page);
|
|
await expect(page.locator("body")).not.toContainText(/404/i);
|
|
}
|
|
|
|
async function seedAuthenticatedRole(page, {
|
|
token,
|
|
permissions,
|
|
sessionData = {},
|
|
pos = undefined,
|
|
}: {
|
|
token: string;
|
|
permissions: string[];
|
|
sessionData?: Record<string, unknown>;
|
|
pos?: unknown;
|
|
}) {
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions,
|
|
sessionData,
|
|
pos,
|
|
});
|
|
await seedAuthenticatedState(page, token);
|
|
}
|
|
|
|
test.describe("Local production release gate", () => {
|
|
test.describe("PWA sanity", () => {
|
|
test.use({ serviceWorkers: "allow" });
|
|
|
|
test("PWA bootstrap registers on the production build", async ({ page }, testInfo) => {
|
|
test.skip(
|
|
!testInfo.project.name.startsWith("chromium"),
|
|
"Service worker registration is release-gated on Chromium where Playwright exposes stable registrations.",
|
|
);
|
|
|
|
const guards = attachPageHealthGuards(page, LOCAL_PROD_BASE_URL);
|
|
|
|
await mockApi(page, { authenticated: false });
|
|
|
|
await gotoHealthyRoute(page, "/");
|
|
await expect(page.getByTestId("landing-title")).toContainText("Truck Wash");
|
|
|
|
await page.waitForFunction(async () => {
|
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
return registrations.some((registration) => Boolean(
|
|
registration.active || registration.installing || registration.waiting,
|
|
));
|
|
}, undefined, { timeout: 20_000 });
|
|
|
|
await guards.expectHealthy();
|
|
guards.dispose();
|
|
});
|
|
});
|
|
|
|
test.describe("Mocked local-prod browser coverage", () => {
|
|
test.use({ serviceWorkers: "block" });
|
|
|
|
test("public shell routes render on the production build", async ({ page }) => {
|
|
const guards = attachPageHealthGuards(page, LOCAL_PROD_BASE_URL);
|
|
|
|
await mockApi(page, {
|
|
authenticated: false,
|
|
selfServe: true,
|
|
});
|
|
|
|
await gotoHealthyRoute(page, "/");
|
|
await expect(page.getByTestId("landing-title")).toContainText("Truck Wash");
|
|
|
|
const manifestHref = await page.locator('link[rel="manifest"]').first().getAttribute("href");
|
|
expect(manifestHref).toBeTruthy();
|
|
const manifestResponse = await page.request.get(new URL(manifestHref!, LOCAL_PROD_BASE_URL).toString());
|
|
expect(manifestResponse.ok()).toBeTruthy();
|
|
|
|
await page.reload();
|
|
await settlePage(page);
|
|
await expect(page.getByTestId("landing-title")).toBeVisible();
|
|
|
|
for (const path of ["/about-us", "/privacy-policy", "/guest/home", "/guest/book/wash"]) {
|
|
await gotoHealthyRoute(page, path);
|
|
}
|
|
|
|
await guards.expectHealthy();
|
|
guards.dispose();
|
|
});
|
|
|
|
test("auth routes render and protected routes redirect when unauthenticated", async ({ page }) => {
|
|
const guards = attachPageHealthGuards(page, LOCAL_PROD_BASE_URL);
|
|
|
|
await mockApi(page, {
|
|
authenticated: false,
|
|
selfServe: true,
|
|
});
|
|
|
|
await gotoHealthyRoute(page, "/login");
|
|
await expect(page.getByTestId("login-submit")).toBeVisible();
|
|
|
|
await gotoHealthyRoute(page, "/login/driver");
|
|
await expect(page.locator('button[id="subuser-login-button"]')).toBeVisible();
|
|
|
|
await gotoHealthyRoute(page, "/admin/login");
|
|
await expect(page.locator('button[id="operator_login_button"]')).toBeVisible();
|
|
|
|
for (const path of ["/user", "/admin/12/modules/pos", "/superuser"]) {
|
|
await page.goto(path);
|
|
await settlePage(page);
|
|
await expect(page).toHaveURL(LOGIN_URL);
|
|
await expect(page.getByTestId("login-submit")).toBeVisible();
|
|
}
|
|
|
|
await guards.expectHealthy();
|
|
guards.dispose();
|
|
});
|
|
|
|
test("seeded user flow reaches dashboard, profile, and bookings", async ({ page }) => {
|
|
const guards = attachPageHealthGuards(page, LOCAL_PROD_BASE_URL);
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: ["user"],
|
|
sessionData: {
|
|
display_name: "Release User",
|
|
},
|
|
});
|
|
|
|
await loginAsUser(page, {
|
|
customerNumber: "release-user",
|
|
password: "release-password",
|
|
twoFactorAuthentication: false,
|
|
});
|
|
|
|
await expect(page.locator("#book-wash-button")).toBeVisible();
|
|
await expect(page.locator("#download-invoices-button")).toBeVisible();
|
|
|
|
await gotoHealthyRoute(page, "/user/profile");
|
|
await expect(page.locator(".card-header").first()).toBeVisible();
|
|
|
|
await gotoHealthyRoute(page, "/user/bookings");
|
|
await expect(page.locator(".title").first()).toBeVisible();
|
|
|
|
await guards.expectHealthy();
|
|
guards.dispose();
|
|
});
|
|
|
|
test("seeded department admin flow renders the POS shell", async ({ page }) => {
|
|
const guards = attachPageHealthGuards(page, LOCAL_PROD_BASE_URL);
|
|
|
|
await seedAuthenticatedRole(page, {
|
|
token: "release-admin-token",
|
|
permissions: POS_PERMISSIONS,
|
|
sessionData: {
|
|
display_name: "Release Admin",
|
|
},
|
|
pos: createPosFixture(),
|
|
});
|
|
|
|
await gotoHealthyRoute(page, "/admin/12/modules/pos");
|
|
await expectOneVisible([
|
|
page.getByTestId("pos-step-1"),
|
|
page.getByTestId("pos-mobile-step-1-shell"),
|
|
]);
|
|
|
|
await guards.expectHealthy();
|
|
guards.dispose();
|
|
});
|
|
|
|
test("seeded superuser flow renders the vehicles page", async ({ page }) => {
|
|
const guards = attachPageHealthGuards(page, LOCAL_PROD_BASE_URL);
|
|
|
|
await seedAuthenticatedRole(page, {
|
|
token: "release-superuser-token",
|
|
permissions: ["superuser", "user"],
|
|
sessionData: {
|
|
display_name: "Release Superuser",
|
|
},
|
|
});
|
|
|
|
await gotoHealthyRoute(page, "/superuser/vehicles");
|
|
await expect(page.locator("h2.title").first()).toBeVisible();
|
|
await expect(page.locator("body")).toContainText(/registrerede/i);
|
|
await expect(page.locator("body")).not.toContainText(/Order ID is required/i);
|
|
|
|
await guards.expectHealthy();
|
|
guards.dispose();
|
|
});
|
|
});
|
|
});
|