JavaScript Testing

Debug 3 Jest/Puppeteer Import Errors Fast (2025 Fix)

Stuck on Jest and Puppeteer import errors? This 2025 guide helps you fix 3 common issues fast, from ES Modules vs. CommonJS to environment conflicts.

A

Alex Ivanov

Senior Test Automation Engineer specializing in modern JavaScript testing frameworks.

6 min read1 views

You’ve done it. You’ve crafted the perfect end-to-end test script. It’s elegant, it covers the critical user journey, and it’s ready to stand guard against regressions. You type npm test into your terminal, lean back, and wait for the glorious green “PASS.”

Instead, you’re greeted with a splash of angry red text. SyntaxError. TypeError. Something about an unexpected token. Your flow is broken, and now you’re tumbling down a rabbit hole of Stack Overflow tabs, GitHub issues, and configuration files. If this sounds familiar, you’re in the right place.

The Powerful (But Tricky) Duo: Jest & Puppeteer

Jest and Puppeteer are a dream team for automated browser testing. Jest provides a world-class testing framework with a fantastic developer experience, while Puppeteer offers powerful, low-level control over a headless Chrome or Chromium browser. But getting them to shake hands properly can be a major headache, especially due to the evolution of JavaScript modules (ES Modules vs. CommonJS) and the specific environments Jest creates.

This guide is your 2025 shortcut. We’ll cut through the noise and diagnose three of the most common import and setup errors you'll encounter, providing clear, copy-pasteable solutions to get you back to writing tests that matter.

Error #1: The ESM vs. CommonJS Clash

This is, without a doubt, the most frequent offender. You write modern JavaScript using import and export, but Jest throws a fit.

The Symptom: SyntaxError: Cannot use import statement outside a module


  Jest encountered an unexpected token

  This is often caused by a set of reasons...

  Details:

  /path/to/your/project/tests/example.test.js:1
  import puppeteer from 'puppeteer';
  ^^^^^^

  SyntaxError: Cannot use import statement outside a module
  

The Cause: A Tale of Two Module Systems

Historically, Node.js used the CommonJS (CJS) module system, with require() and module.exports. Jest was built on this system and defaults to it. However, the official standard for JavaScript is now ECMAScript Modules (ESM), which uses the import and export syntax you’re likely using in your source code.

The error occurs because, by default, Jest tries to run your ESM-style test file in a CJS environment, and it doesn't understand the import keyword.

The Fix: Teach Jest to Speak ESM

You have two excellent, modern options to resolve this. Pick the one that best fits your project's setup.

Solution A: The Babel Transpilation Route (Most Common)

Babel is a JavaScript compiler that can convert your modern ESM code into a CJS format that Jest understands on the fly. It's the most robust and widely-used solution.

Step 1: Install dependencies.

npm install --save-dev babel-jest @babel/core @babel/preset-env

Step 2: Create a Babel configuration file.

In your project root, create a babel.config.js file:

// babel.config.js
module.exports = {
  presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};

This tells Babel to compile your code for the current version of Node.js you are running.

Step 3: Let Jest know.

Jest will automatically detect and use your babel.config.js file. In most cases, you don't even need to update your jest.config.js! The presence of `babel-jest` is enough. Running your test command again should now work seamlessly.

Solution B: The Native Node.js ESM Route (Simpler, Modern)

If you prefer to avoid a build step, you can instruct Node.js to treat your files as native ES Modules. This is a great, lightweight option for new projects.

Step 1: Signal ESM in package.json.

Add the following top-level key to your package.json:

// package.json
{
  ...
  "type": "module",
  ...
}

Step 2: Configure Jest for ESM.

Update your jest.config.js to use a preset that handles ESM correctly and set the test environment.

// jest.config.js
export default {
  // Use this preset to handle ESM
  preset: 'ts-jest/presets/default-esm',
  // Tell Jest to use the Node.js environment and transform modules
  testEnvironment: 'node',
  transform: {},
};

Note: You might need to install ts-jest even for JS projects for its ESM handling capabilities: npm i -D ts-jest.

Comparison of ESM handling methods in Jest
Approach Pros Cons
Babel Transpilation Extremely compatible; handles other syntax transformations; mature ecosystem. Adds a build/transpilation step; more dev dependencies.
Native Node.js ESM No build step; faster; aligns with the future of Node.js. Can have edge cases with older CJS-only libraries.

