{"id":13798676,"url":"https://github.com/stoically/webextensions-jsdom","last_synced_at":"2025-07-17T20:37:49.135Z","repository":{"id":111377258,"uuid":"123647313","full_name":"stoically/webextensions-jsdom","owner":"stoically","description":"Load popup, sidebar and background with JSDOM based on the manifest.json for testing purposes","archived":false,"fork":false,"pushed_at":"2022-06-30T22:15:33.000Z","size":172,"stargazers_count":19,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-14T17:12:17.664Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stoically.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-03-03T01:08:26.000Z","updated_at":"2024-07-14T18:17:28.000Z","dependencies_parsed_at":"2024-01-02T22:29:44.031Z","dependency_job_id":"9a56584e-075c-4b0a-8e4e-96738222b2ca","html_url":"https://github.com/stoically/webextensions-jsdom","commit_stats":null,"previous_names":["webexts/webextensions-jsdom"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoically%2Fwebextensions-jsdom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoically%2Fwebextensions-jsdom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoically%2Fwebextensions-jsdom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoically%2Fwebextensions-jsdom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stoically","download_url":"https://codeload.github.com/stoically/webextensions-jsdom/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248923765,"owners_count":21183953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T00:00:49.515Z","updated_at":"2025-04-14T17:12:22.093Z","avatar_url":"https://github.com/stoically.png","language":"JavaScript","funding_links":[],"categories":["Testing"],"sub_categories":[],"readme":"### WebExtensions JSDOM\n\nWhen testing [WebExtensions](https://developer.mozilla.org/Add-ons/WebExtensions) you might want to test your browser_action/page_action default_popup, sidebar_action default_panel or background page/scripts inside [JSDOM](https://github.com/jsdom/jsdom). This package lets you do that based on the `manifest.json`. It will automatically stub `window.browser` with [`webextensions-api-mock`](https://github.com/stoically/webextensions-api-mock).\n\n### Installation\n\n```\nnpm install --save-dev webextensions-jsdom sinon\n```\n\n**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`.\n\n### Usage\n\n```js\nconst webExtensionsJSDOM = require(\"webextensions-jsdom\");\nconst webExtension = await webExtensionsJSDOM.fromManifest(\n  \"/absolute/path/to/manifest.json\"\n);\n```\n\nBased on what's given in your `manifest.json` this will create JSDOM instances with stubbed `browser` and load popup/sidebar/background in it.\n\nThe resolved return value is an `\u003cobject\u003e` with several properties:\n\n- _background_ `\u003cobject\u003e`, with properties `dom`, `window`, `document`, `browser` and `destroy` (If background page or scripts are defined in the manifest)\n- _popup_ `\u003cobject\u003e`, with properties `dom`, `window`, `document`, `browser` and `destroy` (If browser_action with default_popup is defined in the manifest)\n- _pageActionPopup_ `\u003cobject\u003e`, with properties `dom`, `window`, `document`, `browser` and `destroy` (If page_action with default_popup is defined in the manifest)\n- _sidebar_ `\u003cobject\u003e`, with properties `dom`, `window`, `document`, `browser` and `destroy` (If sidebar_action with default_panel is defined in the manifest)\n- _destroy_ `\u003cfunction\u003e`, shortcut to `background.destroy`, `popup.destroy` and `sidebar.destroy`\n\n`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](#api).\n\nIf 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])`.\n\n### Automatic wiring\n\nIf 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.\n\n```js\nawait webExtensionsJSDOM.fromManifest(\"/absolute/path/to/manifest.json\", {\n  wiring: true\n});\n```\n\n### API Fake\n\nPassing `apiFake: true` in the options to `fromManifest` automatically applies [`webextensions-api-fake`](https://github.com/stoically/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.\n\n```js\nawait webExtensionsJSDOM.fromManifest(\"/absolute/path/to/manifest.json\", {\n  apiFake: true\n});\n```\n\n### Code Coverage\n\nCode coverage with [nyc / istanbul](https://istanbul.js.org/) 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.\n\nIf you want to know how that's possible you can [check out this excellent article by @freaktechnik](https://humanoids.be/log/2017/10/code-coverage-reports-for-webextensions/).\n\n### Chrome Extensions\n\nNot supported, but you could use [webextension-polyfill](https://github.com/mozilla/webextension-polyfill).\n\n### Example\n\nIn your `manifest.json` you have default_popup and background page defined:\n\n```json\n{\n  \"browser_action\": {\n    \"default_popup\": \"popup.html\"\n  },\n\n  \"background\": {\n    \"page\": \"background.html\"\n  }\n}\n```\n\n_Note: \"scripts\" are supported too._\n\nIn your `popup.js` loaded from `popup.html` you have something like this:\n\n```js\ndocument.getElementById(\"doSomethingUseful\").addEventListener(\"click\", () =\u003e {\n  browser.runtime.sendMessage({\n    method: \"usefulMessage\"\n  });\n});\n```\n\nand in your `background.js` loaded from the `background.html`\n\n```js\nbrowser.runtime.onMessage.addListener(message =\u003e {\n  // do something useful with the message\n});\n```\n\nTo test this with `webextensions-jsdom` you can do (using `mocha`, `chai` and `sinon-chai` in this case):\n\n```js\nconst path = require(\"path\");\nconst sinonChai = require(\"sinon-chai\");\nconst chai = require(\"chai\");\nconst expect = chai.expect;\nchai.use(sinonChai);\n\nconst webExtensionsJSDOM = require(\"webextensions-jsdom\");\nconst manifestPath = path.resolve(\n  path.join(__dirname, \"path/to/manifest.json\")\n);\n\ndescribe(\"Example\", () =\u003e {\n  let webExtension;\n  beforeEach(async () =\u003e {\n    webExtension = await webExtensionsJSDOM.fromManifest(manifestPath, {\n      wiring: true\n    });\n  });\n\n  describe(\"Clicking in the popup\", () =\u003e {\n    beforeEach(async () =\u003e {\n      await webExtension.popup.document\n        .getElementById(\"doSomethingUseful\")\n        .click();\n    });\n\n    it(\"should call the background\", async () =\u003e {\n      expect(\n        webExtension.background.browser.onMessage.addListener\n      ).to.have.been.calledWithMatch({\n        method: \"usefulMessage\"\n      });\n    });\n  });\n\n  afterEach(async () =\u003e {\n    await webExtension.destroy();\n  });\n});\n```\n\nThere's a fully functional example in [`examples/random-container-tab`](examples/random-container-tab).\n\n### API\n\n#### Exported function fromManifest(path[, options])\n\n- _path_ `\u003cstring\u003e`, required, absolute path to the `manifest.json` file\n- _options_ `\u003cobject\u003e`, optional\n  - _background_ `\u003cobject|false\u003e` optional, if `false` is given background wont be loaded\n    - _jsdom_ `\u003cobject\u003e`, optional, this will set all given properties as [options for the JSDOM constructor](https://github.com/jsdom/jsdom#customizing-jsdom), an useful example might be [`beforeParse(window)`](https://github.com/jsdom/jsdom#intervening-before-parsing). Note: Setting  `resources` or `runScripts` might lead to unexpected behavior.\n    - _afterBuild(background)_ `\u003cfunction\u003e` 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.\n  - _popup_ `\u003cobject|false\u003e` optional, if `false` is given popup wont be loaded\n    - _jsdom_ `\u003cobject\u003e`, optional, this will set all given properties as [options for the JSDOM constructor](https://github.com/jsdom/jsdom#customizing-jsdom), an useful example might be [`beforeParse(window)`](https://github.com/jsdom/jsdom#intervening-before-parsing). Note: Setting  `resources` or `runScripts` might lead to unexpected behavior.\n  - _pageActionPopup_ `\u003cobject|false\u003e` optional, if `false` is given popup wont be loaded\n    - _jsdom_ `\u003cobject\u003e`, optional, this will set all given properties as [options for the JSDOM constructor](https://github.com/jsdom/jsdom#customizing-jsdom), an useful example might be [`beforeParse(window)`](https://github.com/jsdom/jsdom#intervening-before-parsing). Note: Setting  `resources` or `runScripts` might lead to unexpected behavior.\n    - _afterBuild(popup)_ `\u003cfunction\u003e` optional, executed after the popup dom is build. If a Promise is returned it will be resolved before continuing.\n  - _sidebar_ `\u003cobject|false\u003e` optional, if `false` is given sidebar wont be loaded\n    - _jsdom_ `\u003cobject\u003e`, optional, this will set all given properties as [options for the JSDOM constructor](https://github.com/jsdom/jsdom#customizing-jsdom), an useful example might be [`beforeParse(window)`](https://github.com/jsdom/jsdom#intervening-before-parsing). Note: Setting  `resources` or `runScripts` might lead to unexpected behavior.\n    - _afterBuild(sidebar)_ `\u003cfunction\u003e` optional, executed after the sidebar dom is build. If a Promise is returned it will be resolved before continuing.\n  - _autoload_ `\u003cboolean\u003e` optional, if `false` will not automatically load background/popup/sidebar (might be useful for `loadURL`)\n  - _apiFake_ `\u003cboolean\u003e` optional, if `true` automatically applies [API fakes](#api-fake) to the `browser` using [`webextensions-api-fake`](https://github.com/stoically/webextensions-api-fake) and if `path/_locales` is present its content will get passed down to api-fake.\n  - _wiring_ `\u003cboolean\u003e` optional, if `true` the [automatic wiring](#automatic-wiring) is enabled\n\nReturns a Promise that resolves an `\u003cobject\u003e` with the following properties in case of success:\n\n- _background_ `\u003cobject\u003e`\n\n  - _dom_ `\u003cobject\u003e` the JSDOM object\n  - _window_ `\u003cobject\u003e` shortcut to `dom.window`\n  - _document_ `\u003cobject\u003e` shortcut to `dom.window.document`\n  - _browser_ `\u003cobject\u003e` stubbed `browser` using `webextensions-api-mock`\n  - _destroy_ `\u003cfunction\u003e` destroy the `dom` and potentially write coverage data if executed with `nyc`. Returns a Promise that resolves if destroying is done.\n\n- _popup_ `\u003cobject\u003e`\n\n  - _dom_ `\u003cobject\u003e` the JSDOM object\n  - _window_ `\u003cobject\u003e` shortcut to `dom.window`\n  - _document_ `\u003cobject\u003e` shortcut to `dom.window.document`\n  - _browser_ `\u003cobject\u003e` stubbed `browser` using `webextensions-api-mock`\n  - _destroy_ `\u003cfunction\u003e` destroy the `dom` and potentially write coverage data if executed with `nyc`. Returns a Promise that resolves if destroying is done.\n  - _helper_ `\u003cobject\u003e`\n    - _clickElementById(id)_ `\u003cfunction\u003e` shortcut for `dom.window.document.getElementById(id).click();`, returns a promise\n\n- _pageActionPopup_ `\u003cobject\u003e`\n\n  - _dom_ `\u003cobject\u003e` the JSDOM object\n  - _window_ `\u003cobject\u003e` shortcut to `dom.window`\n  - _document_ `\u003cobject\u003e` shortcut to `dom.window.document`\n  - _browser_ `\u003cobject\u003e` stubbed `browser` using `webextensions-api-mock`\n  - _destroy_ `\u003cfunction\u003e` destroy the `dom` and potentially write coverage data if executed with `nyc`. Returns a Promise that resolves if destroying is done.\n  - _helper_ `\u003cobject\u003e`\n    - _clickElementById(id)_ `\u003cfunction\u003e` shortcut for `dom.window.document.getElementById(id).click();`, returns a promise\n\n- _sidebar_ `\u003cobject\u003e`\n\n  - _dom_ `\u003cobject\u003e` the JSDOM object\n  - _window_ `\u003cobject\u003e` shortcut to `dom.window`\n  - _document_ `\u003cobject\u003e` shortcut to `dom.window.document`\n  - _browser_ `\u003cobject\u003e` stubbed `browser` using `webextensions-api-mock`\n  - _destroy_ `\u003cfunction\u003e` destroy the `dom` and potentially write coverage data if executed with `nyc`. Returns a Promise that resolves if destroying is done.\n  - _helper_ `\u003cobject\u003e`\n    - _clickElementById(id)_ `\u003cfunction\u003e` shortcut for `dom.window.document.getElementById(id).click();`, returns a promise\n\n- _destroy_ `\u003cfunction\u003e`, shortcut to call `background.destroy`,`popup.destroy` and `sidebar.destroy`. Returns a Promise that resolves if destroying is done.\n\n#### Exported function fromFile(path[, options])\n\nLoad an arbitrary `.html` file, accepts the following parameters:\n\n- _path_ `\u003cstring\u003e`, required, absolute path to the html file that should be loaded\n- _options_ `\u003cobject\u003e`, optional, accepts the following parameters\n  - _apiFake_ `\u003cboolean\u003e` optional, if `true` automatically applies [API fakes](#api-fake) to the `browser` using [`webextensions-api-fake`](https://github.com/stoically/webextensions-api-fake)\n  - _jsdom_ `\u003cobject\u003e`, optional, this will set all given properties as [options for the JSDOM constructor](https://github.com/jsdom/jsdom#customizing-jsdom), an useful example might be [`beforeParse(window)`](https://github.com/jsdom/jsdom#intervening-before-parsing). Note: Setting  `resources` or `runScripts`. might lead to unexpected behavior\n\nReturns a Promise that resolves an `\u003cobject\u003e` with the following properties in case of success:\n\n- _dom_ `\u003cobject\u003e` the JSDOM object\n- _window_ `\u003cobject\u003e` shortcut to `dom.window`\n- _document_ `\u003cobject\u003e` shortcut to `dom.window.document`\n- _browser_ `\u003cobject\u003e` stubbed `browser` using `webextensions-api-mock`\n- _destroy_ `\u003cfunction\u003e` destroy the `dom` and potentially write coverage data if executed with `nyc`. Returns a Promise that resolves if destroying is done.\n\n### GeckoDriver\n\nIf you're looking for a way to do functional testing with GeckoDriver then [`webextensions-geckodriver`](https://github.com/webexts/webextensions-geckodriver) might be for you.\n\n### Sinon useFakeTimers\n\n```js\nsinon.useFakeTimers({\n  toFake: [\"setTimeout\", \"clearTimeout\", \"setInterval\", \"clearInterval\"]\n});\n```\n\n- https://stackoverflow.com/a/50152624\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstoically%2Fwebextensions-jsdom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstoically%2Fwebextensions-jsdom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstoically%2Fwebextensions-jsdom/lists"}