Home

Awesome

WebExtensions API Fake

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.

Installation

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.

Usage

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.

API Fake

Currently supported API fake implementations based on Firefox57+:

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.

Special Fake Methods

NodeJS Example

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

JSDOM

If you want to execute your WebExtensions tests using JSDOM, then webextensions-jsdom might be for you.

API

Exported default function([options])

Returns a new stubbed browser with newly created and applied fakes.

Exported Class: WebExtensionsApiFake

Constructor: new WebExtensionsApiFake
webExtensionsApiFake.createBrowser([options])

Returns a new stubbed browser without applied fakes.

webExtensionsApiFake.fakeApi(browser)

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.