Error #2: The "puppeteer.launch is not a function" Puzzle

You've solved the module system issue, but now a new error appears when you actually try to use Puppeteer.

The Symptom: TypeError: puppeteer.launch is not a function


  TypeError: puppeteer.launch is not a function

      at Object.<anonymous> (tests/example.test.js:5:23)
      at ...
  

The Cause: Default vs. Named Imports

This error is almost always caused by an incorrect import statement. You might be trying to destructure a named export called launch when Puppeteer actually provides a default export that contains the `launch` method.

Let's look at the code that causes this problem:

The WRONG Way:

// This is incorrect!
import { launch } from 'puppeteer';

describe('My Test', () => {
  it('should work', async () => {
    // Here, `launch` is undefined because it's not a named export
    const browser = await launch(); 
    // ...
  });
});

The library doesn't export a variable named `launch`. It exports a main object, and that object has a `launch` method on it.

The Fix: Use the Default Import

The solution is simple: import the entire default object and give it a name (conventionally, puppeteer).

The RIGHT Way:

// This is correct!
import puppeteer from 'puppeteer';

describe('My Test', () => {
  it('should work', async () => {
    // Now we call the .launch() method on the imported object
    const browser = await puppeteer.launch();
    // ...
    await browser.close();
  });
});

By changing import { launch } from 'puppeteer' to import puppeteer from 'puppeteer', you gain access to the main object and can correctly call puppeteer.launch().

Error #3: The Ghost in the Machine

Your tests are finally running and passing! But when the test suite finishes, Jest hangs for a minute and then prints a warning.

The Symptom: Jest has detected the following 1 open handle potentially keeping Jest from exiting


  Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  ●  TCPWRAP

       6 | 
       7 | describe('Google Homepage', () => {
    >  8 |   it('should have the correct title', async () => {
         |   ^
       ...
  

The Cause: Unclosed Browser Instance

This isn't a syntax error, but a resource management error. When you call puppeteer.launch(), you open a connection to a real browser process. This process is an “open handle.” If you don't explicitly tell Puppeteer to close the browser when your tests are done, the process keeps running in the background, and Jest correctly warns you about it.

A common mistake is to launch and close the browser inside a single it() block, which is inefficient, or to forget to close it at all.

The Fix: Use beforeAll and afterAll

Jest provides helper hooks to run code before and after your tests. The best practice is to launch one browser instance for all tests in a file (using beforeAll) and close it once all tests are complete (using afterAll).

Here’s how to structure your test file correctly:

import puppeteer from 'puppeteer';

// Declare variables in the higher scope
let browser;
let page;

describe('Google Homepage Test Suite', () => {
  // Launch the browser once before all tests
  beforeAll(async () => {
    browser = await puppeteer.launch();
    page = await browser.newPage();
    // Set a longer timeout for async operations
    jest.setTimeout(30000);
  });

  // Close the browser once all tests are finished
  afterAll(async () => {
    if (browser) {
      await browser.close();
    }
  });

  it('should navigate to Google and have the correct title', async () => {
    await page.goto('https://google.com');
    const title = await page.title();
    expect(title).toBe('Google');
  });

  it('should show search results', async () => {
    await page.type('input[name=q]', 'puppeteer');
    await page.keyboard.press('Enter');
    await page.waitForSelector('#rso'); // Wait for search results container
    const results = await page.$$('#rso .g'); // Get all result divs
    expect(results.length).toBeGreaterThan(0);
  });
});

This structure is not only efficient but also guarantees that browser.close() is called, resolving the open handle warning and allowing Jest to exit cleanly and quickly.

Conclusion: From Frustration to Flow

Navigating the setup for Jest and Puppeteer can feel like a chore, but once you understand these three common pitfalls, you're well on your way to a smooth and productive testing workflow.

Let's recap the fixes:

  1. ESM vs. CJS Errors: Configure your project to handle modern JavaScript modules, either by using Babel for transpilation or by enabling Node's native ESM support.
  2. Incorrect Import Errors: Always use the default import for Puppeteer: import puppeteer from 'puppeteer';.
  3. Open Handle Warnings: Manage your browser lifecycle properly with Jest's beforeAll and afterAll hooks to ensure the browser instance is always closed.

By applying these fixes, you can spend less time debugging configurations and more time building reliable, automated tests that give you confidence in your applications. Happy testing!