From 1abd23cf94756607d3b024f296ea78e8f035147a Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 6 Jul 2026 11:21:09 +0200 Subject: [PATCH 1/5] fix(frontend): respect analytics config before initializing PostHog (#6812) # Description of Changes Please provide a summary of the changes, including: - What was changed - Moved PostHog startup out of `index.tsx` and into a config-aware initializer inside `AppProviders`. - Added a dedicated `usePosthogTracking` hook that only initializes PostHog when `enableAnalytics` is explicitly `true` and `enablePosthog` is not disabled. - Kept cookie-consent handling in the same flow so consent is applied only after PostHog is actually initialized. - Removed the unconditional `PostHogProvider` and `posthog.init(...)` bootstrap from the app entrypoint. - Added targeted frontend tests covering analytics-disabled and analytics-enabled startup behavior. - Why the change was made - The previous frontend bootstrap initialized PostHog before app config was loaded, so disabling analytics in the UI or via environment settings did not prevent PostHog network activity. - This change makes analytics behavior follow the server-provided config instead of always connecting on page load. Closes #6358 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have run `task check` to verify linters, typechecks, and tests pass - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .../src/core/components/AppProviders.tsx | 7 ++ .../core/hooks/usePosthogTracking.test.tsx | 76 +++++++++++++++++ .../src/core/hooks/usePosthogTracking.ts | 83 +++++++++++++++++++ frontend/editor/src/index.tsx | 37 +-------- 4 files changed, 169 insertions(+), 34 deletions(-) create mode 100644 frontend/editor/src/core/hooks/usePosthogTracking.test.tsx create mode 100644 frontend/editor/src/core/hooks/usePosthogTracking.ts diff --git a/frontend/editor/src/core/components/AppProviders.tsx b/frontend/editor/src/core/components/AppProviders.tsx index ede9ed266a..f050ae7f9b 100644 --- a/frontend/editor/src/core/components/AppProviders.tsx +++ b/frontend/editor/src/core/components/AppProviders.tsx @@ -27,6 +27,7 @@ import { AdminTourOrchestrationProvider } from "@app/contexts/AdminTourOrchestra import { PageEditorProvider } from "@app/contexts/PageEditorContext"; import { BannerProvider } from "@app/contexts/BannerContext"; import ErrorBoundary from "@app/components/shared/ErrorBoundary"; +import { usePosthogTracking } from "@app/hooks/usePosthogTracking"; import { useScarfTracking } from "@app/hooks/useScarfTracking"; import { useAppInitialization } from "@app/hooks/useAppInitialization"; import { useLogoAssets } from "@app/hooks/useLogoAssets"; @@ -43,6 +44,11 @@ function ScarfTrackingInitializer() { return null; } +function PosthogTrackingInitializer() { + usePosthogTracking(); + return null; +} + // Component to run app-level initialization (must be inside AppProviders for context access) function AppInitializer() { useAppInitialization(); @@ -122,6 +128,7 @@ export function AppProviders({ retryOptions={appConfigRetryOptions} {...appConfigProviderProps} > + diff --git a/frontend/editor/src/core/hooks/usePosthogTracking.test.tsx b/frontend/editor/src/core/hooks/usePosthogTracking.test.tsx new file mode 100644 index 0000000000..41f5fad7dc --- /dev/null +++ b/frontend/editor/src/core/hooks/usePosthogTracking.test.tsx @@ -0,0 +1,76 @@ +import { ReactNode } from "react"; +import { renderHook, waitFor } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AppConfigProvider } from "@app/contexts/AppConfigContext"; + +const posthogState = vi.hoisted(() => ({ loaded: false })); +const posthogMock = vi.hoisted(() => ({ + get __loaded() { + return posthogState.loaded; + }, + init: vi.fn(() => { + posthogState.loaded = true; + }), + opt_out_capturing: vi.fn(), + opt_in_capturing: vi.fn(), + set_config: vi.fn(), + has_opted_in_capturing: vi.fn(() => false), +})); + +vi.mock("posthog-js", () => ({ + default: posthogMock, +})); + +import { usePosthogTracking } from "@app/hooks/usePosthogTracking"; + +describe("usePosthogTracking", () => { + beforeEach(() => { + posthogState.loaded = false; + posthogMock.init.mockClear(); + posthogMock.opt_out_capturing.mockClear(); + posthogMock.opt_in_capturing.mockClear(); + posthogMock.set_config.mockClear(); + vi.stubEnv("VITE_PUBLIC_POSTHOG_KEY", "test-key"); + vi.stubEnv("VITE_PUBLIC_POSTHOG_HOST", "https://eu.i.posthog.com"); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("does not initialize PostHog when analytics is disabled", async () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + + renderHook(() => usePosthogTracking(), { wrapper }); + + await waitFor(() => { + expect(posthogMock.init).not.toHaveBeenCalled(); + }); + }); + + it("initializes PostHog when analytics is enabled", async () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + + renderHook(() => usePosthogTracking(), { wrapper }); + + await waitFor(() => { + expect(posthogMock.init).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/frontend/editor/src/core/hooks/usePosthogTracking.ts b/frontend/editor/src/core/hooks/usePosthogTracking.ts new file mode 100644 index 0000000000..a8037ec4a7 --- /dev/null +++ b/frontend/editor/src/core/hooks/usePosthogTracking.ts @@ -0,0 +1,83 @@ +import { useEffect } from "react"; +import posthog from "posthog-js"; +import { useAppConfig } from "@app/contexts/AppConfigContext"; + +function applyPosthogConsent(): void { + if (typeof window === "undefined" || !posthog.__loaded) { + return; + } + + const optedIn = + window.CookieConsent?.acceptedService?.("posthog", "analytics") || false; + + if (optedIn) { + posthog.set_config({ persistence: "localStorage+cookie" }); + posthog.opt_in_capturing(); + return; + } + + posthog.opt_out_capturing(); + posthog.set_config({ persistence: "memory" }); +} + +function ensurePosthogInitialized(): boolean { + if (typeof window === "undefined") { + return false; + } + + const posthogKey = import.meta.env.VITE_PUBLIC_POSTHOG_KEY; + const posthogHost = import.meta.env.VITE_PUBLIC_POSTHOG_HOST; + + if (!posthogKey || !posthogHost) { + return false; + } + + if (!posthog.__loaded) { + posthog.init(posthogKey, { + api_host: posthogHost, + defaults: "2025-05-24", + capture_exceptions: true, + debug: false, + opt_out_capturing_by_default: true, + persistence: "memory", + cross_subdomain_cookie: false, + }); + } + + return true; +} + +export function usePosthogTracking(): void { + const { config } = useAppConfig(); + + useEffect(() => { + const analyticsEnabled = config?.enableAnalytics === true; + const posthogEnabled = analyticsEnabled && config?.enablePosthog !== false; + + if (!posthogEnabled) { + if (posthog.__loaded) { + posthog.opt_out_capturing(); + posthog.set_config({ persistence: "memory" }); + } + return; + } + + if (!ensurePosthogInitialized()) { + return; + } + + applyPosthogConsent(); + + const handleConsentChange = () => { + applyPosthogConsent(); + }; + + window.addEventListener("cc:onConsent", handleConsentChange); + window.addEventListener("cc:onChange", handleConsentChange); + + return () => { + window.removeEventListener("cc:onConsent", handleConsentChange); + window.removeEventListener("cc:onChange", handleConsentChange); + }; + }, [config?.enableAnalytics, config?.enablePosthog]); +} diff --git a/frontend/editor/src/index.tsx b/frontend/editor/src/index.tsx index 017138cebc..d776918bd1 100644 --- a/frontend/editor/src/index.tsx +++ b/frontend/editor/src/index.tsx @@ -13,8 +13,6 @@ import { ColorSchemeScript } from "@mantine/core"; import { BrowserRouter } from "react-router-dom"; import App from "@app/App"; import "@app/i18n"; // Initialize i18next -import posthog from "posthog-js"; -import { PostHogProvider } from "@posthog/react"; import { BASE_PATH } from "@app/constants/app"; import { startEagerWasmCompilation } from "@app/services/wasmPrecompiler"; @@ -35,33 +33,6 @@ if (typeof window !== "undefined") { } } -posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, - defaults: "2025-05-24", - capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this - debug: false, - opt_out_capturing_by_default: true, // Opt-out by default, controlled by cookie consent - persistence: "memory", // No cookies/localStorage written until user opts in - cross_subdomain_cookie: false, -}); - -function updatePosthogConsent() { - if (!posthog.__loaded) return; - const optIn = - window.CookieConsent?.acceptedService?.("posthog", "analytics") || false; - if (optIn) { - posthog.set_config({ persistence: "localStorage+cookie" }); - posthog.opt_in_capturing(); - } else { - posthog.opt_out_capturing(); - posthog.set_config({ persistence: "memory" }); - } - console.log("Updated PostHog consent: ", optIn ? "opted in" : "opted out"); -} - -window.addEventListener("cc:onConsent", updatePosthogConsent); -window.addEventListener("cc:onChange", updatePosthogConsent); - const container = document.getElementById("root"); if (!container) { throw new Error("Root container missing in index.html"); @@ -71,10 +42,8 @@ const root = ReactDOM.createRoot(container); // Finds the root DOM element root.render( - - - - - + + + , ); From 12563050a60ce3e07f02795bb3a50007400da89b Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 6 Jul 2026 11:21:41 +0200 Subject: [PATCH 2/5] Generate frontend license report on push (#6877) # Description of Changes `app/allowed-licenses.json` has been modified in preparation for when "org.springframework.boot" is upgraded to version "4.0.7". --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .../frontend-backend-licenses-update.yml | 8 ++++++++ app/allowed-licenses.json | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/.github/workflows/frontend-backend-licenses-update.yml b/.github/workflows/frontend-backend-licenses-update.yml index 41d3f35c6e..cc9aa023e3 100644 --- a/.github/workflows/frontend-backend-licenses-update.yml +++ b/.github/workflows/frontend-backend-licenses-update.yml @@ -98,6 +98,13 @@ jobs: - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 + + - name: Generate frontend license report (Push only) + if: github.event_name == 'push' + env: + PR_IS_FORK: "false" + run: task frontend:licenses:generate + - name: Generate frontend license report (internal PR) if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false env: @@ -353,6 +360,7 @@ jobs: - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 + - name: Check licenses and generate report id: license-check run: task backend:licenses:generate || echo "LICENSE_CHECK_FAILED=true" >> $GITHUB_ENV diff --git a/app/allowed-licenses.json b/app/allowed-licenses.json index cd2fe06a3b..9f1ff96359 100644 --- a/app/allowed-licenses.json +++ b/app/allowed-licenses.json @@ -80,10 +80,18 @@ "moduleName": ".*", "moduleLicense": "Apache License Version 2.0" }, + { + "moduleName": ".*", + "moduleLicense": "Apache License version 2.0" + }, { "moduleName": ".*", "moduleLicense": "Apache License, Version 2.0" }, + { + "moduleName": ".*", + "moduleLicense": "Apache License, version 2.0" + }, { "moduleName": ".*", "moduleLicense": "The Apache License, Version 2.0" @@ -108,6 +116,10 @@ "moduleName": ".*", "moduleLicense": "Mozilla Public License 2.0 (MPL-2.0)" }, + { + "moduleName": ".*", + "moduleLicense": "Mozilla Public License Version 2.0" + }, { "moduleName": ".*", "moduleLicense": "CDDL+GPL License" @@ -172,6 +184,14 @@ "moduleName": ".*", "moduleLicense": "Eclipse Public License, Version 2.0" }, + { + "moduleName": ".*", + "moduleLicense": "EPL-2.0" + }, + { + "moduleName": ".*", + "moduleLicense": "LGPL-2.1-only" + }, { "moduleName": ".*", "moduleLicense": "Ubuntu Font Licence 1.0" From a15e8227b4db9b07c158d08a7cf9e1ffa49f0b5f Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 6 Jul 2026 11:21:57 +0200 Subject: [PATCH 3/5] fix(ci): upload Playwright reports from the correct frontend directory (#6859) # Description of Changes This change fixes the artifact upload path used by the Playwright E2E workflows after the frontend directory structure was updated. ### What was changed - Updated the Playwright report artifact path from: - `frontend/editor/playwright-report/` - to `frontend/playwright-report/` - Applied the fix to: - `build-enterprise.yml` - `e2e-stubbed.yml` - `nightly.yml` - Renamed the nightly Playwright artifact from: - `playwright-nightly-${{ github.run_id }}` - to `playwright-report-nightly-${{ github.run_id }}` for consistency with the other workflows. ### Why the change was made The workflows attempted to upload artifacts from a directory that no longer exists, causing GitHub Actions to report: > No files were found with the provided path: `frontend/editor/playwright-report/` Updating the upload path ensures Playwright reports are successfully collected and available for debugging failed E2E runs. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .github/workflows/build-enterprise.yml | 2 +- .github/workflows/build.yml | 3 +++ .github/workflows/coverage-aggregate.yml | 25 +++++++++++++++++------- .github/workflows/e2e-live.yml | 2 +- .github/workflows/e2e-stubbed.yml | 2 +- .github/workflows/nightly.yml | 4 ++-- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-enterprise.yml b/.github/workflows/build-enterprise.yml index d29002ac37..2db275284b 100644 --- a/.github/workflows/build-enterprise.yml +++ b/.github/workflows/build-enterprise.yml @@ -285,5 +285,5 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-report-enterprise-${{ github.run_id }} - path: frontend/editor/playwright-report/ + path: frontend/playwright-report/ retention-days: 7 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7018a38120..0e0a702cc2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -202,6 +202,9 @@ jobs: contents: read uses: ./.github/workflows/coverage-aggregate.yml secrets: inherit + with: + frontend-validation-result: ${{ needs.frontend-validation.result }} + playwright-e2e-live-result: ${{ needs.playwright-e2e-live.result }} # Single status check that branch protection should mark as required. # Succeeds when every upstream job is either `success` or `skipped` (path- diff --git a/.github/workflows/coverage-aggregate.yml b/.github/workflows/coverage-aggregate.yml index bbfaf09363..ce792bcadc 100644 --- a/.github/workflows/coverage-aggregate.yml +++ b/.github/workflows/coverage-aggregate.yml @@ -13,6 +13,17 @@ name: Aggregate backend coverage # producers themselves on: workflow_call: + inputs: + frontend-validation-result: + description: Result of the frontend-validation producer job + required: false + type: string + default: skipped + playwright-e2e-live-result: + description: Result of the playwright-e2e-live producer job + required: false + type: string + default: skipped permissions: contents: read @@ -196,9 +207,9 @@ jobs: # -------------------------------------------------------------- - name: Download vitest coverage artifact # frontend-validation uploads as `frontend-coverage`. Tolerate - # absence so a backend-only PR still produces the matrix with - # just backend rows populated. - if: always() + # absence on backend-only runs by skipping the download entirely + # when the producer job was not part of this workflow run. + if: inputs.frontend-validation-result == 'success' uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v6.0.0 with: name: frontend-coverage @@ -206,12 +217,12 @@ jobs: continue-on-error: true - name: Download Playwright frontend coverage artifact - # e2e-live uploads as `playwright-frontend-coverage-`. - # Same tolerance as vitest - matrix script handles missing inputs. - if: always() + # e2e-live uploads the artifact with a stable name. Skip the + # download entirely when the producer job did not run. + if: inputs.playwright-e2e-live-result == 'success' uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v6.0.0 with: - name: playwright-frontend-coverage-${{ github.run_id }} + name: playwright-frontend-coverage path: matrix-inputs/playwright/ continue-on-error: true diff --git a/.github/workflows/e2e-live.yml b/.github/workflows/e2e-live.yml index eb6d8d7be5..44d5443a73 100644 --- a/.github/workflows/e2e-live.yml +++ b/.github/workflows/e2e-live.yml @@ -169,7 +169,7 @@ jobs: if: always() && steps.pw-frontend-coverage.outputs.summary == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: playwright-frontend-coverage-${{ github.run_id }} + name: playwright-frontend-coverage path: | .test-state/playwright/coverage-pw-summary/ .test-state/playwright/coverage-pw/ diff --git a/.github/workflows/e2e-stubbed.yml b/.github/workflows/e2e-stubbed.yml index dd6bcc8ea7..33bb24ee4c 100644 --- a/.github/workflows/e2e-stubbed.yml +++ b/.github/workflows/e2e-stubbed.yml @@ -50,5 +50,5 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-report-stubbed-${{ github.run_id }} - path: frontend/editor/playwright-report/ + path: frontend/playwright-report/ retention-days: 7 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 11a8ea2be5..1801876bcd 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -53,8 +53,8 @@ jobs: if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: playwright-nightly-${{ github.run_id }} - path: frontend/editor/playwright-report/ + name: playwright-report-nightly-${{ github.run_id }} + path: frontend/playwright-report/ retention-days: 14 # Builds all desktop platforms on a schedule so the Rust dependency cache is From e630d6697b39235a785b2cc224f8f9e409cfa607 Mon Sep 17 00:00:00 2001 From: James Brunton Date: Mon, 6 Jul 2026 10:22:41 +0100 Subject: [PATCH 4/5] Fix tooltip positioning on Add Page Numbers (#6885) # Description of Changes ## Before image ## After image --- .../addPageNumbers/AddPageNumbersAppearanceSettings.tsx | 5 +++++ .../tools/addPageNumbers/AddPageNumbersPositionSettings.tsx | 2 ++ 2 files changed, 7 insertions(+) diff --git a/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx b/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx index 627f7c1b9e..11ca767bca 100644 --- a/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx +++ b/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx @@ -27,6 +27,7 @@ const AddPageNumbersAppearanceSettings = ({ return ( 001). Set 0 to disable.", @@ -90,6 +93,7 @@ const AddPageNumbersAppearanceSettings = ({ Date: Mon, 6 Jul 2026 10:25:46 +0100 Subject: [PATCH 5/5] Fix Playwright tests in Firefox and Safari (#6868) # Description of Changes Playwright tests currently fail in Firefox and Safari because of inconsistent behaviour across the browsers. This is causing the nightlies to fail every night. This PR fixes the test behaviour to work consistently across browsers (most of the issues were to do with the tests opening the file picker, which was being automatically suppressed in Chromium, but not the other browsers). --- .../src/core/tests/helpers/stub-test-base.ts | 2 + .../src/core/tests/helpers/test-base.ts | 2 + .../src/core/tests/helpers/ui-helpers.ts | 46 +++++++++++++++++-- .../live/encrypted-unlock-then-tool.spec.ts | 3 ++ .../stubbed/certificate-validation.spec.ts | 5 ++ .../src/core/tests/stubbed/convert.spec.ts | 14 ++++-- .../stubbed/encrypted-pdf-unlock.spec.ts | 12 ++++- .../stubbed/files-page-screenshots.spec.ts | 13 +++++- .../tests/stubbed/pdf-text-search.spec.ts | 5 +- .../stubbed/viewer-text-selection.spec.ts | 16 +++++++ 10 files changed, 105 insertions(+), 13 deletions(-) diff --git a/frontend/editor/src/core/tests/helpers/stub-test-base.ts b/frontend/editor/src/core/tests/helpers/stub-test-base.ts index 5752108d42..e6ae98c6f7 100644 --- a/frontend/editor/src/core/tests/helpers/stub-test-base.ts +++ b/frontend/editor/src/core/tests/helpers/stub-test-base.ts @@ -6,6 +6,7 @@ import { skipOnboarding, type MockAppApiOptions, } from "@app/tests/helpers/api-stubs"; +import { suppressNativeFilePicker } from "@app/tests/helpers/ui-helpers"; /** * Custom Playwright fixture for backend-free specs. @@ -57,6 +58,7 @@ export const test = base.extend({ seedJwt: [false, { option: true }], page: async ({ page, stubOptions, autoGoto, seedJwt }, use) => { + suppressNativeFilePicker(page); await seedCookieConsent(page); if (seedJwt) { // Logged-in users hit the orchestrator path that surfaces the diff --git a/frontend/editor/src/core/tests/helpers/test-base.ts b/frontend/editor/src/core/tests/helpers/test-base.ts index 1659bc3abd..90de9432f9 100644 --- a/frontend/editor/src/core/tests/helpers/test-base.ts +++ b/frontend/editor/src/core/tests/helpers/test-base.ts @@ -1,6 +1,7 @@ import { test as base, expect } from "@playwright/test"; import * as fs from "node:fs/promises"; import * as path from "node:path"; +import { suppressNativeFilePicker } from "@app/tests/helpers/ui-helpers"; /** * Custom test fixture that: @@ -40,6 +41,7 @@ const COVERAGE_DIR = path.resolve( export const test = base.extend({ page: async ({ page }, use, testInfo) => { + suppressNativeFilePicker(page); await page.context().addCookies([ { name: "cc_cookie", diff --git a/frontend/editor/src/core/tests/helpers/ui-helpers.ts b/frontend/editor/src/core/tests/helpers/ui-helpers.ts index 7c08f93f60..353037c106 100644 --- a/frontend/editor/src/core/tests/helpers/ui-helpers.ts +++ b/frontend/editor/src/core/tests/helpers/ui-helpers.ts @@ -11,6 +11,37 @@ import { expect, type Page, type Locator } from "@playwright/test"; const MANTINE_MODAL_OVERLAY = ".mantine-Modal-overlay"; +/** + * Suppress the native OS file picker for the whole page, on every browser. + * + * Several upload entry points (the FileSidebar "Open from computer" button, + * the Mantine ``, AddFileCard, etc.) open a file dialog by clicking + * a hidden ``. On firefox/webkit Playwright only intercepts + * that dialog while the page has a `filechooser` listener - it toggles + * `Page.setInterceptFileChooserDialog` off the event subscription. With no + * listener the real OS picker leaks onto the host and hangs the nightly run. + * + * Registering a (no-op) `filechooser` listener flips that interception on for + * every browser, so the dialog is suppressed at the browser level however it + * was triggered - a programmatic `.click()`, a `