163 lines
5.2 KiB
Markdown
163 lines
5.2 KiB
Markdown
# Testing
|
|
|
|
WXT provides several utils for writing tests.
|
|
|
|
## Unit tests
|
|
|
|
If you're using auto-imports (enabled by default), [Vitest](https://vitest.dev/) is the only testing framework that supports them.
|
|
|
|
If you want to use a different testing library/framework (like Jest, mocha, node:test, etc), you can keep using it, but you have two options:
|
|
|
|
1. Switch to Vitest (recommended)
|
|
2. Configure the testing library manually
|
|
- Disable auto-imports by setting `imports: false` in your `wxt.config.ts` file
|
|
- Manually add globals normally provided by WXT (like `__BROWSER__`) that you consume to the global scope before accessing them (`globalThis.__BROWSER__ = "chrome"`)
|
|
|
|
### Vitest Setup
|
|
|
|
Install vitest and add the `WxtVitest` plugin to your `vitest.config.ts` file.
|
|
|
|
```sh
|
|
pnpm i -D vitest
|
|
```
|
|
|
|
```ts
|
|
// <root>/vitest.config.ts
|
|
import { defineConfig } from 'vitest/config';
|
|
import { WxtVitest } from 'wxt/testing';
|
|
|
|
export default defineConfig({
|
|
plugins: [WxtVitest()],
|
|
test: {
|
|
server: {
|
|
deps: {
|
|
// Add any dependencies that import webextension-polyfill here, otherwise tests will attempt to import the real polyfill, breaking the
|
|
// TODO: Auto-detect these dependencies inside `WxtVitest` so maintaining this list manually isn't necessary
|
|
inline: [...],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
And that's it. You're ready to start writing tests.
|
|
|
|
### Writing Tests
|
|
|
|
Here's a very basic test, written with a few different testing libraries, with a few different approaches for mocking the `browser` global.
|
|
|
|
:::code-group
|
|
|
|
```ts [Vitest]
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
function logRuntimeId() {
|
|
// Vitest automatically mocks "browser" with "fakeBrowser"
|
|
console.log(browser.runtime.id);
|
|
}
|
|
|
|
describe('logRuntimeId', () => {
|
|
it("should log the extension's runtime ID", () => {
|
|
// Set a known ID on fakeBrowser for the test
|
|
const id = 'some-runtime-id';
|
|
fakeBrowser.runtime.id = id;
|
|
const logSpy = vi.spyOn(console, 'log');
|
|
|
|
logRuntimeId();
|
|
|
|
expect(logSpy).toBeCalledWith(id);
|
|
});
|
|
});
|
|
```
|
|
|
|
```ts [Jest - Manual Mock]
|
|
import { fakeBrowser } from 'wxt/testing';
|
|
import { browser } from 'wxt/browser';
|
|
|
|
function logRuntimeId() {
|
|
console.log(browser.runtime.id);
|
|
}
|
|
|
|
// Manually mock
|
|
jest.mock('wxt/browser', () => {
|
|
const { fakeBrowser } = require('wxt/testing');
|
|
return { browser: fakeBrowser };
|
|
});
|
|
|
|
describe('logRuntimeId', () => {
|
|
it("should log the extension's runtime ID", () => {
|
|
// Set a known ID on fakeBrowser for the test
|
|
const id = 'some-runtime-id';
|
|
fakeBrowser.runtime.id = id;
|
|
const logSpy = jest.spyOn(console, 'log');
|
|
|
|
logRuntimeId();
|
|
|
|
expect(logSpy).toBeCalledWith(id);
|
|
});
|
|
});
|
|
```
|
|
|
|
```ts [node:test - Parameterized]
|
|
import { describe, it, mock } from 'node:test';
|
|
import { assert } from 'node:assert';
|
|
import { fakeBrowser } from 'wxt/testing';
|
|
import { browser } from 'wxt/browser';
|
|
|
|
// Add browser as a parameter so fakeBrowser can be passed instead of browser
|
|
function logRuntimeId(browser = browser) {
|
|
console.log(browser.runtime.id);
|
|
}
|
|
|
|
describe('logRuntimeId', () => {
|
|
it("should log the extension's runtime ID", () => {
|
|
// Set a known ID on fakeBrowser for the test
|
|
const id = 'some-runtime-id';
|
|
fakeBrowser.runtime.id = id;
|
|
console.log = mock.fn();
|
|
|
|
// pass in fakeBrowser during tests
|
|
logRuntimeId(fakeBrowser);
|
|
|
|
assert.deepStrictEqual(console.log.mock.calls[0].arguments, [id]);
|
|
});
|
|
});
|
|
```
|
|
|
|
:::
|
|
|
|
:::warning
|
|
Without mocking the `browser` variable, you'll see errors like this:
|
|
|
|
```
|
|
This script should only be loaded in a browser extension.
|
|
```
|
|
|
|
:::
|
|
|
|
WXT provides an in-memory, partial implementation of `browser`, [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser), from the [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/guide/fake-browser/) package. `fakeBrowser` works with all testing frameworks/libraries. See their docs for a list of [implemented APIs](https://webext-core.aklinker1.io/guide/fake-browser/implemented-apis.html) and more example tests.
|
|
|
|
## E2E Tests
|
|
|
|
WXT does not provide any utils for running E2E tests. There are two libraries you can use to run E2E tests for any chrome extension.
|
|
|
|
- [`playwright`](https://playwright.dev/docs/chrome-extensions) (recommended) - "A high-level API to automate web browsers"
|
|
- [`puppeteer`](https://pptr.dev/guides/chrome-extensions) - "A high-level API to control headless Chrome over the DevTools Protocol"
|
|
|
|
:::info
|
|
Note that both only support running tests on Chrome.
|
|
:::
|
|
|
|
Before running tests with either of these tools, you must build the extension with `wxt build` and then load the extension from the output directory in a new tab.
|
|
|
|
To test an extension's UI, like the popup or options page, you'll need to know the extension's ID to open the URL directly.
|
|
|
|
> _chrome-extension://`browser.runtime.id`/popup.html_
|
|
|
|
- Playwright provides an API to get your extension ID after it has been installed. [See their docs](https://playwright.dev/docs/chrome-extensions#testing).
|
|
- Puppeteer requires you know the ID before installing the extension, so you can hard code it into the URLs you open. Follow [Chrome's guide](https://developer.chrome.com/docs/extensions/mv3/manifest/key/) to setup a consistent runtime id.
|
|
|
|
:::info
|
|
You cannot test popups in their normal popup window, you have to open them in a tab.
|
|
:::
|