How to share a Puppeteer instance across multiple Jest test files?

Issue

Currently, I’m transitioning from CasperJS to using Jest along with Puppeteer. Everything works well when the tests are all within a single file:

beforeAll(async () => {
  // obtain `page` and `browser` objects from puppeteer
});

describe('Unit Test A', () => {
   // test implementation
});

describe('Unit Test B', () => {
   // test implementation
});

afterAll(async () => {
 // terminate the browser
});

However, I would prefer not to keep all tests in one file, as it becomes complicated to manage and challenging to execute individual tests, such as only ‘Unit Test A’.

My Attempts

I reviewed Jest’s documentation and discovered the concept of using a setupFiles option. While this sounds ideal, it executes before each test file, and I would prefer to initialize Puppeteer only once to avoid lengthy setup times. My goal is to reuse the same browser instance across various test files without incurring the setup cost each time.

I considered this approach:

// setting up puppeteer
await require('./unitTestA')(page, browser, config);
await require('./unitTestB')(page, browser, config);
// cleanup operations

This method successfully maintains modularity and reuses the browser instance; however, it prevents me from running the tests independently.

Ultimately, I came across the option to establish a custom Jest testEnvironment. This is promising, yet the documentation is lacking, leaving me unsure whether a new environment instance is established for each test file or maintained throughout the Jest execution. Furthermore, the API stability is compromised as there is no dedicated setup method for Puppeteer initialization—all setup must occur in a non-async constructor.

Reason for Inquiry

As I’m still acclimating to Jest, I might be overlooking something fundamental. Before delving deeper, I wanted to ask for insights here.

To efficiently share a Puppeteer instance across multiple Jest test files, you can establish a custom testEnvironment. This allows you to manage the Puppeteer instance globally and ensure that the setup is minimized, thus reducing the overhead for each test run.

Here's a practical approach you can follow:

  1. Create a custom test environment file:
// custom-environment.js
const NodeEnvironment = require('jest-environment-node');
const puppeteer = require('puppeteer');

class PuppeteerEnvironment extends NodeEnvironment {
  async setup() {
    await super.setup();
    // Launch Puppeteer once
    if (!this.global.browser) {
      this.global.browser = await puppeteer.launch();
    }
  }

  async teardown() {
    // Close the browser only once
    if (this.global.browser) {
      await this.global.browser.close();
    }
    await super.teardown();
  }

  async handleTestEvent() {
    // Handle any test events if needed
  }
}

module.exports = PuppeteerEnvironment;
  1. Update your jest.config.js to point to your custom environment:
module.exports = {
  testEnvironment: '/custom-environment.js',
};

This configuration will ensure that the same browser instance is shared across all test files, minimizing setup times while allowing tests to run independently. This solution emphasizes efficiency, ensuring your tests are both modular and quick.

To further expand on the existing solution provided, another approach to effectively share a Puppeteer instance across multiple Jest test files is by utilizing a combination of the custom test environment and the globalSetup and globalTeardown Jest configuration options.

Here’s how you can implement this:

  1. First, create a global setup file to initialize your Puppeteer instance:
// jest-global-setup.js
const puppeteer = require('puppeteer');

module.exports = async () => {
  global.__BROWSER__ = await puppeteer.launch();
};
  1. Create a global teardown file to close the Puppeteer instance:
// jest-global-teardown.js
module.exports = async () => {
  await global.__BROWSER__.close();
};
  1. Set up a custom test environment file to reuse the Puppeteer instance:
// custom-environment.js
const NodeEnvironment = require('jest-environment-node');

class PuppeteerEnvironment extends NodeEnvironment {
  async setup() {
    await super.setup();
    // Assign the Puppeteer browser to the global object
    this.global.browser = global.__BROWSER__;
  }
}

module.exports = PuppeteerEnvironment;
  1. Update your jest.config.js file to integrate these changes:
module.exports = {
  globalSetup: './jest-global-setup.js',
  globalTeardown: './jest-global-teardown.js',
  testEnvironment: './custom-environment.js',
};

With this configuration, the Puppeteer instance is initialized once before the test suite starts and is closed when it finishes. Each test file will have access to the same browser instance through the custom environment, minimizing setup costs while maintaining your ability to run tests independently and efficiently.