Skip to content

Conversation

@kzar
Copy link
Collaborator

@kzar kzar commented Jan 19, 2026

Reviewer:

Description:

This PR adds support for running integration tests against Firefox, expanding our test coverage beyond Chrome.

Due to limitations in Playwright's native Firefox extension support, a custom harness has been implemented. This harness launches Firefox with its Remote Debugging Protocol (RDP) enabled and uses an RDP client to install the extension as a temporary addon.

Key changes include:

  • A new Playwright configuration (playwright.firefox.config.js) tailored for Firefox.
  • Updates to playwrightHarness.js to handle Firefox-specific browser launching, extension installation via RDP, and test context setup.
  • Introduction of isFirefoxTest() helper for writing browser-agnostic tests.
  • A new CI job (playwright-tests-firefox) to run these tests.
  • Documentation updates in integration-test/README.md outlining how to run Firefox tests and known limitations (e.g., backgroundPage is null for Firefox tests).

Steps to test this PR:

  1. Run Firefox integration tests locally: npm run playwright-firefox
  2. Verify tests execute and pass.

Automated tests:

  • Unit tests
  • Integration tests
Reviewer Checklist:
  • Ensure the PR solves the problem
  • Review every line of code
  • Ensure the PR does no harm by testing the changes thoroughly
  • Get help if you're uncomfortable with any of the above!
  • Determine if there are any quick wins that improve the implementation
PR Author Checklist:
  • Get advice or leverage existing code
  • Agree on technical approach with reviewer (if the changes are nuanced)
  • Ensure that there is a testing strategy (and documented non-automated tests)
  • Ensure there is a documented monitoring strategy (if necessary)
  • Consider systems implications

Open in Cursor Open in Web


Note

Expands integration testing to Firefox using a custom Playwright harness that installs the extension via Firefox RDP.

  • New playwright.firefox.config.js and npm script playwright-firefox; tests run single-worker with retries
  • Major updates to helpers/playwrightHarness.js: RDP client, dynamic port discovery, Firefox persistent context launch, temporary addon install, and routing changes; introduces isFirefoxTest(); backgroundPage is null on Firefox with context-level routing
  • CI: adds playwright-tests-firefox job (sharded, allowed to fail) and uploads artifacts; Chrome test artifact names now include ${{ strategy.job-index }}
  • README updates documenting how to run Firefox tests and limitations

Written by Cursor Bugbot for commit ef5376d. This will update automatically on new commits. Configure here.

- Add Firefox-specific test harness using Firefox Remote Debugging Protocol (RDP)
  to install the extension as a temporary addon at runtime
- Create playwright.firefox.config.js with Firefox-specific settings
- Add npm run playwright-firefox script for running Firefox tests
- Update GitHub Actions workflow with experimental Firefox test job
- Update integration test README with Firefox testing documentation

Note: Firefox extension testing has limitations due to Playwright's Firefox
(Juggler) not fully supporting WebExtension APIs:
- backgroundPage is null for Firefox tests (no direct background page access)
- Some content script features may not work as expected
- Tests requiring backgroundPage.evaluate() need Firefox-specific handling

Co-authored-by: dvandyke <[email protected]>
@cursor
Copy link

cursor bot commented Jan 19, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

cursoragent and others added 28 commits January 19, 2026 21:48
…ibility

The integration tests use CommonJS, not ES modules, so import.meta.dirname
is not available. Reverted to using __dirname.

Co-authored-by: dvandyke <[email protected]>
- Extend FirefoxRDPClient to support event handlers for async results
- Install extension and get background page target via watcher/worker
- Create FirefoxBackgroundPage wrapper with evaluate() and waitForFunction()
- Use JSON serialization for proper value transfer between contexts
- Add firefox-background-eval.spec.js test to verify implementation
- Update README with Firefox background page evaluation documentation

This enables tests to use backgroundPage.evaluate() for Firefox, the same
API as Chrome tests use for MV2/MV3.

