Puppeteer Run All Test Files With Single Browser Instance

Viewed 38

I have 30 different tests that's built on puppeteer and using jest. Each one has different flows, and I want to reduce system & network load by running each test only one single browser with creating new tabs instead of creating new browser instance for each test files.

There are few problems to do that:

  • How to share one browser instance between test files? Because it's async process and there's no async export in Node.js

    const browser = await puppeteer.launch({ headless: false });

  • How to understand all test suits are done and it's time to close browser? I can close pages when the tests completed but I can't know which test will be the last one or what if it fails?

PS (to editors): This is not a duplicate thread, the similar thread was opened for C# and environment is completely different. Puppeteer - Run 1 browser instance across multiple tests

1 Answers

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:

  1. launch & file the websocket endpoint of puppeteer with Global Setup
  2. connect to puppeteer from each Test Environment
  3. 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

Related