Skip to content

Commit 95959b0

Browse files
committed
WIP - Add experimental Firefox integration test support
Warning: These changes were initially generated with Cursor, and I am now in the process of tidying them up.
1 parent af71d7a commit 95959b0

File tree

10 files changed

+843
-60
lines changed

10 files changed

+843
-60
lines changed

.github/workflows/checks.yml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,34 @@ jobs:
6767
- uses: actions/upload-artifact@v4
6868
if: always()
6969
with:
70-
name: ${{ matrix.test }}-report
70+
name: ${{ matrix.test }}-report-${{ strategy.job-index }}
7171
path: playwright-report/
7272
retention-days: 1
73-
73+
74+
playwright-tests-firefox:
75+
timeout-minutes: 30
76+
runs-on: ubuntu-latest
77+
# Firefox tests are experimental - don't block CI on failures
78+
continue-on-error: true
79+
strategy:
80+
fail-fast: false
81+
matrix:
82+
shard: [1/4, 2/4, 3/4, 4/4]
83+
steps:
84+
- uses: actions/checkout@v4
85+
- uses: actions/setup-node@v4
86+
with:
87+
node-version: 22
88+
cache: 'npm'
89+
- run: npm run install-ci
90+
- name: Install Playwright Browsers
91+
run: npx playwright install --with-deps firefox
92+
- name: Run Firefox tests
93+
run: xvfb-run --auto-servernum -- npm run playwright-firefox -- --shard ${{ matrix.shard }}
94+
- uses: actions/upload-artifact@v4
95+
if: always()
96+
with:
97+
name: playwright-firefox-report-${{ strategy.job-index }}
98+
path: playwright-report/
99+
retention-days: 1
100+