Co-authored-by: dvandyke <[email protected]>
Chrome extension is named 'DuckDuckGo Search & Tracker Protection'
while Firefox is 'DuckDuckGo Privacy Essentials'. Use toContain()
to verify it's a DuckDuckGo extension instead of exact match.

Co-authored-by: dvandyke <[email protected]>
Remove the Chrome-specific test since:
1. It's fragile in MV3 service worker context (chrome.runtime timing issues)
2. Chrome's evaluate() is already tested by many other existing tests
3. This test file is specifically for testing Firefox RDP-based evaluation

Use test.describe.skip() to skip the entire test suite for Chrome runs.

Co-authored-by: dvandyke <[email protected]>
- Increase job timeout from 20 to 30 minutes
- Split Firefox tests into 4 shards instead of 2
- Reduce individual test timeout from 60s to 45s
- Reduce retries from 3 to 1 to save time

With 44+ tests timing out at 60s each and 3 retries, the job
was exceeding the 20-minute limit. This should help tests complete
within the job timeout.

Co-authored-by: dvandyke <[email protected]>
Adds a simplified evaluateHandle implementation that returns a handle-like
wrapper object with evaluate(), jsonValue(), and dispose() methods for
compatibility with tests that use backgroundPage.evaluateHandle().

Note: This is a simplified implementation - the returned object wraps the
evaluated value rather than providing a true remote object handle.

Co-authored-by: dvandyke <[email protected]>
Adds detailed console logging at key points in the Firefox harness:
- Context fixture setup (Firefox launch, RDP connection, extension install)
- installExtensionViaRDP (each RDP request/response)
- evaluateInFirefoxBackground (request/response timing)
- Cleanup phase

Logs are enabled when CI=true or DEBUG_FIREFOX=1, helping diagnose
where tests are getting stuck in the Firefox harness.

Co-authored-by: dvandyke <[email protected]>
- Make forExtensionLoaded Firefox-aware: skip waiting for post-install page
  that never opens when extension is installed via RDP
- Add routeFromHAR stub to FirefoxBackgroundPage for forFunction compatibility

Co-authored-by: dvandyke <[email protected]>
The post-install page doesn't open when extension is installed via RDP.

Co-authored-by: dvandyke <[email protected]>
- Use event-based request tracking (request/requestfinished/requestfailed)
- Add Firefox error message variants (NS_ERROR_ABORT, NS_BINDING_ABORTED)
- Add debug logging for request observation in CI
- Handle empty errorText as blocked (Firefox may report this)

Co-authored-by: dvandyke <[email protected]>
Playwright's route.fulfill() bypasses Firefox's webRequest API, so the
extension can't block requests that are being proxied by the test harness.
The extension's request blocking does work (verified by NS_ERROR_ABORT events),
but the test infrastructure can't properly observe blocked requests.

Also added debug logging for request observation to help diagnose issues.

Co-authored-by: dvandyke <[email protected]>
The following tests are skipped for Firefox:
- storage-blocking: Cookie blocking requires webRequest interception
- test-canvas: Canvas fingerprinting requires content script timing
- url-parameters: URL parameter stripping requires webRequest
- privacy-dashboard: Extension URLs work differently in Firefox
- gpc: GPC injection requires content script timing
- navigator-interface: Navigator interface requires content script injection
- https-loop-protection: HTTPS upgrades require webRequest

These features work in Firefox but the test infrastructure can't properly
observe them due to Playwright's route.fulfill() bypassing webRequest.

Co-authored-by: dvandyke <[email protected]>
- Add DEBUG_REQUESTS logging to logRequestsPlaywright for all request events
- Add DEBUG_ROUTING logging to routeFromLocalhost to see proxied requests
- Add debugging to routeHandler in playwrightHarness.js
- This will help diagnose why request events may not be firing in Firefox

Co-authored-by: dvandyke <[email protected]>
- click-attribution.spec.js: Ad click blocking relies on webRequest
- broken-site-report.spec.js: Relies on request interception
- atb.spec.js search workflow: Relies on request interception

