Skip to content

Commit dbe56dc

Browse files
committed
test(functional): Add real test for 'web-ext run -t chromium'
The existing unit tests for web-ext run -t chromium are at tests/unit/test-extension-runners/test.chromium.js but are testing implementation details without verifying that Chrome is actually going to launch the extension. Before refactoring the implementation as needed to support Chrome 137+ and later, this introduces a unit test that verifies the behavior against the real Chrome (skipped by default), and a fake Chrome binary that behaves like the real Chrome binary where relevant. This will help us with catching regressions in the implementation and/or downstream dependencies (chrome-launcher). These Chrome behavior has been verified across Chrome 69, 70, 75, 77, 88, 100, 122, 125, 126, 134, 139. The auto-reload case of the "real Chrome" test in Chrome 134 requires manual intervention in the form of visiting chrome://extensions/, enabling 'Developer Mode' and then manually reloading the extension, for the following reasons: - The bundled "web-ext Reload Manager Extension" uses Manifest Version 2, which is disabled by default in recent Chrome. - When 'Developer Mode' is off (by default), an attempt to reload extensions that were loaded with --load-extension causes the extension to be disabled. These two issues will be resolved once we switch to the new CDP-based method of loading Chrome extensions.
1 parent 88dbb43 commit dbe56dc

File tree

8 files changed

+634
-0
lines changed

8 files changed

+634
-0
lines changed