integration-test/README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@ We use [Playwright](https://playwright.dev/) as a test runner. Tests are defined
1212

1313
Playwright tests can be run with the following npm commands:
1414
- `npm run playwright` to test the Chrome MV3 extension
15-
- `npm run playwright-mv2` to test the Chrome MV2 extension (closest thing to testing Firefox MV2 extension we have until Playwright adds support for testing Firefox extensions)
15+
- `npm run playwright-mv2` to test the Chrome MV2 extension
1616

1717
If you want to re-run tests without rebuilding the extension, you can subsequently run:
1818
- `npx playwright test` to run all tests
19-
- `npx playright test integration-test/<file>.spec.js` to just run tests in a single file.
19+
- `npx playwright test integration-test/<file>.spec.js` to just run tests in a single file.
20+
21+
### Firefox Testing (Experimental)
22+
23+
Firefox integration tests are available for a subset of tests. The Firefox harness uses the
24+
Remote Debugging Protocol (RDP) to install the extension and evaluate code in its background page.
25+
So far many tests can't run because request interception is not working correctly.
26+
- `npm run playwright-firefox`
2027

2128
## Writing tests
2229

@@ -40,10 +47,10 @@ test('my test', async ({ manifestVersion, page, backgroundPage, backgroundNetwor
4047
The arguments to the test function are:
4148
- `manifestVersion`: `2` or `3`. Allows you to check which version of the extension is being tested.
4249
- `page`: A [Page](https://playwright.dev/docs/api/class-page) instance for loading web pages.
43-
- `backgroundPage`: The extension's background page, which is a `Page` for MV2, or `Worker` for MV3. Use `backgroundPage.evaluate` to run code in the extension's background context.
50+
- `backgroundPage`: The extension's background page. For Chrome MV2, this is a `Page`. For Chrome MV3, this is a `Worker`. For Firefox, this is a `FirefoxBackgroundPage` wrapper that supports `evaluate()` via RDP. Use `backgroundPage.evaluate()` to run code in the extension's background context.
4451
- `backgroundNetworkContext`: A context for listening to and intercepting requests from the extension's background context with Playwright's [Network](https://playwright.dev/docs/network) APIs.
4552
- `context`: The [BrowserContext](https://playwright.dev/docs/api/class-browsercontext) for the test run.
4653

4754
Static files for tests are in the `data` directory:
4855
- `har` - [HAR files](./data/har/README.md) for offline tests.
49-
- `staticcdn` - Mocked CDN resources to used when the extension loads.
56+
- `staticcdn` - Mocked CDN resources to used when the extension loads.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { test, expect, isFirefox } from './helpers/playwrightHarness.js';
2+
import backgroundWait from './helpers/backgroundWait';
3+
4+
test.describe('test harness background page/ServiceWorker evaluation', () => {
5+
test('can evaluate code in extension background', async ({ context, backgroundPage }) => {
6+
expect(backgroundPage).not.toBeNull();
7+
await backgroundWait.forExtensionLoaded(context);
8+
9+
if (isFirefox()) {
10+
expect(backgroundPage.isAvailable()).toBe(true);
11+
} else {
12+
expect(backgroundPage.isAvailable).toBe(undefined);
13+
}
14+
15+
// Test simple evaluation
16+
const manifestName = await backgroundPage.evaluate(() => {
17+
return chrome.runtime.getManifest().name;
18+
});
19+
expect(manifestName.startsWith('DuckDuckGo')).toBe(true);
20+
21+
// Test evaluation with arguments
22+
const result = await backgroundPage.evaluate(
23+
({ a, b }) => {
24+
return a + b;
25+
},
26+
{ a: 2, b: 3 },
27+
);
28+
expect(result).toBe(5);
29+
30+
// Test string argument
31+
const greeting = await backgroundPage.evaluate((name) => {
32+
return 'Hello, ' + name;
33+
}, 'Firefox');
34+
expect(greeting).toBe('Hello, Firefox');
35+
36+
// Test returning an object (important for compatibility with existing tests)
37+
const obj = await backgroundPage.evaluate(() => {
38+
return { foo: 'bar', num: 42, bool: true };
39+
});
40+
expect(obj.foo).toBe('bar');
41+
expect(obj.num).toBe(42);
42+
expect(obj.bool).toBe(true);
43+
44+
// Test returning an array
45+
const arr = await backgroundPage.evaluate(() => {
46+
return [1, 2, 3, 'four'];
47+
});
48+
expect(arr).toEqual([1, 2, 3, 'four']);
49+
});
50+
});

integration-test/fingerprint-protection.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect, getHARPath } from './helpers/playwrightHarness';
1+
import { test, expect, getHARPath, addScriptTag } from './helpers/playwrightHarness';
22
import backgroundWait from './helpers/backgroundWait';
33
import { overridePrivacyConfig } from './helpers/testConfig';
44

@@ -61,7 +61,7 @@ test.describe('First Party Fingerprint Randomization', () => {
6161
async function runTest(testCase, page) {
6262
await page.routeFromHAR(testCase.har);
6363
await page.goto(`https://${testCase.url}`);
64-
await page.addScriptTag({ path: 'node_modules/@fingerprintjs/fingerprintjs/dist/fp.js' });
64+
await addScriptTag(page, { path: 'node_modules/@fingerprintjs/fingerprintjs/dist/fp.js' });
6565

6666
const fingerprint = await page.evaluate(() => {
6767
/* global FingerprintJS */

integration-test/fingerprint-randomization.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import fs from 'fs';
22
import path from 'path';
33

4-
import { test, expect } from './helpers/playwrightHarness';
4+
import { test, expect, addScriptTag } from './helpers/playwrightHarness';
55
import { forExtensionLoaded } from './helpers/backgroundWait';
66

77
const fakeOrigin = 'http://test.example';
88

99
async function getFingerprintOfContext(ctx) {
10-
await ctx.addScriptTag({ path: 'node_modules/@fingerprintjs/fingerprintjs/dist/fp.js' });
10+
await addScriptTag(ctx, { path: 'node_modules/@fingerprintjs/fingerprintjs/dist/fp.js' });
1111
return ctx.evaluate(() => {
1212
/* global FingerprintJS */
1313
return (async () => {

integration-test/helpers/backgroundWait.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ function manuallyWaitForFunction(bgPage, func, { polling, timeout }, ...args) {
99
try {
1010
result = await bgPage.evaluate(func, ...args);
1111
} catch (e) {
12-
reject(e);
12+
return reject(e);
1313
}
1414
if (result) {
15-
resolve(result);
15+
return resolve(result);
1616
} else {
1717
if (Date.now() - startTime > timeout) {
18-
reject(new errors.TimeoutError('Manually waiting for function timed out: ' + func.toString()));
18+
return reject(new errors.TimeoutError('Manually waiting for function timed out: ' + func.toString()));
1919
} else {
2020
setTimeout(waitForFunction, polling);
2121
}

0 commit comments

Comments
 (0)