diff --git a/README.md b/README.md index a62bf8d6..2b421b09 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Default worker allocation: ```sh PLAYWRIGHT_PARALLEL_WORKERS_CHROMIUM=2 PLAYWRIGHT_PARALLEL_WORKERS_FIREFOX=1 -PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=2 +PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=1 ``` Optional overrides: @@ -132,7 +132,7 @@ Optional overrides: PLAYWRIGHT_PARALLEL_BASE_PORT=5191 PLAYWRIGHT_PARALLEL_WORKERS_CHROMIUM=2 PLAYWRIGHT_PARALLEL_WORKERS_FIREFOX=1 -PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=2 +PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=1 ``` The runner fails fast if the combined worker count exceeds 5. diff --git a/scripts/run-playwright-ci-parallel.mjs b/scripts/run-playwright-ci-parallel.mjs index 09b0f27a..52cde50a 100644 --- a/scripts/run-playwright-ci-parallel.mjs +++ b/scripts/run-playwright-ci-parallel.mjs @@ -28,7 +28,7 @@ const groups = [ { name: "webkit", projects: ["webkit-desktop", "webkit-tablet", "webkit-mobile"], - defaultWorkers: 2, + defaultWorkers: 1, }, ]; diff --git a/tests/e2e/fixtures/authHelpers.ts b/tests/e2e/fixtures/authHelpers.ts index 3bb67364..c4db59b4 100644 --- a/tests/e2e/fixtures/authHelpers.ts +++ b/tests/e2e/fixtures/authHelpers.ts @@ -26,6 +26,7 @@ const isBenignNavigationError = (error: unknown) => { return ( message.includes("interrupted by another navigation") || message.includes("ERR_ABORTED") || + message.includes("NS_BINDING_ABORTED") || message.includes("Frame load interrupted") ); }; @@ -38,10 +39,6 @@ function fulfillJson(route: Route, body: unknown, status = 200) { }); } -function getErrorMessage(error: unknown) { - return error instanceof Error ? error.message : String(error); -} - async function hasConnectivityIssue(page: Page) { return page .getByText(CONNECTIVITY_ISSUE_PATTERN) diff --git a/tests/e2e/invoice-distribution.smoke.spec.js b/tests/e2e/invoice-distribution.smoke.spec.js index bd3ce2d7..9d74510e 100644 --- a/tests/e2e/invoice-distribution.smoke.spec.js +++ b/tests/e2e/invoice-distribution.smoke.spec.js @@ -57,6 +57,20 @@ async function prepareInvoiceDistributionPage(page, 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 = []; @@ -68,7 +82,7 @@ test.describe("Invoice distribution smoke", () => { }); await prepareInvoiceDistributionPage(page); - await page.goto("/superuser/invoices?activeTab=distribution"); + 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(); @@ -95,7 +109,8 @@ test.describe("Invoice distribution smoke", () => { test("@smoke monthly tabs keep URL query-state in sync", async ({ page }) => { await prepareInvoiceDistributionPage(page); - await page.goto( + await gotoInvoiceDistribution( + page, "/superuser/invoices/distribution/2026/3/customers?customerSearch=acme&customerSource=fixed_pricing&customerDepartment=Copenhagen&compareMode=line_by_line" ); @@ -118,7 +133,7 @@ test.describe("Invoice distribution smoke", () => { test("@smoke compare flow shows progress and mismatch-first results", async ({ page }) => { await prepareInvoiceDistributionPage(page); - await page.goto("/superuser/invoices/distribution/2026/3/compare?compareMode=line_by_line"); + 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"); @@ -139,7 +154,7 @@ test.describe("Invoice distribution smoke", () => { test("@smoke mobile layout sanity keeps primary controls visible", async ({ page }) => { await prepareInvoiceDistributionPage(page); - await page.goto("/superuser/invoices/distribution/2026/3/overview"); + 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(); @@ -155,12 +170,12 @@ test.describe("Invoice distribution smoke", () => { invoiceDistributionForceCompareFallback: true, }); - await page.goto("/superuser/invoices?activeTab=distribution"); + await gotoInvoiceDistribution(page, "/superuser/invoices?activeTab=distribution"); await expect(page.locator(".message.is-warning").filter({ hasText: /v2/i }).first()).toBeVisible({ timeout: 15_000, }); - await page.goto("/superuser/invoices/distribution/2026/3/compare?compareMode=line_by_line"); + await gotoInvoiceDistribution(page, "/superuser/invoices/distribution/2026/3/compare?compareMode=line_by_line"); await expect(page.locator(".message.is-warning").filter({ hasText: /v2/i }).first()).toBeVisible({ timeout: 15_000, }); diff --git a/tests/e2e/release-bootstrap.spec.js b/tests/e2e/release-bootstrap.spec.js index d8929174..529bd9a4 100644 --- a/tests/e2e/release-bootstrap.spec.js +++ b/tests/e2e/release-bootstrap.spec.js @@ -87,7 +87,7 @@ test("non-default release channel keeps the regular frontend and dynamic API run expect(page.url()).toContain("/shared/passkey-safe-link"); expect(page.url()).not.toContain("api-v2.truckwash.io"); - expect(runtimeRequests).toHaveLength(1); + await expect.poll(() => runtimeRequests.length).toBe(1); const runtimeRequestUrl = new URL(runtimeRequests[0]); expect(["/api/release/runtime", "/master/api/release/runtime"]).toContain(runtimeRequestUrl.pathname); expect(runtimeRequestUrl.pathname).not.toBe("/canary/api/release/runtime"); diff --git a/tests/e2e/twoFactorAuth.spec.ts b/tests/e2e/twoFactorAuth.spec.ts index dc5c30b9..567c1014 100644 --- a/tests/e2e/twoFactorAuth.spec.ts +++ b/tests/e2e/twoFactorAuth.spec.ts @@ -120,6 +120,7 @@ function isBenignNavigationError(error) { return ( message.includes("interrupted by another navigation") || message.includes("ERR_ABORTED") || + message.includes("NS_BINDING_ABORTED") || message.includes("Frame load interrupted") ); } diff --git a/tests/e2e/userAuth.spec.ts b/tests/e2e/userAuth.spec.ts index 25ecfd57..ccda3667 100644 --- a/tests/e2e/userAuth.spec.ts +++ b/tests/e2e/userAuth.spec.ts @@ -48,6 +48,7 @@ function isBenignNavigationError(error) { return ( message.includes("interrupted by another navigation") || message.includes("ERR_ABORTED") || + message.includes("NS_BINDING_ABORTED") || message.includes("Frame load interrupted") ); } diff --git a/tests/e2e/userBookings.spec.ts b/tests/e2e/userBookings.spec.ts index 8d5a9ab9..5eadb3ec 100644 --- a/tests/e2e/userBookings.spec.ts +++ b/tests/e2e/userBookings.spec.ts @@ -28,6 +28,8 @@ test("[PAGES][User][/user/bookings] should display the title", async ({ page }) }); test("[BOOKINGS][User][Mobile] should keep booking overview cards within the viewport", async ({ page }) => { + const today = new Date().toISOString().split("T")[0]; + await mockApi(page, { authenticated: true, sessionData: { @@ -42,7 +44,7 @@ test("[BOOKINGS][User][Mobile] should keep booking overview cards within the vie customer_number: 12345679, customer_name: "Pleno Logistics ApS", department: 12, - datetime: "2026-05-28 08:30:00", + datetime: `${today} 08:30:00`, reg_1: "AB12345", reg_2: "CD67890", reference: "Lang intern reference der tidligere pressede kortet ud over kanten", @@ -65,7 +67,7 @@ test("[BOOKINGS][User][Mobile] should keep booking overview cards within the vie customer_number: 12345679, customer_name: "Pleno Logistics ApS", department: 1, - datetime: "2026-05-28 14:15:00", + datetime: `${today} 14:15:00`, reg_1: "EF24680", reg_2: "", reference: "",