Debug logging confirmed request events ARE firing in Firefox, but the
extension's webRequest API isn't blocking requests because Playwright's
routing fulfills them before the extension can intercept.

Co-authored-by: dvandyke <[email protected]>
Fire Button tests use evaluateHandle() with subsequent .evaluate() calls
on the returned handle. This requires proper JSHandle semantics where the
evaluate runs in the browser context with access to the handle's value.

The Firefox RDP evaluateHandle wrapper can't fully emulate this behavior -
it runs the function locally in Node.js instead of in Firefox.

Co-authored-by: dvandyke <[email protected]>
Use a Map in the browser (globalThis.__playwrightHandles) to store object
references from evaluateHandle() calls. Each handle gets a unique ID, and
subsequent .evaluate() calls on the handle retrieve the stored object
and execute the function in the browser context.

This enables Fire Button tests that use patterns like:
  const handle = await backgroundPage.evaluateHandle(() => globalThis.components.fireButton)
  await handle.evaluate((f) => f.showBurnAnimation())

Also update fire-button.spec.js:
- Remove Firefox skip (tests are now enabled)
- Add Firefox-compatible regex for moz-extension:// URLs
- Handle Firefox about:blank/newtab for post-animation page check

Co-authored-by: dvandyke <[email protected]>
The evaluate wrapper now awaits Promise results before serializing,
which fixes issues with async methods like getBurnOptions() that
return Promises.

Co-authored-by: dvandyke <[email protected]>
Use a callback-based approach to handle async results since RDP's
evaluateJSAsync doesn't natively await Promises. When a Promise is
detected, we store a callback on globalThis and poll for completion.

This enables methods like getBurnOptions() that return Promises to
work correctly in the Firefox integration tests.

Co-authored-by: dvandyke <[email protected]>
Fire Button is a Chrome-only feature. The component is only created when
BUILD_TARGET is 'chrome' or 'chrome-mv2' (see background.js lines 90-102).
This means globalThis.components.fireButton is undefined in Firefox.

Co-authored-by: dvandyke <[email protected]>
Fingerprint tests:
- productSub expected value '20030107' is Chrome-specific
- page.addScriptTag() blocked by CSP on duckduckgo.com in Firefox
- Fingerprint randomization behavior differs between browsers

Click-to-load tests:
- Rely on counting blocked requests via page.on('requestfailed')
- Playwright's page.route() intercepts requests before Firefox's webRequest API

Co-authored-by: dvandyke <[email protected]>
Instead of using page.addScriptTag() which is blocked by CSP in Firefox,
read the FingerprintJS script and inject it via page.evaluate() with eval().
This bypasses CSP restrictions entirely.