src/cmd/run.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default async function run(
3434
noReload = false,
3535
preInstall = false,
3636
sourceDir,
37+
verbose = false,
3738
watchFile,
3839
watchIgnored,
3940
startUrl,
@@ -188,6 +189,7 @@ export default async function run(
188189
if (target && target.includes('chromium')) {
189190
const chromiumRunnerParams = {
190191
...commonRunnerParams,
192+
verbose,
191193
chromiumBinary,
192194
chromiumProfile,
193195
};

src/extension-runners/chromium.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export class ChromiumExtensionRunner {
211211
chromeFlags,
212212
startingUrl,
213213
userDataDir,
214+
logLevel: this.params.verbose ? 'verbose' : 'silent',
214215
// Ignore default flags to keep the extension enabled.
215216
ignoreDefaultFlags: true,
216217
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* globals chrome */
2+
3+
chrome.runtime.onInstalled.addListener(() => {
4+
// Although this URL is hard-coded, it is easy to set up a test server with a
5+
// random port, and let Chrome resolve this host to that local server with:
6+
// --host-resolver-rules='MAP localhost:1337 localhost:12345'
7+
// (where 12345 is the actual port of the local server)
8+
//
9+
// We are intentionally using localhost instead of another domain, to make
10+
// sure that the browser does not upgrade the http:-request to https.
11+
chrome.tabs.create({ url: 'http://localhost:1337/hello_from_extension' });
12+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "Test extension opens tab on install/reload",
3+
"description": "Opens localhost:1337 tab from runtime.onInstalled. Extension ID in Chrome is hgobbjbpnmemikbdbflmolpneekpflab",
4+
"version": "1",
5+
"manifest_version": 3,
6+
"background": {
7+
"service_worker": "background.js"
8+
},
9+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmr/8tpL+H1zEIEzrOhC8tkheWH7N7FOfiSnLq9D8Uja+b4W69rPIatslIPd4a53IgLT5qv1UZz03kETaKWDYhxoWbaKWiPtJcHoWKAROZ4Ydk2eC7jDcCKYkhXSgTpDzZBhHkyKZoh10O0AEYeJ+xfCmUJT3PT/CzxrSI4rjXFOBie5FMTgjusLwjiwVSBnO9haE7HqwekGVFvixG5Ao6rXRaZx+mm2+7PLTgMSxaXk1OqBtKmz4da67zZGrnHjiz4a0JHkyRuNkyamT5HFjD9neAWjgSwWD5yXKgJKkwGHePsq2WgSZlWIaKFafcbnoIr91hk5+mRCYVm2l/wgqvwIDAQAB"
10+
}

tests/functional/common.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createServer } from 'http';
12
import path from 'path';
23
import { spawn } from 'child_process';
34
import { promisify } from 'util';
@@ -31,6 +32,13 @@ export const fakeServerPath = path.join(
3132
'fake-amo-server.js',
3233
);
3334

35+
export const chromeExtPath = path.join(fixturesDir, 'chrome-extension-mv3');
36+
// NOTE: Depends on preload_on_windows.cjs to load this!
37+
export const fakeChromePath = path.join(
38+
functionalTestsDir,
39+
'fake-chrome-binary.js',
40+
);
41+
3442
// withTempAddonDir helper
3543

3644
const copyDirAsPromised = promisify(copyDir);
@@ -80,7 +88,21 @@ export function execWebExt(argv, spawnOptions) {
8088
...process.env,
8189
...spawnOptions.env,
8290
};
91+
} else {
92+
spawnOptions.env = { ...process.env };
93+
}
94+
95+
if (process.platform === 'win32') {
96+
// See preload_on_windows.cjs for an explanation.
97+
const preloadPath = path.join(functionalTestsDir, 'preload_on_windows.cjs');
98+
// NODE_OPTIONS allows values to be quoted, and anything within to be escaped
99+
// with a backslash: https://nodejs.org/api/cli.html#node_optionsoptions
100+
// https://github.com/nodejs/node/blob/411495ee9326096e88d12d3f3efae161cbd19efd/src/node_options.cc#L1717-L1741
101+
const escapedAbsolutePath = preloadPath.replace(/\\|"/g, '\\$&');
102+
spawnOptions.env.NODE_OPTIONS ||= '';
103+
spawnOptions.env.NODE_OPTIONS += ` --require "${escapedAbsolutePath}"`;
83104
}
105+
84106
const spawnedProcess = spawn(
85107
process.execPath,
86108
[webExt, ...argv],
@@ -105,3 +127,74 @@ export function execWebExt(argv, spawnOptions) {
105127

106128
return { argv, waitForExit, spawnedProcess };
107129
}
130+
131+
export function monitorOutput(spawnedProcess) {
132+
const callbacks = new Set();
133+
let outputData = '';
134+
function checkCallbacks() {
135+
for (const callback of callbacks) {
136+
const { outputTestFunc, resolve } = callback;
137+
if (outputTestFunc(outputData)) {
138+
callbacks.delete(callback);
139+
resolve();
140+
}
141+
}
142+
}
143+
spawnedProcess.stdout.on('data', (data) => {
144+
outputData += data;
145+
checkCallbacks();
146+
});
147+
148+
const waitUntilOutputMatches = (outputTestFunc) => {
149+
return new Promise((resolve) => {
150+
callbacks.add({ outputTestFunc, resolve });
151+
checkCallbacks();
152+
});
153+
};
154+
155+
return { waitUntilOutputMatches };
156+
}
157+
158+
// Test server to receive request from chrome-extension-mv3, once loaded.
159+
export async function startServerReceivingHelloFromExtension() {
160+
let requestCount = 0;
161+
let lastSeenUserAgent;
162+
let resolveWaitingForHelloFromExtension;
163+
const server = createServer((req, res) => {
164+
if (req.url !== '/hello_from_extension') {
165+
res.writeHead(404);
166+
res.end('test server only handles /hello_from_extension');
167+
return;
168+
}
169+
res.writeHead(200);
170+
res.end('test server received /hello_from_extension');
171+
lastSeenUserAgent = req.headers['user-agent'];
172+
++requestCount;
173+
resolveWaitingForHelloFromExtension?.();
174+
});
175+
await new Promise((resolve) => {
176+
server.listen(0, '127.0.0.1', () => resolve());
177+
});
178+
const testServerHost = `127.0.0.1:${server.address().port}`;
179+
return {
180+
get requestCount() {
181+
return requestCount;
182+
},
183+
get lastSeenUserAgent() {
184+
return lastSeenUserAgent;
185+
},
186+
getHostResolverRulesArgForChromeBinary() {
187+
// chrome-extension-mv3 sends requests to http://localhost:1337, but our
188+
// test server uses a free port to make sure that it does not conflict
189+
// with an existing local server. Pass --host-resolver-rules to Chrome so
190+
// that it sends requests targeting localhost:1337 to this test server.
191+
return `--host-resolver-rules=MAP localhost:1337 ${testServerHost}`;
192+
},
193+
waitForHelloFromExtension: () => {
194+
return new Promise((resolve) => {
195+
resolveWaitingForHelloFromExtension = resolve;
196+
});
197+
},
198+
close: () => new Promise((resolve) => server.close(() => resolve())),
199+
};
200+
}

0 commit comments

Comments
 (0)