201 lines
8.0 KiB
JavaScript
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"));
|
|
});
|
|
});
|