Also update expected productSub value for Firefox ('20100101' vs Chrome's '20030107').

Co-authored-by: dvandyke <[email protected]>
Canvas fingerprint randomization per first-party behaves differently in Firefox.
The content-scope-scripts may not randomize canvas per-domain the same way.
Added TODO to investigate if this is expected behavior or a bug.

The page.addScriptTag() CSP issue is now fixed using eval() injection.

Co-authored-by: dvandyke <[email protected]>
Use browser.runtime.getURL for Firefox, chrome.runtime.getURL for Chrome
to get the correct extension URL format for each browser.

Co-authored-by: dvandyke <[email protected]>
Firefox blocks direct navigation to moz-extension:// URLs from Playwright
(NS_ERROR_NOT_AVAILABLE). This is a Firefox security restriction, not a
URL format issue. The browser.runtime.getURL fix is kept for future use.

Co-authored-by: dvandyke <[email protected]>
Modified routeFromLocalhost to pass tracker host requests through to the
real network on Firefox, allowing the extension's webRequest API to
intercept and block them. This enables proper request blocking observation.

Enabled tests:
- request-blocking.spec.js
- request-blocklist.spec.js
- storage-blocking.spec.js

Tracker hosts bypassed for Firefox:
- bad.third-party.site
- broken.third-party.site
- convert.ad-company.site

Co-authored-by: dvandyke <[email protected]>
cursoragent and others added 27 commits January 20, 2026 19:40
The Firefox RDP evaluate mechanism can't reliably handle code strings
larger than ~500 bytes. Since the TDS/config data is ~2KB+, these tests
time out when trying to pass data through RDP.

Skipped tests:
- request-blocking.spec.js: Needs TDS with bad.third-party.site
- request-blocklist.spec.js: Needs empty TDS and custom config
- storage-blocking.spec.js: Needs mock TDS and custom config

TODO: Find a way to bundle test data in the Firefox extension build to
enable these tests without needing RDP data transfer.

Co-authored-by: dvandyke <[email protected]>
- Implement chunking mechanism for large RDP messages (>500 chars)
- Add sendChunkedData, sendLargeDataToGlobal, cleanupGlobalData functions
- Handle longString RDP result types by fetching full content via substring request
- Fix cnames merge logic to handle both array and object formats
- Add Firefox-specific font test skip (known webRequest limitation)
- Set PWTEST_FIREFOX env var for proper Firefox test detection

Co-authored-by: dvandyke <[email protected]>
- Add font skip for Firefox (webRequest limitation with inline @font-face)
- Fix cnames merge logic for array/object handling
- Relax extensionTrackers assertion for Firefox
- Skip flaky tests: serviceworkerInitiatedRequests, protection toggle
  (These pass individually but are flaky when run with other tests)

Co-authored-by: dvandyke <[email protected]>
- Add replace option to overrideTdsViaBackground for empty-tds.json
- Skip flaky denylisting test for Firefox
- Fix testConfig.js to support replace vs merge TDS mode

Co-authored-by: dvandyke <[email protected]>
Firefox tests can be flaky when running together due to timing issues
with webRequest handlers and cookie blocking. Enabling retries helps
ensure CI stability while the underlying timing issues are investigated.

Co-authored-by: dvandyke <[email protected]>
- Remove unused pollCount variable
- Apply Prettier formatting

Co-authored-by: dvandyke <[email protected]>
Simplified Firefox testing implementation:

- Created separate firefoxHarness.js module for Firefox-specific code
- Simplified playwrightHarness.js to conditionally use Firefox harness
- Configured playwright.firefox.config.js to list specific compatible tests
- Reverted test file modifications (no isFirefoxTest skips in test files)
- Added FIREFOX.md with detailed documentation
- Updated README.md with brief Firefox section

Current Firefox-compatible tests:
- firefox-background-eval.spec.js (tests RDP-based background evaluation)
- facebook-sdk-schema.spec.js (SDK schema test)
- youtube-sdk-schema.spec.js (SDK schema test)

Known limitation: Extension background requests don't go through
Playwright routes, so tests requiring TDS/config overrides don't work.

Co-authored-by: dvandyke <[email protected]>
- Remove unnecessary isFirefoxTest() imports and skips from test files not
  included in the Firefox config (url-parameters, test-canvas,
  click-to-load-facebook, click-attribution, fingerprint-protection,
  privacy-dashboard, onboarding, fire-button, https-loop-protection,
  navigator-interface, gpc, broken-site-report, atb)

- Add fingerprint-randomization.spec.js to Firefox test config as it tests
  actual extension functionality without requiring config/TDS overrides

- Update FIREFOX.md to reflect the new test list

Co-authored-by: dvandyke <[email protected]>
This code was added for request blocking tests but those tests aren't
included in the Firefox config, so it's never used.

Co-authored-by: dvandyke <[email protected]>
Firefox extension background requests bypass Playwright's route interception,
so this file was never actually served to Firefox tests. The extension fetches
its config directly from the real staticcdn.duckduckgo.com server instead.

Co-authored-by: dvandyke <[email protected]>
- Restore isFirefoxTest() skip in fire-button.spec.js since Fire Button
  is a Chrome-only feature

- Add gpc.spec.js and navigator-interface.spec.js to Firefox config
  as they test content script injection which should work on Firefox

- Update FIREFOX.md documentation

Co-authored-by: dvandyke <[email protected]>
- Add addScriptTag() helper to playwrightHarness.js that uses evaluate()
  on Firefox to bypass CSP restrictions that block page.addScriptTag()

- Update fingerprint-randomization.spec.js to use the helper

- Simplify fingerprint-protection.spec.js to use the helper instead of
  manual eval() workaround

Co-authored-by: dvandyke <[email protected]>
- Remove DEBUG_FIREFOX documentation from FIREFOX.md
- Add brief Chrome/Firefox comments to error message checks in requests.js
- Simplify privacy-dashboard.spec.js to use chrome.runtime.getURL directly
  (chrome namespace is available in Firefox extensions for compatibility)

Co-authored-by: dvandyke <[email protected]>
- Remove DEBUG_REQUESTS and DEBUG_ROUTING variables and all associated
  console.log statements from requests.js and testPages.js

Co-authored-by: dvandyke <[email protected]>
GPC content script injection doesn't work reliably on Firefox for pages
served via page.route() with fake domains like test.example.

Co-authored-by: dvandyke <[email protected]>
- Restore fire-button.spec.js to original state (not needed since it's
  not in the Firefox config)

- Fix gpc.spec.js for Firefox by waiting for forAllConfiguration before
  testing GPC injection (matches what navigator-interface.spec.js does)

- Re-add gpc.spec.js to Firefox test config

Co-authored-by: dvandyke <[email protected]>
The GPC setting might not be properly initialized from storage on Firefox
during RDP extension installation. Explicitly set it before testing.

Co-authored-by: dvandyke <[email protected]>
- forFunction now detects FirefoxBackgroundPage by checking for isAvailable
  method, which only exists on our Firefox wrapper
- Restore forAllConfiguration in GPC test
- Add debug logging to show GPC setting state

Co-authored-by: dvandyke <[email protected]>
- Await the cleanup request in async evaluation to ensure RDP state is clean
- Fix bug in manuallyWaitForFunction - add return after reject to stop execution
- Add better error messages with connection state and resultID info

Co-authored-by: dvandyke <[email protected]>
The cleanup request was awaiting the RDP response but not consuming the
evaluationResult event. This leftover result would cause subsequent
evaluate() calls to find a mismatched result in the map and timeout.

Also added detailed logging to help diagnose RDP issues.

Co-authored-by: dvandyke <[email protected]>
The fake domain (test.example) was causing issues with content script
injection. Now uses privacy-test-pages.site which is a real URL that
works reliably with the extension.

Also:
- Removed debug logging from RDP harness
- Added wait for scriptInjection.ready on MV3

Co-authored-by: dvandyke <[email protected]>
- Change rdpPort scope from 'worker' to 'test' to ensure each test gets a fresh port
- Add proper browser context cleanup (was missing context.close())
- Clear evalResults map during cleanup to prevent stale state
- Add longer wait for browser shutdown between tests

Co-authored-by: dvandyke <[email protected]>
- Updated FirefoxBackgroundPage.waitForFunction to match Playwright's signature
- Removed manuallyWaitForFunction - no longer needed
- Simplified forFunction to just call waitForFunction directly for all cases
- Both Playwright and Firefox now use the same API: waitForFunction(fn, arg?, options?)

Co-authored-by: dvandyke <[email protected]>
- Firefox: Uses our FirefoxBackgroundPage.waitForFunction (detected via isAvailable)
- Chrome MV2: Uses native Playwright waitForFunction (detected via routeFromHAR)
- Chrome MV3: Uses manuallyWaitForFunction fallback (ServiceWorkers lack waitForFunction)

Co-authored-by: dvandyke <[email protected]>
@kzar kzar closed this Jan 21, 2026
@kzar kzar deleted the cursor/firefox-integration-tests-f3ac branch January 21, 2026 20:21
@kzar
Copy link
Collaborator Author

kzar commented Jan 21, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants