npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

webextensions-jsdom

v1.4.1

Published

Load popup, sidebar and background with JSDOM based on the manifest.json for testing purposes

Downloads

340

Readme

WebExtensions JSDOM

When testing WebExtensions you might want to test your browser_action/page_action default_popup, sidebar_action default_panel or background page/scripts inside JSDOM. This package lets you do that based on the manifest.json. It will automatically stub window.browser with webextensions-api-mock.

Installation

npm install --save-dev webextensions-jsdom 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-mock.

Usage

const webExtensionsJSDOM = require("webextensions-jsdom");
const webExtension = await webExtensionsJSDOM.fromManifest(
  "/absolute/path/to/manifest.json"
);

Based on what's given in your manifest.json this will create JSDOM instances with stubbed browser and load popup/sidebar/background in it.

The resolved return value is an <object> with several properties:

  • background <object>, with properties dom, window, document, browser and destroy (If background page or scripts are defined in the manifest)
  • popup <object>, with properties dom, window, document, browser and destroy (If browser_action with default_popup is defined in the manifest)
  • pageActionPopup <object>, with properties dom, window, document, browser and destroy (If page_action with default_popup is defined in the manifest)
  • sidebar <object>, with properties dom, window, document, browser and destroy (If sidebar_action with default_panel is defined in the manifest)
  • destroy <function>, shortcut to background.destroy, popup.destroy and sidebar.destroy

dom is a new JSDOM instance. window is a shortcut to dom.window. document is a shortcut to dom.window.document. browser is a new webextensions-api-mock instance that is also exposed on dom.window.browser. And destroy is a function to clean up. More infos in the API docs.

If you expose variables in your code on window, you can access them now, or trigger registered listeners by e.g. browser.webRequest.onBeforeRequest.addListener.yield([arguments]).

Automatic wiring

If popup/sidebar and background are defined and loaded then runtime.sendMessage in the popup is automatically wired with runtime.onMessage in the background if you pass the wiring: true option. That makes it possible to e.g. "click" elements in the popup and then check if the background was called accordingly, making it ideal for feature-testing.

await webExtensionsJSDOM.fromManifest("/absolute/path/to/manifest.json", {
  wiring: true
});

API Fake

Passing apiFake: true in the options to fromManifest automatically applies webextensions-api-fake to the browser stubs. It will imitate some of the WebExtensions API behavior (like an in-memory storage), so you don't have to manually define behavior. This is especially useful when feature-testing.

await webExtensionsJSDOM.fromManifest("/absolute/path/to/manifest.json", {
  apiFake: true
});

Code Coverage

Code coverage with nyc / istanbul is supported if you execute the test using webextensions-jsdom with nyc. To get coverage-output you need to call the exposed destroy function after the background, popup and/or sidebar are no longer needed. This should ideally be after each test.

If you want to know how that's possible you can check out this excellent article by @freaktechnik.

Chrome Extensions

Not supported, but you could use webextension-polyfill.

Example

In your manifest.json you have default_popup and background page defined:

{
  "browser_action": {
    "default_popup": "popup.html"
  },

  "background": {
    "page": "background.html"
  }
}

Note: "scripts" are supported too.

In your popup.js loaded from popup.html you have something like this:

document.getElementById("doSomethingUseful").addEventListener("click", () => {
  browser.runtime.sendMessage({
    method: "usefulMessage"
  });
});

and in your background.js loaded from the background.html

browser.runtime.onMessage.addListener(message => {
  // do something useful with the message
});

To test this with webextensions-jsdom you can do (using mocha, chai and sinon-chai in this case):

const path = require("path");
const sinonChai = require("sinon-chai");
const chai = require("chai");
const expect = chai.expect;
chai.use(sinonChai);

const webExtensionsJSDOM = require("webextensions-jsdom");
const manifestPath = path.resolve(
  path.join(__dirname, "path/to/manifest.json")
);

describe("Example", () => {
  let webExtension;
  beforeEach(async () => {
    webExtension = await webExtensionsJSDOM.fromManifest(manifestPath, {
      wiring: true
    });
  });

  describe("Clicking in the popup", () => {
    beforeEach(async () => {
      await webExtension.popup.document
        .getElementById("doSomethingUseful")
        .click();
    });

    it("should call the background", async () => {
      expect(
        webExtension.background.browser.onMessage.addListener
      ).to.have.been.calledWithMatch({
        method: "usefulMessage"
      });
    });
  });

  afterEach(async () => {
    await webExtension.destroy();
  });
});

There's a fully functional example in examples/random-container-tab.

API

Exported function fromManifest(path[, options])

  • path <string>, required, absolute path to the manifest.json file
  • options <object>, optional
    • background <object|false> optional, if false is given background wont be loaded
      • jsdom <object>, optional, this will set all given properties as options for the JSDOM constructor, an useful example might be beforeParse(window). Note: Setting resources or runScripts might lead to unexpected behavior.
      • afterBuild(background) <function> optional, executed directly after the background dom is build (might be useful to do things before the popup dom starts building). If a Promise is returned it will be resolved before continuing.
    • popup <object|false> optional, if false is given popup wont be loaded
    • pageActionPopup <object|false> optional, if false is given popup wont be loaded
      • jsdom <object>, optional, this will set all given properties as options for the JSDOM constructor, an useful example might be beforeParse(window). Note: Setting resources or runScripts might lead to unexpected behavior.
      • afterBuild(popup) <function> optional, executed after the popup dom is build. If a Promise is returned it will be resolved before continuing.
    • sidebar <object|false> optional, if false is given sidebar wont be loaded
      • jsdom <object>, optional, this will set all given properties as options for the JSDOM constructor, an useful example might be beforeParse(window). Note: Setting resources or runScripts might lead to unexpected behavior.
      • afterBuild(sidebar) <function> optional, executed after the sidebar dom is build. If a Promise is returned it will be resolved before continuing.
    • autoload <boolean> optional, if false will not automatically load background/popup/sidebar (might be useful for loadURL)
    • apiFake <boolean> optional, if true automatically applies API fakes to the browser using webextensions-api-fake and if path/_locales is present its content will get passed down to api-fake.
    • wiring <boolean> optional, if true the automatic wiring is enabled

Returns a Promise that resolves an <object> with the following properties in case of success:

  • background <object>

    • dom <object> the JSDOM object
    • window <object> shortcut to dom.window
    • document <object> shortcut to dom.window.document
    • browser <object> stubbed browser using webextensions-api-mock
    • destroy <function> destroy the dom and potentially write coverage data if executed with nyc. Returns a Promise that resolves if destroying is done.
  • popup <object>

    • dom <object> the JSDOM object
    • window <object> shortcut to dom.window
    • document <object> shortcut to dom.window.document
    • browser <object> stubbed browser using webextensions-api-mock
    • destroy <function> destroy the dom and potentially write coverage data if executed with nyc. Returns a Promise that resolves if destroying is done.
    • helper <object>
      • clickElementById(id) <function> shortcut for dom.window.document.getElementById(id).click();, returns a promise
  • pageActionPopup <object>

    • dom <object> the JSDOM object
    • window <object> shortcut to dom.window
    • document <object> shortcut to dom.window.document
    • browser <object> stubbed browser using webextensions-api-mock
    • destroy <function> destroy the dom and potentially write coverage data if executed with nyc. Returns a Promise that resolves if destroying is done.
    • helper <object>
      • clickElementById(id) <function> shortcut for dom.window.document.getElementById(id).click();, returns a promise
  • sidebar <object>

    • dom <object> the JSDOM object
    • window <object> shortcut to dom.window
    • document <object> shortcut to dom.window.document
    • browser <object> stubbed browser using webextensions-api-mock
    • destroy <function> destroy the dom and potentially write coverage data if executed with nyc. Returns a Promise that resolves if destroying is done.
    • helper <object>
      • clickElementById(id) <function> shortcut for dom.window.document.getElementById(id).click();, returns a promise
  • destroy <function>, shortcut to call background.destroy,popup.destroy and sidebar.destroy. Returns a Promise that resolves if destroying is done.

Exported function fromFile(path[, options])

Load an arbitrary .html file, accepts the following parameters:

  • path <string>, required, absolute path to the html file that should be loaded
  • options <object>, optional, accepts the following parameters

Returns a Promise that resolves an <object> with the following properties in case of success:

  • dom <object> the JSDOM object
  • window <object> shortcut to dom.window
  • document <object> shortcut to dom.window.document
  • browser <object> stubbed browser using webextensions-api-mock
  • destroy <function> destroy the dom and potentially write coverage data if executed with nyc. Returns a Promise that resolves if destroying is done.

GeckoDriver

If you're looking for a way to do functional testing with GeckoDriver then webextensions-geckodriver might be for you.

Sinon useFakeTimers

sinon.useFakeTimers({
  toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"]
});
  • https://stackoverflow.com/a/50152624