Files
pleno-vue/tests/e2e/invoice-distribution.smoke.spec.js
T

201 lines
8.0 KiB
JavaScript

import { expect, test } from "@playwright/test";
import { mockApi, primeMockSession } from "./support/network.js";
const FALLBACK_WARNING_TIMEOUT_MS = 30_000;
function currentUtcMonthStartIso() {
const now = new Date();
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0)).toISOString();
}
async function expectV2FallbackWarning(locator) {
await expect(locator).toBeVisible({ timeout: FALLBACK_WARNING_TIMEOUT_MS });
await expect(locator).toContainText(/v2/i, { timeout: FALLBACK_WARNING_TIMEOUT_MS });
}
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 primeSuperuserSession(page) {
const token = "superuser-e2e-token";
await primeMockSession(page, { token, bootPath: null });
}
async function prepareInvoiceDistributionPage(page, overrides = {}) {
await suppressVueDevtoolsOverlay(page);
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
invoiceDistribution: true,
...overrides,
});
await primeSuperuserSession(page);
}
async function gotoInvoiceDistribution(page, url) {
for (let attempt = 0; attempt < 2; attempt += 1) {
try {
await page.goto(url);
return;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (attempt > 0 || !message.includes("WebKit encountered an internal error")) {
throw error;
}
}
}
}
test.describe("Invoice distribution smoke", () => {
test("@smoke @pr overview loads and quick-open month action works", async ({ page }) => {
const componentWarnings = [];
page.on("console", (message) => {
const text = message.text();
if (text.includes("Failed to resolve component: b-card")) {
componentWarnings.push(text);
}
});
await prepareInvoiceDistributionPage(page);
await gotoInvoiceDistribution(page, "/superuser/invoices?activeTab=distribution");
await expect(page.getByTestId("distribution-overview-page")).toBeVisible({ timeout: 15_000 });
await expect(page.locator('[data-testid="distribution-overview-page"] h2').first()).toBeVisible();
expect(componentWarnings).toEqual([]);
const openMonthButton = page.getByTestId("distribution-overview-open-month").first();
await expect(openMonthButton).toBeVisible({ timeout: 15_000 });
const distributionMonthUrl = /\/superuser\/invoices\/distribution\/\d+\/\d+/;
const popupPromise = page.waitForEvent("popup", { timeout: 10_000 }).catch(() => null);
const samePageNavigationPromise = page
.waitForURL(distributionMonthUrl, { timeout: 10_000 })
.then(() => null)
.catch(() => null);
await openMonthButton.click();
const popup = await Promise.race([popupPromise, samePageNavigationPromise]);
if (popup) {
await expect(popup).toHaveURL(distributionMonthUrl);
return;
}
await expect(page).toHaveURL(distributionMonthUrl);
});
test("@smoke monthly tabs keep URL query-state in sync", async ({ page }) => {
await prepareInvoiceDistributionPage(page);
await gotoInvoiceDistribution(
page,
"/superuser/invoices/distribution/2026/3/customers?customerSearch=acme&customerSource=fixed_pricing&customerDepartment=Copenhagen&compareMode=line_by_line"
);
await expect(page.getByTestId("distribution-month-tabs")).toBeVisible();
await expect(page).toHaveURL(/customerSearch=acme/);
await expect(page).toHaveURL(/customerSource=fixed_pricing/);
await page.getByRole("tab", { name: /Departments|Afdelinger/i }).click();
await page.getByTestId("distribution-department-search").fill("Odense");
await expect(page).toHaveURL(/departmentSearch=Odense/);
await page.getByRole("tab", { name: /Customers|Kunder/i }).click();
await page.getByTestId("distribution-customer-search").fill("Nordic");
await expect(page).toHaveURL(/customerSearch=Nordic/);
await page.getByRole("tab", { name: /Compare|Sammenlign/i }).click();
await expect(page).toHaveURL(/\/compare/);
await expect(page).toHaveURL(/compareMode=line_by_line/);
});
test("@smoke compare flow shows progress and mismatch-first results", async ({ page }) => {
await prepareInvoiceDistributionPage(page);
await gotoInvoiceDistribution(page, "/superuser/invoices/distribution/2026/3/compare?compareMode=line_by_line");
await page.getByTestId("distribution-compare-submit").click();
const compareTable = page.getByTestId("distribution-compare-table");
const progress = page.locator(".compare-progress");
await Promise.race([
progress.waitFor({ state: "visible", timeout: 6_000 }).catch(() => null),
compareTable.waitFor({ state: "visible", timeout: 10_000 }),
]);
await expect(compareTable).toBeVisible();
await expect(compareTable.getByText("#101")).toBeVisible();
await expect(compareTable.getByText("#102")).toBeVisible();
const firstDataRow = compareTable.locator("tbody tr").first();
await expect(firstDataRow).toContainText("#101");
});
test("@smoke mobile layout sanity keeps primary controls visible", async ({ page }) => {
await prepareInvoiceDistributionPage(page);
await gotoInvoiceDistribution(page, "/superuser/invoices/distribution/2026/3/overview");
await expect(page.getByTestId("distribution-month-toolbar")).toBeVisible();
await expect(page.getByTestId("distribution-month-prev")).toBeVisible();
await expect(page.getByTestId("distribution-month-next")).toBeVisible();
await expect(page.locator(".summary-ribbon:visible").first()).toBeVisible();
await page.getByRole("tab", { name: /Departments|Afdelinger/i }).click();
await expect(page.locator(".table-container--scroll:visible").first()).toBeVisible();
});
test("@smoke fallback mode keeps results and surfaces warning banners", async ({ page }) => {
test.setTimeout(90_000);
await prepareInvoiceDistributionPage(page, {
invoiceDistributionFirstOrderDate: currentUtcMonthStartIso(),
invoiceDistributionForceLegacyFallback: true,
invoiceDistributionForceCompareFallback: true,
});
await gotoInvoiceDistribution(page, "/superuser/invoices?activeTab=distribution");
await expect(page.getByTestId("distribution-overview-page")).toBeVisible({ timeout: 15_000 });
await expectV2FallbackWarning(page.getByTestId("distribution-overview-legacy-fallback-warning"));
await gotoInvoiceDistribution(page, "/superuser/invoices/distribution/2026/3/compare?compareMode=line_by_line");
await expect(page.getByTestId("distribution-month-tabs")).toBeVisible({ timeout: 15_000 });
await expectV2FallbackWarning(page.getByTestId("distribution-month-legacy-fallback-warning"));
await page.getByTestId("distribution-compare-submit").click();
await expect(page.getByTestId("distribution-compare-table")).toBeVisible({ timeout: 15_000 });
await expectV2FallbackWarning(page.getByTestId("distribution-compare-legacy-fallback-warning"));
});
});