- Introduced `AssignDraftOrderCustomerModal` component with supporting localization for managing customer assignments. - Added e2e harness for modal testing in `assign-draft-order-customer-modal.html` and `assign-draft-order-customer-modal.ts`. - Implemented e2e test cases for customer assignment workflows in `assign-draft-order-modal-create.spec.ts` and `assign-draft-order-modal-layout.spec.ts`.
1054 lines
37 KiB
JavaScript
1054 lines
37 KiB
JavaScript
import { expect, test } from "@playwright/test";
|
|
import { createPosFixture, mockApi, seedAuthenticatedState } from "./support/network.js";
|
|
import { createMobilePosFixture, REGULAR_CUSTOMER_ID, setupMobilePosPage } from "./support/mobilePos.js";
|
|
|
|
const POS_PERMISSIONS = [
|
|
"admin",
|
|
"department_access_12",
|
|
"edit_order_items",
|
|
"get_user",
|
|
"list_customer_notes",
|
|
"list_customer_attributes",
|
|
"get_custom_prices_other",
|
|
];
|
|
|
|
async function primeSession(page, token = "pos-visual-token") {
|
|
await seedAuthenticatedState(page, token);
|
|
const sessionRequest = page.waitForResponse((response) => {
|
|
return response.request().method() === "GET" && response.url().includes("/auth/session");
|
|
});
|
|
await page.goto("/login");
|
|
await sessionRequest;
|
|
}
|
|
|
|
async function seedMobileStepTwoState(page, { customerId = 12345679, orderId = 54518 } = {}) {
|
|
await page.addInitScript(
|
|
({ activeCustomerId, activeOrderId }) => {
|
|
window.localStorage.removeItem("pos_order_id");
|
|
window.localStorage.setItem(
|
|
"pos",
|
|
JSON.stringify({
|
|
vehicles: {
|
|
vehicle_1: {
|
|
reg: "EC21235",
|
|
status: "verified",
|
|
reference: "EC21233 - Test Ref. / Intern nummer",
|
|
type: 53,
|
|
customer_id: activeCustomerId,
|
|
},
|
|
vehicle_2: null,
|
|
vehicle_3: null,
|
|
activeVehicleIndex: 1,
|
|
},
|
|
views: {
|
|
manualInput: false,
|
|
vehicleSelection: false,
|
|
additionalItemSelection: false,
|
|
transactionHistoryView: false,
|
|
},
|
|
transactionItems: {
|
|
primaryItem: {
|
|
id: 53,
|
|
name: "Forvogn",
|
|
price: 649,
|
|
addons: [
|
|
{
|
|
id: 63,
|
|
quantity: 1,
|
|
product: {
|
|
id: 63,
|
|
name: "Indvendig vask Forvogn",
|
|
price: 399,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
additionalItems: [
|
|
{
|
|
id: 65,
|
|
name: "Ekstraordinær pr. 10 min inkl. kemi",
|
|
price: 299,
|
|
quantity: 1,
|
|
},
|
|
],
|
|
},
|
|
categories: {
|
|
list: [],
|
|
selected: null,
|
|
},
|
|
productList: {
|
|
list: [],
|
|
},
|
|
metadata: {
|
|
customerId: activeCustomerId,
|
|
notes: "Visual regression note",
|
|
reference: "EC21233 - Test Ref. / Intern nummer",
|
|
washId: null,
|
|
bookingId: null,
|
|
laneId: null,
|
|
},
|
|
attachments: {
|
|
files: [],
|
|
base64: [],
|
|
wash_certificate: false,
|
|
},
|
|
transactionHistory: [],
|
|
lastVehicleOrders: {
|
|
vehicle_1: {
|
|
id: activeOrderId,
|
|
created_at: "2026-04-08 08:44:07",
|
|
items: [],
|
|
},
|
|
vehicle_2: null,
|
|
vehicle_3: null,
|
|
},
|
|
timestamp: Date.now(),
|
|
})
|
|
);
|
|
},
|
|
{
|
|
activeCustomerId: customerId,
|
|
activeOrderId: orderId,
|
|
}
|
|
);
|
|
}
|
|
|
|
async function expectCustomerTabsDividerSpacing(scope) {
|
|
const tabs = scope.locator(".pos-selected-customer__tabs").first();
|
|
await expect(tabs).toBeVisible();
|
|
const spacing = await tabs.evaluate((element) => Number.parseFloat(window.getComputedStyle(element).marginBottom));
|
|
expect(spacing).toBeGreaterThan(5);
|
|
}
|
|
|
|
async function expectPosCardTabsDividerSpacing(scope) {
|
|
const tabs = scope.locator(".pos-card-tabs > .tabs").first();
|
|
await expect(tabs).toBeVisible();
|
|
const spacing = await tabs.evaluate((element) => Number.parseFloat(window.getComputedStyle(element).paddingBottom));
|
|
expect(spacing).toBeGreaterThan(5);
|
|
}
|
|
|
|
async function commitDesktopReg1ByBlur(page, value) {
|
|
const reg1Input = page.locator("#reg_1");
|
|
await reg1Input.fill(value);
|
|
await reg1Input.evaluate((element) => {
|
|
element.blur();
|
|
});
|
|
}
|
|
|
|
async function getElementMetrics(locator) {
|
|
await expect(locator).toBeVisible();
|
|
return locator.evaluate((element) => {
|
|
const styles = window.getComputedStyle(element);
|
|
const rect = element.getBoundingClientRect();
|
|
|
|
return {
|
|
fontSize: Number.parseFloat(styles.fontSize),
|
|
lineHeight: Number.parseFloat(styles.lineHeight),
|
|
height: rect.height,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function getSidebarTabMetrics(locator) {
|
|
await expect(locator).toBeVisible();
|
|
return locator.evaluate((element) => {
|
|
const styles = window.getComputedStyle(element);
|
|
const rect = element.getBoundingClientRect();
|
|
|
|
return {
|
|
color: styles.color,
|
|
backgroundColor: styles.backgroundColor,
|
|
borderRadius: styles.borderRadius,
|
|
minHeight: styles.minHeight,
|
|
paddingTop: styles.paddingTop,
|
|
paddingRight: styles.paddingRight,
|
|
paddingBottom: styles.paddingBottom,
|
|
paddingLeft: styles.paddingLeft,
|
|
height: rect.height,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function getSidebarTabListMetrics(locator) {
|
|
await expect(locator).toBeVisible();
|
|
return locator.evaluate((element) => {
|
|
const styles = window.getComputedStyle(element);
|
|
|
|
return {
|
|
gap: styles.gap,
|
|
rowGap: styles.rowGap,
|
|
columnGap: styles.columnGap,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function getHorizontalBounds(locator) {
|
|
await expect(locator).toBeVisible();
|
|
return locator.evaluate((element) => {
|
|
const rect = element.getBoundingClientRect();
|
|
return {
|
|
left: rect.left,
|
|
right: rect.right,
|
|
top: rect.top,
|
|
bottom: rect.bottom,
|
|
height: rect.height,
|
|
width: rect.width,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function expectPrimaryActionAboveClearAll(primaryAction, clearAllAction) {
|
|
const primaryBounds = await getHorizontalBounds(primaryAction);
|
|
const clearAllBounds = await getHorizontalBounds(clearAllAction);
|
|
|
|
expect(primaryBounds.top).toBeLessThan(clearAllBounds.top);
|
|
expect(primaryBounds.bottom).toBeLessThan(clearAllBounds.bottom);
|
|
}
|
|
|
|
async function openOrderDetailAddItems(page, { mobile = false } = {}) {
|
|
const addItemsPanel = page.locator('[data-testid="pos-order-add-items-panel"]:visible').first();
|
|
const addItemsBackButton = page.locator('[data-testid="pos-order-add-items-back"]:visible').first();
|
|
await page.goto("/admin/12/modules/pos/orders/54518");
|
|
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
|
|
await page.getByTestId("pos-order-add-item").click();
|
|
await expect(addItemsPanel).toBeVisible();
|
|
await expect(addItemsBackButton).toBeVisible();
|
|
await expect(getVisibleTestId(page, "pos-order-panel-cart")).toBeVisible();
|
|
await expect(getVisibleTestId(page, "pos-order-metadata-grid")).toBeVisible();
|
|
if (mobile) {
|
|
await expect(getVisibleTestId(page, "pos-order-mobile-tabs")).toBeVisible();
|
|
}
|
|
}
|
|
|
|
async function waitForOrderDetailMetadata(page) {
|
|
await expect(getVisibleTestId(page, "pos-order-metadata-license-plates")).toContainText("EC21235", {
|
|
timeout: 10_000,
|
|
});
|
|
await expect(getVisibleTestId(page, "pos-order-metadata-customer-wishes")).toContainText("Reference", {
|
|
timeout: 10_000,
|
|
});
|
|
}
|
|
|
|
async function expectClippedLocatorScreenshot(page, locator, snapshotName, { width, height, maxDiffPixels = 300 }) {
|
|
await locator.scrollIntoViewIfNeeded();
|
|
const box = await locator.boundingBox();
|
|
if (!box) {
|
|
throw new Error(`Unable to capture screenshot for ${snapshotName}: locator has no bounding box.`);
|
|
}
|
|
const screenshot = await page.screenshot({
|
|
clip: {
|
|
x: Math.floor(box.x),
|
|
y: Math.floor(box.y),
|
|
width,
|
|
height,
|
|
},
|
|
scale: "css",
|
|
});
|
|
expect(screenshot).toMatchSnapshot(snapshotName, { maxDiffPixels });
|
|
}
|
|
|
|
function getVisibleTestId(page, testId) {
|
|
return page.locator(`[data-testid="${testId}"]:visible`).first();
|
|
}
|
|
|
|
function buildTodayTimestamp(time = "10:00:00.000Z") {
|
|
const today = new Date().toISOString().split("T")[0];
|
|
return `${today}T${time}`;
|
|
}
|
|
|
|
function createRequiredWarningsPosFixture() {
|
|
const customerNumber = 12345679;
|
|
const baseFixture = createPosFixture();
|
|
|
|
return createPosFixture({
|
|
customerAttributesByNumber: {
|
|
[customerNumber]: [
|
|
{
|
|
id: 11,
|
|
customer_number: customerNumber,
|
|
attribute: "requiresReferenceNumber",
|
|
},
|
|
{
|
|
id: 12,
|
|
customer_number: customerNumber,
|
|
attribute: "usePONumbers",
|
|
},
|
|
],
|
|
},
|
|
ordersById: {
|
|
54518: {
|
|
...baseFixture.ordersById[54518],
|
|
reference: "",
|
|
po: "",
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
test.describe("POS visuals", () => {
|
|
test("desktop step 1 snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
const posFixture = createPosFixture({
|
|
orderBookings: [
|
|
{
|
|
id: 77,
|
|
reg_1: "AB12345",
|
|
reg_2: "",
|
|
},
|
|
],
|
|
});
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: posFixture,
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-1-token");
|
|
|
|
await page.goto("/admin/12/modules/pos");
|
|
const stepOne = page.getByTestId("pos-step-1");
|
|
await expect(stepOne).toBeVisible();
|
|
await expectPosCardTabsDividerSpacing(stepOne);
|
|
await expect(page.getByTestId("pos-recent-scan-row-801")).toBeVisible();
|
|
await expect(stepOne.getByText(/^Booket$/)).toBeVisible();
|
|
await expect(stepOne).not.toContainText("Booket (fremt. opdat.)");
|
|
await expect(stepOne).toHaveScreenshot("pos-step-1-desktop.png", {
|
|
maxDiffPixels: 300,
|
|
});
|
|
});
|
|
|
|
test("desktop step 1 expanded recent scan snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
const posFixture = createPosFixture({
|
|
orderBookings: [
|
|
{
|
|
id: 77,
|
|
reg_1: "AB12345",
|
|
reg_2: "",
|
|
},
|
|
],
|
|
});
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: posFixture,
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-1-expanded-token");
|
|
|
|
await page.goto("/admin/12/modules/pos");
|
|
await expect(page.getByTestId("pos-step-1")).toBeVisible();
|
|
await expect(page.getByTestId("pos-recent-scan-details-801")).toHaveCount(0);
|
|
|
|
await page.getByTestId("pos-recent-scan-row-801").click();
|
|
|
|
await expect(page.locator("#reg_1")).toHaveValue("AB12345");
|
|
await expect(page.getByTestId("pos-recent-scan-details-801")).toContainText("RENAULT");
|
|
await expect(page.getByTestId("pos-recent-scan-details-801")).toContainText("Captur");
|
|
await expect(page.getByTestId("pos-recent-scan-details-801")).toContainText("Kundenummer");
|
|
|
|
await expect(page.getByTestId("pos-step-1")).toHaveScreenshot("pos-step-1-desktop-expanded-scan.png", {
|
|
maxDiffPixels: 1000,
|
|
});
|
|
});
|
|
|
|
test("desktop step 1 customer snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-1-customer-token");
|
|
|
|
await page.goto("/admin/12/modules/pos");
|
|
await expect(page.getByTestId("pos-step-1")).toBeVisible();
|
|
await page.locator("#reg_1").fill("EC21235");
|
|
await expect(page.getByTestId("pos-step-1").locator(".pos-card-tabs > .tabs li.is-active")).toContainText("Kunde");
|
|
await expectPosCardTabsDividerSpacing(page.getByTestId("pos-step-1"));
|
|
await expectCustomerTabsDividerSpacing(page.getByTestId("pos-step-1"));
|
|
await expect(page.getByTestId("pos-step-1")).toHaveScreenshot("pos-step-1-customer-desktop.png", {
|
|
maxDiffPixels: 2000,
|
|
});
|
|
});
|
|
|
|
test("desktop step 1 duplicate warning snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
const posFixture = createPosFixture();
|
|
posFixture.ordersById[9206] = {
|
|
id: 9206,
|
|
customer_id: 12345679,
|
|
department_id: 12,
|
|
reference: "DUP-VISUAL",
|
|
po: "",
|
|
safety_seal: "",
|
|
notes: "",
|
|
reg_1: "AB12345",
|
|
reg_2: "",
|
|
reg_3: "",
|
|
invoice_collection_id: null,
|
|
booking_id: null,
|
|
completed_at: null,
|
|
closed_at: null,
|
|
created_at: buildTodayTimestamp(),
|
|
include_in_invoice: null,
|
|
};
|
|
posFixture.orderItemsByOrderId[9206] = [];
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: posFixture,
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-1-duplicate-warning-token");
|
|
|
|
await page.goto("/admin/12/modules/pos");
|
|
await expect(page.getByTestId("pos-step-1")).toBeVisible();
|
|
|
|
await commitDesktopReg1ByBlur(page, "AB12345");
|
|
|
|
const duplicateWarning = page.getByTestId("pos-desktop-duplicate-warning-inline");
|
|
await expect(duplicateWarning).toBeVisible({ timeout: 10_000 });
|
|
await duplicateWarning.getByTestId("pos-desktop-duplicate-warning-toggle-details").click();
|
|
await expect(page.getByTestId("pos-desktop-duplicate-warning-details-inline")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
await expect(duplicateWarning).toHaveScreenshot("pos-step-1-desktop-duplicate-warning.png", {
|
|
maxDiffPixels: 2000,
|
|
});
|
|
});
|
|
|
|
test("desktop step 1 keeps customer notes visible without refetching on blur", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
const customerNoteRequests = [];
|
|
page.on("request", (request) => {
|
|
if (request.method() === "GET" && request.url().includes("/customer/notes?customer_number=12345679")) {
|
|
customerNoteRequests.push(request.url());
|
|
}
|
|
});
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture({
|
|
customerNotesByNumber: {
|
|
12345679: [
|
|
{
|
|
id: 401,
|
|
created_at: "2026-04-09 09:15:00",
|
|
note: "Ring inden vask",
|
|
cashier_id: null,
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-1-notes-token");
|
|
|
|
await page.goto("/admin/12/modules/pos");
|
|
await expect(page.getByTestId("pos-step-1")).toBeVisible();
|
|
|
|
const stepOne = page.getByTestId("pos-step-1");
|
|
const registrationInput = page.locator("#reg_1");
|
|
const referenceInput = page.locator("#reference");
|
|
const noteRow = stepOne.getByTestId("pos-customer-note-401");
|
|
|
|
await registrationInput.fill("EC21235");
|
|
await expect(noteRow).toBeVisible();
|
|
await expect(noteRow).toContainText("Ring inden vask");
|
|
await expect.poll(() => customerNoteRequests.length).toBe(1);
|
|
|
|
await referenceInput.click();
|
|
await expect(noteRow).toBeVisible();
|
|
await expect(noteRow).toContainText("Ring inden vask");
|
|
await page.waitForTimeout(250);
|
|
expect(customerNoteRequests).toHaveLength(1);
|
|
});
|
|
|
|
test("desktop order detail snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-token");
|
|
|
|
await page.goto("/admin/12/modules/pos/orders/54518");
|
|
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
|
|
await expect(page.locator('[data-auto-excel-export-button="1"]')).toHaveCount(0);
|
|
await expect(page.getByTestId("pos-order-registration-add-2")).toBeVisible();
|
|
await expect(page.getByTestId("pos-order-note-empty-state")).toBeVisible();
|
|
await expect(page.getByTestId("pos-order-item-edit-9101")).toBeVisible();
|
|
await expect(page.getByTestId("pos-order-add-item")).toBeVisible();
|
|
await expect(page.getByTestId("pos-order-total")).toContainText("1372 DKK");
|
|
await expect(page.getByTestId("pos-order-detail")).toHaveScreenshot("pos-order-detail-desktop.png", {
|
|
maxDiffPixels: 300,
|
|
});
|
|
});
|
|
|
|
test("desktop order detail required warnings snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createRequiredWarningsPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-required-warnings-token");
|
|
|
|
await page.goto("/admin/12/modules/pos/orders/54518");
|
|
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
|
|
await expect(page.getByTestId("pos-order-customer-wishes-reference-control")).toHaveAttribute(
|
|
"data-warning-state",
|
|
"danger"
|
|
);
|
|
await expect(page.getByTestId("pos-order-customer-wishes-po-control")).toHaveAttribute(
|
|
"data-warning-state",
|
|
"warning"
|
|
);
|
|
await expect(page.getByTestId("pos-order-detail")).toHaveScreenshot(
|
|
"pos-order-detail-desktop-required-warnings.png",
|
|
{
|
|
maxDiffPixels: 300,
|
|
}
|
|
);
|
|
});
|
|
|
|
test("desktop step 2 shared workspace snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-2-title-token");
|
|
|
|
await page.goto("/admin/12/modules/pos");
|
|
await expect(page.getByTestId("pos-step-1")).toBeVisible();
|
|
await page.locator("#reg_1").fill("EC21235");
|
|
await expect(page.getByTestId("pos-step-1").locator(".pos-card-tabs > .tabs li.is-active")).toContainText("Kunde");
|
|
|
|
await page.locator('[data-testid="pos-next-step"]:visible').click();
|
|
|
|
const stepTwo = page.getByTestId("pos-step-2");
|
|
const primaryAction = stepTwo.getByTestId("pos-next-step");
|
|
const clearAllAction = stepTwo.getByRole("button", { name: /Slet alle/i });
|
|
await expect(stepTwo).toBeVisible();
|
|
await expect(stepTwo.getByTestId("pos-order-add-items-panel")).toBeVisible();
|
|
await expect(stepTwo.getByTestId("pos-order-panel-cart")).toBeVisible();
|
|
await expect(stepTwo.getByTestId("pos-order-metadata-grid")).toBeVisible();
|
|
await expect(primaryAction).toBeVisible();
|
|
await expect(clearAllAction).toBeVisible();
|
|
await expectPrimaryActionAboveClearAll(primaryAction, clearAllAction);
|
|
await expect(stepTwo).toHaveScreenshot("pos-step-2-desktop.png", {
|
|
maxDiffPixels: 300,
|
|
});
|
|
});
|
|
|
|
test("desktop step 3 shared workspace snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
test.skip(testInfo.project.name === "firefox-desktop", "Step 3 workspace height is unstable on Firefox desktop.");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-3-token");
|
|
|
|
const customerResponse = page.waitForResponse((response) => {
|
|
return (
|
|
response.request().method() === "GET" && response.url().includes("/users/customer?customer_number=12345679")
|
|
);
|
|
});
|
|
await page.goto("/admin/12/modules/pos?id=54518&customer_id=12345679&step=3");
|
|
await customerResponse;
|
|
const stepThree = page.getByTestId("pos-step-3");
|
|
const workspace = stepThree.getByTestId("pos-order-workspace");
|
|
const primaryAction = stepThree.getByTestId("pos-next-step");
|
|
const clearAllAction = stepThree.getByRole("button", { name: /Slet alle/i });
|
|
await expect(stepThree).toBeVisible();
|
|
await expect(workspace).toBeVisible();
|
|
await expect(stepThree.getByTestId("pos-order-panel-cart")).toBeVisible();
|
|
await expect(stepThree.getByTestId("pos-order-metadata-grid")).toBeVisible();
|
|
await expect(stepThree.getByTestId("pos-order-rail")).toBeVisible();
|
|
await expect(stepThree).toContainText("Tilføj flere varer");
|
|
await expect(primaryAction).toBeVisible();
|
|
await expect(clearAllAction).toBeVisible();
|
|
await expect(stepThree.getByRole("button", { name: /Fuldfør/i })).toBeVisible();
|
|
await expectPrimaryActionAboveClearAll(primaryAction, clearAllAction);
|
|
const stepThreeCartPanel = stepThree.getByTestId("pos-order-panel-cart");
|
|
const clipByProject = {
|
|
"chromium-desktop": { width: 460, height: 369 },
|
|
"webkit-desktop": { width: 452, height: 367 },
|
|
};
|
|
await expectClippedLocatorScreenshot(page, stepThreeCartPanel, "pos-step-3-desktop.png", {
|
|
...(clipByProject[testInfo.project.name] || { width: 460, height: 369 }),
|
|
maxDiffPixels: 2500,
|
|
});
|
|
});
|
|
|
|
test("desktop step 4 shared workspace snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
test.skip(testInfo.project.name === "firefox-desktop", "Step 4 workspace height is unstable on Firefox desktop.");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-4-token");
|
|
|
|
const customerResponse = page.waitForResponse((response) => {
|
|
return (
|
|
response.request().method() === "GET" && response.url().includes("/users/customer?customer_number=12345679")
|
|
);
|
|
});
|
|
await page.goto("/admin/12/modules/pos?id=54518&customer_id=12345679&step=4");
|
|
await customerResponse;
|
|
const stepFour = page.getByTestId("pos-step-4");
|
|
const workspace = stepFour.getByTestId("pos-order-workspace");
|
|
const primaryAction = stepFour.getByTestId("pos-next-step");
|
|
const clearAllAction = stepFour.getByRole("button", { name: /Slet alle/i });
|
|
await expect(stepFour).toBeVisible();
|
|
await expect(workspace).toBeVisible();
|
|
await expect(stepFour.getByTestId("pos-order-panel-cart")).toBeVisible();
|
|
await expect(stepFour.getByTestId("pos-order-metadata-grid")).toBeVisible();
|
|
await expect(stepFour.getByTestId("pos-order-rail")).toBeVisible();
|
|
await expect(stepFour).toContainText("Tilføj flere varer");
|
|
await expect(primaryAction).toBeVisible();
|
|
await expect(clearAllAction).toBeVisible();
|
|
await expect(stepFour.getByRole("button", { name: /Fuldfør/i })).toBeVisible();
|
|
await expectPrimaryActionAboveClearAll(primaryAction, clearAllAction);
|
|
const detailItemsTable = stepFour.getByTestId("pos-order-detail-items-table");
|
|
const clipByProject = {
|
|
"chromium-desktop": { width: 423, height: 288 },
|
|
"webkit-desktop": { width: 415, height: 287 },
|
|
};
|
|
await expectClippedLocatorScreenshot(page, detailItemsTable, "pos-step-4-desktop.png", {
|
|
...(clipByProject[testInfo.project.name] || { width: 423, height: 288 }),
|
|
maxDiffPixels: 2500,
|
|
});
|
|
});
|
|
|
|
test("desktop step 3 guest stripe action order", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
const guestStripeCustomer = {
|
|
id: 2,
|
|
customerNumber: 999,
|
|
name: "Card Terminal Customer",
|
|
address: "Terminal Street 9",
|
|
zip: "2630",
|
|
city: "Taastrup",
|
|
mobilePhone: "87654321",
|
|
email: "card@example.com",
|
|
corporateIdentificationNumber: "99999999",
|
|
economic_customer: 999,
|
|
barred: false,
|
|
};
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture({
|
|
customersByNumber: {
|
|
999: guestStripeCustomer,
|
|
12345679: guestStripeCustomer,
|
|
},
|
|
ordersById: {
|
|
54518: {
|
|
id: 54518,
|
|
customer_id: 999,
|
|
department_id: 12,
|
|
reference: "CARD-REF-54518",
|
|
notes: "Paid at terminal",
|
|
reg_1: "ZZ99999",
|
|
reg_2: "",
|
|
reg_3: "",
|
|
invoice_collection_id: null,
|
|
booking_id: null,
|
|
completed_at: null,
|
|
closed_at: null,
|
|
created_at: "2026-04-08 08:44:07",
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-step-3-guest-token");
|
|
|
|
await page.goto("/admin/12/modules/pos?id=54518&customer_id=999&step=3");
|
|
const stepThree = page.getByTestId("pos-step-3");
|
|
const primaryAction = stepThree
|
|
.locator(
|
|
'[data-testid="pos-stripe-create-intent"], [data-testid="pos-stripe-capture-intent"], [data-testid="pos-stripe-no-readers"], [data-testid="pos-stripe-payment-in-progress"], [data-testid="pos-stripe-payment-succeeded"], [data-testid="pos-stripe-error"]'
|
|
)
|
|
.first();
|
|
const clearAllAction = stepThree.getByRole("button", { name: /Slet alle/i });
|
|
|
|
await expect(stepThree).toBeVisible();
|
|
await expect(stepThree.getByTestId("pos-order-panel-cart")).toBeVisible();
|
|
await expect(stepThree.getByTestId("pos-order-metadata-grid")).toBeVisible();
|
|
await expect(stepThree.getByTestId("pos-order-rail")).toBeVisible();
|
|
await expect(primaryAction).toBeVisible();
|
|
await expect(clearAllAction).toBeVisible();
|
|
await expectPrimaryActionAboveClearAll(primaryAction, clearAllAction);
|
|
});
|
|
|
|
test("desktop order detail add-items workspace snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-add-items-token");
|
|
|
|
await openOrderDetailAddItems(page);
|
|
await expect(getVisibleTestId(page, "pos-order-workspace")).toContainText("Transaktion #54518");
|
|
await expect(getVisibleTestId(page, "pos-product-card-53")).toBeVisible();
|
|
await expect(getVisibleTestId(page, "pos-order-item-edit-9101")).toBeVisible();
|
|
await expect(getVisibleTestId(page, "pos-order-total")).toContainText("1372 DKK");
|
|
await expect(getVisibleTestId(page, "pos-order-workspace")).toHaveScreenshot(
|
|
"pos-order-detail-add-items-desktop.png",
|
|
{
|
|
maxDiffPixels: 300,
|
|
}
|
|
);
|
|
});
|
|
|
|
test("desktop order detail edit modal snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("desktop"), "Desktop only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-desktop-edit-modal-token");
|
|
|
|
await page.goto("/admin/12/modules/pos/orders/54518");
|
|
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
|
|
await page.getByTestId("pos-order-item-edit-9101").click();
|
|
await expect(page.getByTestId("pos-order-item-edit-modal")).toBeVisible();
|
|
await expect(page.getByTestId("pos-order-item-edit-modal")).toHaveScreenshot(
|
|
"pos-order-detail-edit-modal-desktop.png",
|
|
{
|
|
maxDiffPixels: 300,
|
|
}
|
|
);
|
|
});
|
|
|
|
test("mobile step 2 snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("mobile"), "Mobile only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await seedMobileStepTwoState(page);
|
|
await primeSession(page, "pos-visual-mobile-step-2-token");
|
|
|
|
await page.goto("/admin/12/modules/pos?id=54518&customer_id=12345679&step=2");
|
|
await expect(page.getByTestId("pos-mobile-step-2")).toBeVisible();
|
|
const customerBannerHeight = await page
|
|
.getByTestId("pos-mobile-customer-banner")
|
|
.evaluate((element) => element.getBoundingClientRect().height);
|
|
expect(customerBannerHeight).toBeLessThan(84);
|
|
await expect(page.getByTestId("pos-mobile-step-2")).toHaveScreenshot("pos-mobile-step-2.png", {
|
|
maxDiffPixels: 300,
|
|
});
|
|
});
|
|
|
|
test("mobile step 1 selected customer CTA snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("mobile"), "Mobile only");
|
|
test.skip(testInfo.project.name === "webkit-mobile", "Seeded customer CTA is unreliable on WebKit mobile.");
|
|
|
|
const longCustomerName = "(TEST) Pleno Vognmandsforretning";
|
|
const fixture = createMobilePosFixture({
|
|
customersByNumber: {
|
|
[REGULAR_CUSTOMER_ID]: {
|
|
id: REGULAR_CUSTOMER_ID,
|
|
customerNumber: REGULAR_CUSTOMER_ID,
|
|
name: longCustomerName,
|
|
address: "Demo Street 1",
|
|
zip: "2630",
|
|
city: "Taastrup",
|
|
mobilePhone: "12345678",
|
|
email: "pos-mobile@example.com",
|
|
corporateIdentificationNumber: "00012345",
|
|
economic_customer: REGULAR_CUSTOMER_ID,
|
|
barred: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
await setupMobilePosPage(page, fixture, {
|
|
token: "pos-visual-mobile-step-1-customer-token",
|
|
permissions: ["admin", "department_access_1"],
|
|
seedState: {
|
|
customerId: REGULAR_CUSTOMER_ID,
|
|
reg: "EC21235",
|
|
reference: "EC21233 - Test Ref. / Intern nummer",
|
|
includePrimaryItem: false,
|
|
vehicleType: null,
|
|
lastOrderId: null,
|
|
},
|
|
route: {
|
|
step: 1,
|
|
},
|
|
});
|
|
|
|
await expect(page.getByTestId("pos-mobile-step-1")).toBeVisible();
|
|
const nextStepButton = page.getByTestId("pos-mobile-next-step");
|
|
const hasSelectedCustomer = await nextStepButton
|
|
.textContent()
|
|
.then((value) => String(value || "").includes(longCustomerName))
|
|
.catch(() => false);
|
|
|
|
if (!hasSelectedCustomer) {
|
|
await nextStepButton.click();
|
|
await page.getByTestId(`pos-mobile-customer-option-${REGULAR_CUSTOMER_ID}`).first().click();
|
|
}
|
|
|
|
await expect(nextStepButton).toContainText(longCustomerName, { timeout: 10_000 });
|
|
await expect(page.getByTestId("pos-mobile-fixed-actions")).toHaveScreenshot("pos-mobile-step-1-fixed-actions.png", {
|
|
maxDiffPixels: 300,
|
|
});
|
|
});
|
|
|
|
test("mobile transaction history actions keep text left and icons right", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("mobile"), "Mobile only");
|
|
|
|
const fixture = createMobilePosFixture();
|
|
|
|
await setupMobilePosPage(page, fixture, {
|
|
token: "pos-visual-mobile-transaction-history-token",
|
|
permissions: ["admin", "department_access_1"],
|
|
seedState: {
|
|
customerId: REGULAR_CUSTOMER_ID,
|
|
reg: "AB12345",
|
|
includePrimaryItem: false,
|
|
transactionHistoryView: true,
|
|
lastOrderId: 9201,
|
|
},
|
|
route: {
|
|
step: 1,
|
|
},
|
|
});
|
|
|
|
await expect(page.getByTestId("pos-mobile-transaction-history")).toBeVisible();
|
|
const reloadAction = page.getByTestId("pos-mobile-transaction-history-reload-action");
|
|
const reloadLabel = page.getByTestId("pos-mobile-transaction-history-reload-label");
|
|
const reloadIcon = page.getByTestId("pos-mobile-transaction-history-reload-icon");
|
|
const closeAction = page.getByTestId("pos-mobile-transaction-history-close-action");
|
|
const closeLabel = page.getByTestId("pos-mobile-transaction-history-close-label");
|
|
const closeIcon = page.getByTestId("pos-mobile-transaction-history-close-icon");
|
|
|
|
const [
|
|
reloadActionBounds,
|
|
reloadLabelBounds,
|
|
reloadIconBounds,
|
|
closeActionBounds,
|
|
closeLabelBounds,
|
|
closeIconBounds,
|
|
] = await Promise.all([
|
|
getHorizontalBounds(reloadAction),
|
|
getHorizontalBounds(reloadLabel),
|
|
getHorizontalBounds(reloadIcon),
|
|
getHorizontalBounds(closeAction),
|
|
getHorizontalBounds(closeLabel),
|
|
getHorizontalBounds(closeIcon),
|
|
]);
|
|
|
|
expect(reloadIconBounds.left).toBeGreaterThan(reloadLabelBounds.right + 8);
|
|
expect(reloadActionBounds.right - reloadIconBounds.right).toBeLessThan(24);
|
|
expect(
|
|
Math.abs(
|
|
(reloadIconBounds.top + reloadIconBounds.bottom) / 2 - (reloadActionBounds.top + reloadActionBounds.bottom) / 2
|
|
)
|
|
).toBeLessThan(6);
|
|
|
|
expect(closeIconBounds.left).toBeGreaterThan(closeLabelBounds.right + 8);
|
|
expect(closeActionBounds.right - closeIconBounds.right).toBeLessThan(24);
|
|
expect(
|
|
Math.abs(
|
|
(closeIconBounds.top + closeIconBounds.bottom) / 2 - (closeActionBounds.top + closeActionBounds.bottom) / 2
|
|
)
|
|
).toBeLessThan(6);
|
|
});
|
|
|
|
test("mobile attachment view actions keep the same side inset", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("mobile"), "Mobile only");
|
|
test.skip(
|
|
testInfo.project.name === "webkit-mobile",
|
|
"Attachment view controls do not render consistently on WebKit mobile."
|
|
);
|
|
|
|
const fixture = createMobilePosFixture();
|
|
|
|
await setupMobilePosPage(page, fixture, {
|
|
token: "pos-visual-mobile-attachment-view-token",
|
|
permissions: ["admin", "department_access_1"],
|
|
seedState: {
|
|
customerId: REGULAR_CUSTOMER_ID,
|
|
reg: "AB12345",
|
|
includePrimaryItem: false,
|
|
lastOrderId: null,
|
|
},
|
|
route: {
|
|
step: 1,
|
|
},
|
|
});
|
|
|
|
await expect(page.getByTestId("pos-mobile-step-1")).toBeVisible();
|
|
await page.getByTestId("pos-mobile-attachments-toggle").click();
|
|
|
|
const closeAction = page.getByTestId("pos-mobile-attachment-view-close-action");
|
|
if (!(await closeAction.isVisible().catch(() => false))) {
|
|
await page.getByTestId("pos-mobile-attachments-toggle").click({ force: true });
|
|
}
|
|
await expect(closeAction).toBeVisible({ timeout: 10_000 });
|
|
|
|
const takePictureAction = page.getByTestId("pos-mobile-attachment-view-take-picture-action");
|
|
if (!(await takePictureAction.isVisible().catch(() => false))) {
|
|
test.skip(testInfo.project.name.includes("webkit"), "Take-picture action is unavailable on WebKit mobile.");
|
|
}
|
|
|
|
const takePictureLabel = page.getByTestId("pos-mobile-attachment-view-take-picture-label");
|
|
const takePictureIcon = page.getByTestId("pos-mobile-attachment-view-take-picture-icon");
|
|
const closeLabel = page.getByTestId("pos-mobile-attachment-view-close-label");
|
|
const closeIcon = page.getByTestId("pos-mobile-attachment-view-close-icon");
|
|
|
|
const [
|
|
takePictureActionBounds,
|
|
takePictureLabelBounds,
|
|
takePictureIconBounds,
|
|
closeActionBounds,
|
|
closeLabelBounds,
|
|
closeIconBounds,
|
|
] = await Promise.all([
|
|
getHorizontalBounds(takePictureAction),
|
|
getHorizontalBounds(takePictureLabel),
|
|
getHorizontalBounds(takePictureIcon),
|
|
getHorizontalBounds(closeAction),
|
|
getHorizontalBounds(closeLabel),
|
|
getHorizontalBounds(closeIcon),
|
|
]);
|
|
|
|
expect(takePictureIconBounds.left).toBeGreaterThan(takePictureLabelBounds.right + 8);
|
|
expect(takePictureActionBounds.right - takePictureIconBounds.right).toBeLessThan(24);
|
|
expect(
|
|
Math.abs(
|
|
(takePictureIconBounds.top + takePictureIconBounds.bottom) / 2 -
|
|
(takePictureActionBounds.top + takePictureActionBounds.bottom) / 2
|
|
)
|
|
).toBeLessThan(6);
|
|
|
|
expect(closeIconBounds.left).toBeGreaterThan(closeLabelBounds.right + 8);
|
|
expect(closeActionBounds.right - closeIconBounds.right).toBeLessThan(24);
|
|
expect(
|
|
Math.abs(
|
|
(closeIconBounds.top + closeIconBounds.bottom) / 2 - (closeActionBounds.top + closeActionBounds.bottom) / 2
|
|
)
|
|
).toBeLessThan(6);
|
|
});
|
|
|
|
test("mobile order detail add-items workspace snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("mobile"), "Mobile only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture(),
|
|
});
|
|
await primeSession(page, "pos-visual-mobile-add-items-token");
|
|
|
|
await openOrderDetailAddItems(page, { mobile: true });
|
|
await waitForOrderDetailMetadata(page);
|
|
await expect(getVisibleTestId(page, "pos-order-panel-cart")).toHaveScreenshot(
|
|
"pos-order-detail-add-items-mobile.png",
|
|
{
|
|
maxDiffPixels: 300,
|
|
}
|
|
);
|
|
});
|
|
|
|
test("mobile step 3 snapshot", async ({ page }, testInfo) => {
|
|
test.skip(!testInfo.project.name.includes("mobile"), "Mobile only");
|
|
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: POS_PERMISSIONS,
|
|
edgeGateways: false,
|
|
pos: createPosFixture({
|
|
ordersById: {
|
|
54518: {
|
|
id: 54518,
|
|
customer_id: 999,
|
|
department_id: 12,
|
|
reference: "CARD-REF-54518",
|
|
notes: "Paid at terminal",
|
|
reg_1: "EC21235",
|
|
reg_2: "",
|
|
reg_3: "",
|
|
invoice_collection_id: null,
|
|
booking_id: null,
|
|
completed_at: null,
|
|
closed_at: null,
|
|
created_at: "2026-04-08 08:44:07",
|
|
},
|
|
},
|
|
paymentIntentsByOrderId: {
|
|
54518: {
|
|
id: "pi_54518",
|
|
amount: 123400,
|
|
amount_capturable: 123400,
|
|
amount_received: 0,
|
|
currency: "dkk",
|
|
status: "requires_capture",
|
|
metadata: {
|
|
order_id: "54518",
|
|
reader_id: "reader_online_1",
|
|
tax_percentage: "25",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
await seedMobileStepTwoState(page, { customerId: 999 });
|
|
await primeSession(page, "pos-visual-mobile-step-3-token");
|
|
|
|
await page.goto("/admin/12/modules/pos?id=54518&customer_id=999&step=3");
|
|
await expect(page.getByTestId("pos-mobile-step-3")).toBeVisible();
|
|
await expect(page.getByTestId("pos-mobile-step-3")).toHaveScreenshot("pos-mobile-step-3.png", {
|
|
maxDiffPixels: 300,
|
|
});
|
|
});
|
|
});
|