There is a very neat guide in Jest's Docs about how to reuse browser instances.
You will have the following structure in the end:
[root]
...
+-- jest.config.js
+-- puppeteer_environment.js
+-- setup.js
+-- teardown.js
+-- test.js
jest.config.js: this is the default conf file for Jest, you will wire the other setup files here so Jest will use the custom puppeteer
setup.js: to launch puppeteer only once
puppeteer_environment.js: custom environment Class to connect to the correct websocket
teardown.js: close the browser instance at the end in Jest's globalTeardown
test.js: this is an example Jest test file, the important part is to define page in the beforeAll global as:
page = await globalThis.__BROWSER_GLOBAL__.newPage();
Disclaimer: the below example is copied from the linked page and it is used as a boilerplate by many test projects where the creators are not willing to use the jest-puppeteer preset, but instead set up a custom jest environment with puppeteer.
"[...] The basic idea is to:
- launch & file the websocket endpoint of puppeteer with Global Setup
- connect to puppeteer from each Test Environment
- close puppeteer with Global Teardown
Here's an example of the GlobalSetup script?
setup.js
const {mkdir, writeFile} = require('fs').promises;
const os = require('os');
const path = require('path');
const puppeteer = require('puppeteer');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function () {
const browser = await puppeteer.launch();
// store the browser instance so we can teardown it later
// this global is only available in the teardown but not in TestEnvironments
globalThis.__BROWSER_GLOBAL__ = browser;
// use the file system to expose the wsEndpoint for TestEnvironments
await mkdir(DIR, {recursive: true});
await writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());
};
Then we need a custom Test Environment for puppeteer
puppeteer_environment.js
const {readFile} = require('fs').promises;
const os = require('os');
const path = require('path');
const puppeteer = require('puppeteer');
const NodeEnvironment = require('jest-environment-node').default;
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
class PuppeteerEnvironment extends NodeEnvironment {
constructor(config) {
super(config);
}
async setup() {
await super.setup();
// get the wsEndpoint
const wsEndpoint = await readFile(path.join(DIR, 'wsEndpoint'), 'utf8');
if (!wsEndpoint) {
throw new Error('wsEndpoint not found');
}
// connect to puppeteer
this.global.__BROWSER_GLOBAL__ = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
});
}
async teardown() {
await super.teardown();
}
getVmContext() {
return super.getVmContext();
}
}
module.exports = PuppeteerEnvironment;
Finally, we can close the puppeteer instance and clean-up the file
teardown.js
const fs = require('fs').promises;
const os = require('os');
const path = require('path');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function () {
// close the browser instance
await globalThis.__BROWSER_GLOBAL__.close();
// clean-up the wsEndpoint file
await fs.rm(DIR, {recursive: true, force: true});
};
With all the things set up, we can now write our tests like this:
test.js
const timeout = 5000;
describe(
'/ (Home Page)',
() => {
let page;
beforeAll(async () => {
page = await globalThis.__BROWSER_GLOBAL__.newPage();
await page.goto('https://google.com');
}, timeout);
it('should load without error', async () => {
const text = await page.evaluate(() => document.body.textContent);
expect(text).toContain('google');
});
},
timeout,
);
Finally, set jest.config.js to read from these files. (The jest-puppeteer preset does something like this under the hood.)
module.exports = {
globalSetup: './setup.js',
globalTeardown: './teardown.js',
testEnvironment: './puppeteer_environment.js',
};
"
If you prefer to terminate the test suite when one test fails you can use Jest's bail in the Jest config:
module.exports = {
globalSetup: './setup.js',
globalTeardown: './teardown.js',
testEnvironment: './puppeteer_environment.js',
bail: 1,
};
Links