Files
pleno-vue/tests/e2e/economic-queue-workflow.spec.js
T
Jeppe Bundgaard ade565cbca Introduce completeBookingCreationFlow utility and refactor booking flow tests:
- Added `completeBookingCreationFlow` utility to streamline booking creation logic in `bookingFlow.ts`.
- Refactored `userBookings.spec.ts` to replace inline booking creation steps with the new utility for improved readability and consistency.
- Applied `data-testid` attributes across booking-related components for enhanced testability and test coverage.
- Enhanced Playwright navigation error handling with `isBenignNavigationError` and `settleAuthenticatedNavigation` utilities to improve resilience and test robustness.
2026-04-13 13:39:48 +02:00

512 lines
14 KiB
JavaScript

import { expect, test } from "@playwright/test";
import { mockApi } from "./support/network.js";
function json(body, status = 200) {
return {
status,
contentType: "application/json",
body: JSON.stringify(body),
};
}
function apiMessage(message, status = 422) {
return json(
{
data: {
message,
},
},
status
);
}
async function suppressVueDevtoolsOverlay(page) {
await page.addInitScript(() => {
const STYLE_ID = "__e2e-hide-vue-devtools";
const apply = () => {
const target = document.head || document.documentElement;
if (!target) {
return;
}
if (!document.getElementById(STYLE_ID)) {
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent =
"#__vue-devtools-container__, .vue-devtools__anchor-btn, .vue-devtools__panel-content { display: none !important; visibility: hidden !important; pointer-events: none !important; }";
target.appendChild(style);
}
const container = document.getElementById("__vue-devtools-container__");
if (container) {
container.style.display = "none";
container.style.pointerEvents = "none";
}
};
apply();
const startObserving = () => {
if (!document.documentElement) {
requestAnimationFrame(startObserving);
return;
}
const observer = new MutationObserver(apply);
observer.observe(document.documentElement, { childList: true, subtree: true });
};
startObserving();
});
}
async function openHarness(page) {
await page.goto("/__e2e/economic-queue");
await expect(page.getByTestId("economic-queue-harness-page")).toBeVisible();
}
test.describe("Economic queue async export workflow", () => {
test.beforeEach(async ({ page }) => {
await suppressVueDevtoolsOverlay(page);
await mockApi(page);
});
test("invoice export enqueues, polls, completes, and stops polling in terminal state", async ({ page }) => {
let statusCalls = 0;
await page.route("**/economic/invoice/export", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
await route.fulfill(
json(
{
data: {
job: {
id: 1101,
status: "QUEUED",
progress_percent: 0,
progress_message: "Queued export",
},
},
},
202
)
);
});
await page.route("**/economic/invoice/export/status**", async (route) => {
if (route.request().method() !== "GET") {
await route.fallback();
return;
}
statusCalls += 1;
if (statusCalls === 1) {
await route.fulfill(
json({
data: {
id: 1101,
status: "PROCESSING",
progress_percent: 40,
progress_message: "Preparing invoice payload",
},
})
);
return;
}
await route.fulfill(
json({
data: {
id: 1101,
status: "COMPLETED",
progress_percent: 100,
progress_message: "Completed",
result: {
message: "Invoice export completed by queue worker.",
},
},
})
);
});
await openHarness(page);
const submitButton = page.getByTestId("economic-invoice-export-submit");
await submitButton.click();
await expect(submitButton).toBeDisabled();
await expect(page.getByTestId("economic-invoice-export-progress")).toContainText("Preparing invoice payload");
await expect(page.getByTestId("economic-invoice-export-completed")).toContainText(
"Invoice export completed by queue worker."
);
await expect(page.getByTestId("economic-invoice-export-completed")).toBeVisible();
const terminalCallCount = statusCalls;
await page.waitForTimeout(2300);
expect(statusCalls).toBe(terminalCallCount);
});
test("invoice export shows queue failure message and supports retry to completion", async ({ page }) => {
let phase = "initial";
let retryStatusCalls = 0;
await page.route("**/economic/invoice/export", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
await route.fulfill(
json(
{
data: {
job: {
id: 1201,
status: "QUEUED",
progress_percent: 0,
progress_message: "Queued export",
},
},
},
202
)
);
});
await page.route("**/economic/invoice/export/retry", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
phase = "retry";
await route.fulfill(
json(
{
data: {
job: {
id: 1201,
status: "QUEUED",
progress_percent: 0,
progress_message: "Retry queued",
},
},
},
200
)
);
});
await page.route("**/economic/invoice/export/status**", async (route) => {
if (route.request().method() !== "GET") {
await route.fallback();
return;
}
if (phase === "initial") {
await route.fulfill(
json({
data: {
id: 1201,
status: "FAILED",
progress_percent: 10,
progress_message: "Failed",
error_message: "no billable order items after zero-price/zero-quantity skips",
},
})
);
return;
}
retryStatusCalls += 1;
if (retryStatusCalls === 1) {
await route.fulfill(
json({
data: {
id: 1201,
status: "PROCESSING",
progress_percent: 70,
progress_message: "Retry processing",
},
})
);
return;
}
await route.fulfill(
json({
data: {
id: 1201,
status: "COMPLETED",
progress_percent: 100,
progress_message: "Retry completed",
result: {
message: "Invoice export completed after retry.",
},
},
})
);
});
await openHarness(page);
await page.getByTestId("economic-invoice-export-submit").click();
await expect(page.getByTestId("economic-invoice-export-failed")).toContainText(
"no billable order items after zero-price/zero-quantity skips"
);
await page.getByTestId("economic-invoice-export-retry").click();
await expect(page.getByTestId("economic-invoice-export-progress")).toContainText("Retry processing");
await expect(page.getByTestId("economic-invoice-export-completed")).toContainText(
"Invoice export completed after retry."
);
});
test("draft export surfaces API transport errors separately from queue failure state", async ({ page }) => {
await page.route("**/economic/invoice/draft/export", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
await route.fulfill(apiMessage("order_id is invalid", 422));
});
await openHarness(page);
await page.getByTestId("economic-draft-export-submit").click();
await expect(page.getByTestId("economic-draft-export-transport-error")).toContainText("order_id is invalid");
await expect(page.getByTestId("economic-draft-export-failed")).toHaveCount(0);
});
test("collected invoice create action enqueues and completes with returned result", async ({ page }) => {
let statusCalls = 0;
await page.route("**/collected-invoices/economic", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
const payload = route.request().postDataJSON();
expect(payload).toMatchObject({
id: 6001,
send_as_is: false,
});
await route.fulfill(
json(
{
data: {
job: {
id: 1301,
status: "QUEUED",
progress_percent: 0,
progress_message: "Collected invoice queued",
},
},
},
202
)
);
});
await page.route("**/collected-invoices/economic/queue/status**", async (route) => {
if (route.request().method() !== "GET") {
await route.fallback();
return;
}
statusCalls += 1;
if (statusCalls === 1) {
await route.fulfill(
json({
data: {
id: 1301,
status: "PROCESSING",
progress_percent: 55,
progress_message: "Building e-conomic payload",
},
})
);
return;
}
await route.fulfill(
json({
data: {
id: 1301,
status: "COMPLETED",
progress_percent: 100,
progress_message: "Completed",
result: {
message: "Collected invoice exported.",
},
},
})
);
});
await openHarness(page);
await page.getByTestId("collected-economic-create-invoice").click();
await expect(page.getByTestId("collected-economic-progress")).toContainText("Building e-conomic payload");
await expect(page.getByTestId("collected-economic-completed")).toContainText("Collected invoice exported.");
});
test("collected invoice create accepts enqueue responses with data.job_id alias", async ({ page }) => {
let statusCalls = 0;
await page.route("**/collected-invoices/economic", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
await route.fulfill(
json(
{
data: {
message: "Queued collected invoice export",
job_id: 1302,
job: {
status: "QUEUED",
progress_percent: 0,
progress_message: "Collected invoice queued",
},
},
},
202
)
);
});
await page.route("**/collected-invoices/economic/queue/status**", async (route) => {
if (route.request().method() !== "GET") {
await route.fallback();
return;
}
statusCalls += 1;
if (statusCalls === 1) {
await route.fulfill(
json({
data: {
id: 1302,
status: "PROCESSING",
progress_percent: 65,
progress_message: "Creating draft invoice",
},
})
);
return;
}
await route.fulfill(
json({
data: {
id: 1302,
status: "COMPLETED",
progress_percent: 100,
progress_message: "Completed",
result: {
message: "Collected invoice exported from alias response.",
},
},
})
);
});
await openHarness(page);
await page.getByTestId("collected-economic-create-invoice").click();
await expect(page.getByTestId("collected-economic-progress")).toContainText("Creating draft invoice");
await expect(page.getByTestId("collected-economic-completed")).toContainText(
"Collected invoice exported from alias response."
);
});
test("invalid boolean send_as_is is blocked and shown as validation error", async ({ page }) => {
let enqueueCalls = 0;
await page.route("**/collected-invoices/economic", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
enqueueCalls += 1;
await route.fulfill(apiMessage("This endpoint should not be called for invalid boolean.", 500));
});
await openHarness(page);
await page.getByTestId("economic-queue-harness-invalid-boolean-trigger").click();
await expect(page.getByTestId("economic-queue-harness-invalid-boolean-error")).toContainText(
"send_as_is must be a boolean."
);
expect(enqueueCalls).toBe(0);
});
test("invoice export polling is cancelled when component unmounts", async ({ page }) => {
let statusCalls = 0;
await page.route("**/economic/invoice/export", async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
await route.fulfill(
json(
{
data: {
job: {
id: 1401,
status: "QUEUED",
progress_percent: 0,
progress_message: "Queued export",
},
},
},
202
)
);
});
await page.route("**/economic/invoice/export/status**", async (route) => {
if (route.request().method() !== "GET") {
await route.fallback();
return;
}
statusCalls += 1;
await route.fulfill(
json({
data: {
id: 1401,
status: "PROCESSING",
progress_percent: 25,
progress_message: "Still processing",
},
})
);
});
await openHarness(page);
await page.getByTestId("economic-invoice-export-submit").click();
await expect.poll(() => statusCalls).toBeGreaterThanOrEqual(1);
await page.getByTestId("economic-queue-harness-toggle-invoice-export").click();
const statusCallsAtUnmount = statusCalls;
await page.waitForTimeout(2600);
expect(statusCalls).toBe(statusCallsAtUnmount);
});
});