Puppeteer launcher error results with undefined

Viewed 2710

After testing my project on windows, i've deployed it to Ubuntu-based VM and that is where the Puppeteer behavior baffles me. I did install all required dependencies, but i have a feeling that my error not necessarily derived by lack of dependencies and more about configuration issue.

Function fignature:

  async initPuppeteerBrowser() {
    if (
      this.puppeteerBrowser === null ||
      (await this.puppeteerBrowser.pages()).length === 0
    ) {
      this.puppeteerBrowser = await launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
    }

    return await this.puppeteerBrowser.newPage();
  }

Resulted with:

err: {
      "type": "TypeError",
      "message": "Cannot read properties of undefined (reading '_launcher')",
      "stack":
          TypeError: Cannot read properties of undefined (reading '_launcher')
              at launch (/root/dev/logoex-server/node_modules/puppeteer/lib/cjs/puppeteer/node/Puppeteer.js:125:21)
              at CorporationsScraper.initPuppeteerBrowser (/root/dev/logoex-server/dist/aid/scraper/corporations.scraper.js:33:66)
              at CorporationsScraper.corporationIterator (/root/dev/logoex-server/dist/aid/scraper/corporations.scraper.js:54:37)
              at processTicksAndRejections (node:internal/process/task_queues:96:5)
    }

I don't understand how do i even begin to invastigate this issue

4 Answers

Not sure, if this will help in your situation - I had Typescript, with compilerOptions lib: ["es2020"] and it threw this error. Changing it to lib: ["es2017"] solved this issue for me. I am not even sure, why it is affecting it like this. I guess, when you get a function by importing like import { fn } from "somewhere" but that function is somehow exported from class instance - it loses it's bound this.

Otherwise importing like this works

// Works
import Puppeteer, { Browser, PDFOptions } from "puppeteer";
browser = await Puppeteer.launch({});

// Doesn't work
import { launch, Browser, PDFOptions } from "puppeteer";
browser = await launch({});

The fix for me was to switch from using:

import { launch } from 'puppeteer';
const browser = await launch(...);
import Puppeteer from 'puppeteer';
const browser = await Puppeteer.launch(...);

Use like this.

async initPuppeteerBrowser() {
    if (
      this.puppeteerBrowser === null ||
      (await this.puppeteerBrowser.pages()).length === 0
    ) {
      this.puppeteerBrowser = await launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
    }

    return this.puppeteerBrowser.newPage();
  }

My environment

I'm running my Node.js app using commonjs module format.

  • puppeteer@13.5.1
  • typescript@4.5.4
  • node@14.18.3
  • My package.json appropriately does not specify "module": true.
  • Although I do use ES modules syntax in my TypeScript code, I set "module": "commonjs" to transpile down to commonjs.

My tsconfig.json looks like this:

{
  "compilerOptions": {
    "target": "ESNext",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "module": "commonjs",
    "moduleResolution": "Node",
    // ... other inconsequential options ...
  },
}

Failed approaches

I tested on the official TypeScript playground with my above tsconfig.json to analyse what each of these different approaches transpile to.

Method 1

Source

import { launch } from 'puppeteer';
launch();

Output

const puppeteer_1 = require("puppeteer");
(0, puppeteer_1.launch)();

Method 2

Source

import { default as puppeteer } from 'puppeteer';
puppeteer.launch();

Output

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const puppeteer_1 = __importDefault(require("puppeteer"));
puppeteer_1.default.launch();

Method 3

Source

import * as puppeteer from 'puppeteer';
puppeteer.launch();

Output

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const puppeteer = __importStar(require("puppeteer"));
puppeteer.launch();

Working approach

... so finally, I tried TypeScript's special import = require() format.

Source

import puppeteer = require("puppeteer");
puppeteer.launch();

Output

const puppeteer = require("puppeteer");
puppeteer.launch();

So this looks most similar to method 1. I'm not really sure what magic method 1 is performing with its 0 argument, but maybe it's erasing the this context or something and that's what's causing the module to be unable to find its launch property (which it might reference via this.launch)? Just speculation.

Related