diff --git a/src/cmd/run.js b/src/cmd/run.js index 47392ba7fb..7bab03927b 100644 --- a/src/cmd/run.js +++ b/src/cmd/run.js @@ -28,6 +28,7 @@ const log = createLogger(import.meta.url); export type CmdRunParams = {| artifactsDir: string, browserConsole: boolean, + devtools: boolean, pref?: FirefoxPreferences, firefox: string, firefoxProfile?: string, @@ -75,6 +76,7 @@ export default async function run( { artifactsDir, browserConsole = false, + devtools = false, pref, firefox, firefoxProfile, @@ -174,6 +176,7 @@ export default async function run( profilePath: firefoxProfile, customPrefs, browserConsole, + devtools, preInstall, // Firefox runner injected dependencies. diff --git a/src/extension-runners/firefox-desktop.js b/src/extension-runners/firefox-desktop.js index e9f67a36cf..eab86d0419 100644 --- a/src/extension-runners/firefox-desktop.js +++ b/src/extension-runners/firefox-desktop.js @@ -33,6 +33,7 @@ import type {FirefoxInfo} from '../firefox/index'; // eslint-disable-line import type FirefoxDesktopSpecificRunnerParams = {| customPrefs?: FirefoxPreferences, browserConsole: boolean, + devtools: boolean, firefoxBinary: string, preInstall: boolean, @@ -212,6 +213,7 @@ export class FirefoxDesktopExtensionRunner { async startFirefoxInstance() { const { browserConsole, + devtools, extensions, firefoxBinary, preInstall, @@ -241,6 +243,7 @@ export class FirefoxDesktopExtensionRunner { firefoxBinary, binaryArgs, extensions, + devtools, }); this.runningInfo.firefox.on('close', () => { @@ -262,7 +265,7 @@ export class FirefoxDesktopExtensionRunner { for (const extension of extensions) { try { const addonId = await ( - remoteFirefox.installTemporaryAddon(extension.sourceDir) + remoteFirefox.installTemporaryAddon(extension.sourceDir, devtools) .then((installResult: FirefoxRDPResponseAddon) => { return installResult.addon.id; }) diff --git a/src/firefox/index.js b/src/firefox/index.js index 8a4e35aa66..d3079da8f0 100644 --- a/src/firefox/index.js +++ b/src/firefox/index.js @@ -90,6 +90,7 @@ export type FirefoxRunOptions = { binaryArgs?: Array, args?: Array, extensions: Array, + devtools: boolean, }; /* @@ -103,6 +104,7 @@ export async function run( firefoxBinary, binaryArgs, extensions, + devtools, }: FirefoxRunOptions = {} ): Promise { @@ -164,9 +166,13 @@ export async function run( throw error; }); - log.info( - 'Use --verbose or open Tools > Web Developer > Browser Console ' + - 'to see logging'); + if (!devtools) { + log.info('Use --verbose or --devtools to see logging'); + } + if (devtools) { + log.info('More info about WebExtensions debugging:'); + log.info('https://extensionworkshop.com/documentation/develop/debugging/'); + } firefox.stderr.on('data', (data) => { log.debug(`Firefox stderr: ${data.toString().trim()}`); diff --git a/src/firefox/remote.js b/src/firefox/remote.js index d5bcd03aac..6fe03858a6 100644 --- a/src/firefox/remote.js +++ b/src/firefox/remote.js @@ -131,7 +131,8 @@ export class RemoteFirefox { } async installTemporaryAddon( - addonPath: string + addonPath: string, + openDevTools?: boolean ): Promise { const addonsActor = await this.getAddonsActor(); @@ -140,6 +141,7 @@ export class RemoteFirefox { to: addonsActor, type: 'installTemporaryAddon', addonPath, + openDevTools, }); log.debug(`installTemporaryAddon: ${JSON.stringify(response)}`); log.info(`Installed ${addonPath} as a temporary add-on`); diff --git a/src/program.js b/src/program.js index a27aae854d..63fc0be3f5 100644 --- a/src/program.js +++ b/src/program.js @@ -707,6 +707,12 @@ Example: $0 --help run. demandOption: false, type: 'array', }, + 'devtools': { + describe: 'Open the DevTools for the installed add-on ' + + '(Firefox 106 and later)', + demandOption: false, + type: 'boolean', + }, 'browser-console': { alias: ['bc'], describe: 'Open the DevTools Browser Console.', diff --git a/tests/functional/fake-firefox-binary.js b/tests/functional/fake-firefox-binary.js index 2f8690258d..00ffb3fe2e 100755 --- a/tests/functional/fake-firefox-binary.js +++ b/tests/functional/fake-firefox-binary.js @@ -9,6 +9,7 @@ const REQUEST_INSTALL_ADDON = { to: 'fakeAddonsActor', type: 'installTemporaryAddon', addonPath: process.env.addonPath, + openDevTools: false, // Introduced in Firefox 106 (Bug 1787409 / Bug 1789245) }; const REPLY_INSTALL_ADDON = { from: 'fakeAddonsActor', diff --git a/tests/unit/test-firefox/test.firefox.js b/tests/unit/test-firefox/test.firefox.js index c518df91cf..db70520a37 100644 --- a/tests/unit/test-firefox/test.firefox.js +++ b/tests/unit/test-firefox/test.firefox.js @@ -11,6 +11,9 @@ import {fs} from 'mz'; import * as firefox from '../../../src/firefox/index.js'; import {onlyInstancesOf, UsageError, WebExtError} from '../../../src/errors.js'; import {withTempDir} from '../../../src/util/temp-dir.js'; +import { + consoleStream, // instance is imported to inspect logged messages +} from '../../../src/util/logger.js'; import { basicManifest, fixturePath, @@ -240,6 +243,41 @@ describe('firefox', () => { }); }); + it('logs link to debugging docs', async () => { + const runner = createFakeFxRunner(); + consoleStream.flushCapturedLogs(); + consoleStream.startCapturing(); + + const expectedMessage = 'More info about WebExtensions debugging:'; + const expectedURLToDocs = + 'https://extensionworkshop.com/documentation/develop/debugging/'; + await runFirefox({fxRunner: runner, devtools: false}); + assert.notOk(consoleStream.capturedMessages.find( + (msg) => msg.includes(expectedMessage) + )); + assert.notOk(consoleStream.capturedMessages.find( + (msg) => msg.includes(expectedURLToDocs) + )); + + consoleStream.flushCapturedLogs(); + + await runFirefox({fxRunner: runner, devtools: true}); + const foundMessage = consoleStream.capturedMessages.find( + (msg) => msg.includes(expectedMessage) + ); + const foundDocURL = consoleStream.capturedMessages.find( + (msg) => msg.includes(expectedURLToDocs) + ); + + // Expect the logs to be found. + assert.ok(foundMessage); + assert.ok(foundDocURL); + + // Expected to be emitted as info level logs. + assert.ok(foundMessage?.includes('[info]')); + assert.ok(foundDocURL?.includes('[info]')); + }); + }); describe('copyProfile', () => { @@ -1003,7 +1041,6 @@ describe('firefox', () => { sinon.assert.calledOnce(fakeAsyncFsStat); } )); - }); }); diff --git a/tests/unit/test-firefox/test.remote.js b/tests/unit/test-firefox/test.remote.js index 6213fba295..f607f18288 100644 --- a/tests/unit/test-firefox/test.remote.js +++ b/tests/unit/test-firefox/test.remote.js @@ -297,11 +297,11 @@ describe('firefox.remote', () => { const addonsActor = 'addons1.actor.conn'; const addonPath = '/path/to/addon'; - client.request.withArgs({ + client.request.withArgs(sinon.match({ type: 'installTemporaryAddon', to: addonsActor, addonPath, - }).resolves({ + })).resolves({ addon: {id: 'abc123@temporary-addon'}, }); diff --git a/tests/unit/test.program.js b/tests/unit/test.program.js index 80e214c849..9376f080fb 100644 --- a/tests/unit/test.program.js +++ b/tests/unit/test.program.js @@ -552,6 +552,21 @@ describe('program.main', () => { }); }); + it('opens devtools when --devtools is specified', () => { + const fakeCommands = fake(commands, { + run: () => Promise.resolve(), + }); + return execProgram( + ['run', '--devtools'], + {commands: fakeCommands}) + .then(() => { + sinon.assert.calledWithMatch( + fakeCommands.run, + {devtools: true} + ); + }); + }); + async function testWatchFileOption(watchFile) { const fakeCommands = fake(commands, { run: () => Promise.resolve(),