When testing WebExtensions you might want a working fake implementation of the API in-memory available without spawning a complete browser.
This package depends on sinon and webextensions-api-mock to have the whole browser
WebExtension API available as sinon stubs
. You can pass in your own stubbed version of the browser
.
npm install --save-dev webextensions-api-fake sinon
Important: sinon
is a peer dependency, so you have to install it yourself. That's because it can otherwise lead to unexpected assertion behavior when sinon does instanceof
checks internally. It also allows to upgrade sinon without the need to bump the version in webextensions-api-fake
.
import browserFake from 'webextensions-api-fake';
// or
// const { default: browserFake } = require('webextensions-api-fake');
const browser = browserFake();
browser
is now a webextensions-api-mock
with faked api.
Currently supported API fake implementations based on Firefox57+:
-
- create
- get
- getAll
- clear
- clearAll
-
- create
- triggers: onCreated
- get
- remove
- triggers: onRemoved
- query
- update
- triggers: onUpdated
- create
-
- get
- getAll
- getAllCookieStores
- remove
- set
-
- getAcceptLanguages
- Returns
['en-US']
by default, can be overwritten by_setAcceptLanguages
- Returns
- getMessage
- Returns results based on the
locales
anddefault_locale
passed asoptions
- Returns results based on the
- getUILanguage
- Returns
en-US
by default, can be overwritten by_setUILanguage
- Returns
- detectLanguage
- Returns a Promise that resolves to the result of
getUILanguage
- Returns a Promise that resolves to the result of
- getAcceptLanguages
-
- create
- You can pass in any parameter you want to overwrite
- triggers:
onCreated
. Ifurl
is given that doesn't start withabout:
ormoz-ext:
:webRequest.onBeforeRequest
,webRequest.onCompleted
,onUpdated
- update
- triggers: If
url
is given that doesn't start withabout:
ormoz-ext:
:webRequest.onBeforeRequest
,webRequest.onCompleted
,onUpdated
- triggers: If
- get
- query
- remove
- triggers: onRemoved
- create
-
- local
- get
- remove
- set
- clean
- sync
- get
- remove
- set
- clean
- managed
- get
- remove
- set
- clean
- local
Faked API methods are also directly available with underscore prefix. E.g. browser.tabs._create
exposes the browser.tabs.create
fake. This can be useful to trigger fake behavior from tests without polluting its sinon call history.
-
i18n
- _setAcceptLanguages
- Overwrite the default for
getAcceptLanguages
- Overwrite the default for
- _setUILanguage
- Overwrite the default for
getUILanguage
- Overwrite the default for
- _setAcceptLanguages
-
tabs
- _create - helper method, same as
create
, but takes a special fake object that you can pass as second parameter with the following properties- options
<object>
, optional- webRequest
<object>
, optional, lets you overwrite the object properties for the request that triggerswebRequest.onBeforeRequest
, e.g.requestId
- webRequestRedirects
<array>
, optional, triggerswebRequest.onBeforeRequest
again with the given URLs in the array in the order they are listed. Instead of an URL string its possible to pass an object with propertiesurl
(the url to redirect) andwebRequest
(overwrite request parameters) - webRequestDontYield
<array>
, optional, given listeners are not triggered, e.g.onCompleted
- webRequestError
<boolean>
, optional, iftrue
is givenonErrorOccurred
will be triggered instead ofonCompleted
- instantRedirects
<boolean>
, optional, iftrue
is given redirects will not await theonBeforeRequest
promise
- webRequest
- responses
<object>
, optional, will get filled with the following structure if given- webRequest
<object>
, contains results of the call (yield
) fromonBeforeRequest
andonCompleted
as properties. Also contains therequest
property which is the object passed into theonBeforeRequest
call. - tabs
<object>
, contains results of the call (yield
) fromonCreated
andonUpdated
as properties - promises
<array>
, contains return values of all calls, useful to await Promise.all
- webRequest
- options
- _navigate - helper method to trigger
onBeforeRequest
- tabId
<integer>
, required, id of the tab - url
<string>
, required, url to navigate to, will mutate the tabs url - webRequest
<object>
, optional, lets you overwriterequest
parameters
- tabId
- _redirect - helper method to trigger
onBeforeRequest
for a tab with already usedrequest
, imitating a redirect. Will automatically use the lastrequest
seen for this tab if not overwritten bywebRequest
. Will mutate the stored tabsurl
to the lasturl
in the array. Takes the parameters:- tabId
<integer>
, required, id of the tab - redirects
<array>
, required, triggerswebRequest.onBeforeRequest
with the given URLs in the array in the order they are listed. Instead of an URL string its possible to pass an object with propertiesurl
(the url to redirect) andwebRequest
(overwrite request parameters) - webRequest
<object>
, optional, lets you overwriterequest
parameters
- tabId
- _registerRedirects - helper method to register triggering
onBeforeRequest
for the given redirect urls if the registeredurl
is seen in atabs.create
ortabs.update
. Will mutate the tabs url to the last redirect url. Has higher precedence thanwebRequestRedirects
- targetUrl
<string>
, required, the target url - redirectUrls
<array>
, required, the urls for which follow-uponBeforeRequest
calls are made. Instead of an URL string its possible to pass an object with propertiesurl
(the url to redirect) andwebRequest
(overwrite request parameters)
- targetUrl
- _unregisterRedirects - helper method to remove registered redirects for the given target url
- targetUrl
<string>
, required, the target url
- targetUrl
- _lastRequestId - helper method to return the last used
requestId
- _create - helper method, same as
Given the following production code for your WebExtension:
example.js
browser.tabs.onCreated.addListener(async tab => {
await browser.storage.local.set({
lastCreatedTab: tab,
});
});
const firstWeDoThis = async () => {
const container = await browser.contextualIdentities.create({
name: 'My Container',
color: 'blue',
icon: 'fingerprint',
});
await browser.storage.local.set({
lastCreatedContainer: container.cookieStoreId,
});
};
const thenWeDoThat = async () => {
const { lastCreatedContainer } = await browser.storage.local.get(
'lastCreatedContainer'
);
await browser.tabs.create({
cookieStoreId: lastCreatedContainer,
});
};
const myFancyFeature = async () => {
await firstWeDoThis();
await thenWeDoThat();
};
myFancyFeature();
You could have a test that looks like this (using mocha
, sinon-chai
, chai.should
and require-reload
in this case):
example.test.js
const { default: browserFake } = require('webextensions-api-fake');
const reload = require('require-reload')(require);
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const chai = require('chai');
chai.should();
chai.use(sinonChai);
describe('Useful WebExtension', () => {
beforeEach(async () => {
// fake the browser
global.browser = browserFake();
// execute the production code
reload('./example.js');
// wait a tick to give the production code the chance to execute
return new Promise(resolve => process.nextTick(resolve));
// instead of doing a require and then waiting for the next tick
// it would also be possible to set e.g. `global._testEnv = true;` in the test
// and in the production code something like
// if (!_testEnv) {
// myFancyFeature();
// } else {
// module.exports = myFancyFeature;
// }
//
// that would make it possible to get the actual function when doing require
});
describe('My Fancy Feature which is executed on load', () => {
it('should work', async () => {
browser.tabs.create.should.have.been.calledWithMatch({
cookieStoreId: sinon.match.string,
});
const tabs = await browser.tabs.query({});
tabs.length.should.equal(1);
});
});
describe('Triggering listeners after loading the production code', () => {
it('should work as well', async () => {
const createdTab = await browser.tabs.create({});
const { lastCreatedTab } = await browser.storage.local.get(
'lastCreatedTab'
);
lastCreatedTab.id.should.equal(createdTab.id);
});
});
});
You can find the example in the examples
directory and also execute it:
npm install
npm run example
If you want to execute your WebExtensions tests using JSDOM, then webextensions-jsdom
might be for you.
- options
<object>
, optional- browser
<object>
, optional, stubbed version of the WebExtensions API. Defaults towebextensions-api-mock
if not given - locales
<object>
, optional, used for thei18n.getMessage
fake. Format is{locale: messages}
. E.g.:{'en': {'translated': {'message': 'hello world'}}}
- default_locale
<string>
, optional, used for thei18n.getMessage
fake
- browser
Returns a new stubbed browser
with newly created and applied fakes.
Returns a new stubbed browser
without applied fakes.
- browser
<object>
, required, Stubbed version of the WebExtensions API
Applies the API fakes to the given browser object. Can be called multiple times with different browser stubs and applies the same fakes (with the same in-memory data) in that case.