pouya
pouya

Reputation: 3776

How to use puppeteer-core with electron?

I got this code from another Stackoverflow Question:

import electron from "electron";
import puppeteer from "puppeteer-core";

const delay = (ms: number) =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, ms);
  });

(async () => {
  try {
    const app = await puppeteer.launch({
      executablePath: electron,
      args: ["."],
      headless: false,
    });
    const pages = await app.pages();
    const [page] = pages;

    await page.setViewport({ width: 1200, height: 700 });
    await delay(5000);
    const image = await page.screenshot();
    console.log(image);
    await page.close();
    await delay(2000);
    await app.close();
  } catch (error) {
    console.error(error);
  }
})();

Typescript compiler complains about executablePath property of launch method options object cause it needs to be of type string and not Electron. So how to pass electron chromium executable path to puppeteer?

Upvotes: 12

Views: 24788

Answers (4)

milad nazari
milad nazari

Reputation: 403

According to this document puppeteer-in-electron it's works properly. but note that for work with ipcMain you should initialize app before call main function.

main.js

// main.js
const {BrowserWindow, app} = require("electron");
const pie = require("puppeteer-in-electron")
const puppeteer = require("puppeteer-core");

app.whenReady().then(() => {
    ipcMain.handle('get-url', getUrl)
    createWindow()

    ///
})

async function initialize(){
    await pie.initialize(app);
}
initialize();

async function getUrl(){
  const browser = await pie.connect(app, puppeteer);
 
  const window = new BrowserWindow();
  const url = "https://example.com/";
  await window.loadURL(url);
 
  const page = await pie.getPage(browser, window);
  console.log(page.url());
  window.destroy();
  return page.url();
};

preload.js

const { contextBridge,ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    getUrl: () => ipcRenderer.invoke('get-url')
})

renderer (vue3)

<script setup>
import { ref } from 'vue';

const res = ref();

async function get(){
    const result = await window.electronAPI.getUrl()
    res.value = result
    console.log(result);
}
</script>

my packages version

  • "puppeteer-core": "^19.7.1"
  • "puppeteer-in-electron": "^3.0.5"
  • "electron": "^23.1.0"

Upvotes: 0

itachi
itachi

Reputation: 41

the toppest answer dones't work for me use electron 11 and puppeteer-core 8. but start puppeteer in main process other then in the renderer process works for me.you can use ipcMain and ipcRenderer to comunicate each other.the code below

main.ts(main process code)

import { app, BrowserWindow, ipcMain } from 'electron';
import puppeteer from 'puppeteer-core';
async function newGrabBrowser({ url }) {
  const browser = await puppeteer.launch({
    headless: false,
    executablePath:
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
  });
  const page = await browser.newPage();
  page.goto(url);
}
ipcMain.on('grab', (event, props) => {
  newGrabBrowser(JSON.parse(props));
});

home.ts (renderer process code)

const { ipcRenderer } = require('electron');
ipcRenderer.send('grab',JSON.stringify({url: 'https://www.google.com'}));

Upvotes: 4

PeterDanis
PeterDanis

Reputation: 9336

There is also another option, which works for electron 5.x.y and up (currently up to 7.x.y, I did not test it on 8.x.y beta yet):

// const assert = require("assert");
const electron = require("electron");
const kill = require("tree-kill");
const puppeteer = require("puppeteer-core");
const { spawn } = require("child_process");

let pid;

const run = async () => {
  const port = 9200; // Debugging port
  const startTime = Date.now();
  const timeout = 20000; // Timeout in miliseconds
  let app;

  // Start Electron with custom debugging port
  pid = spawn(electron, [".", `--remote-debugging-port=${port}`], {
    shell: true
  }).pid;

  // Wait for Puppeteer to connect
  while (!app) {
    try {
      app = await puppeteer.connect({
        browserURL: `http://localhost:${port}`,
        defaultViewport: { width: 1000, height: 600 } // Optional I think
      });
    } catch (error) {
      if (Date.now() > startTime + timeout) {
        throw error;
      }
    }
  }

  // Do something, e.g.:
  // const [page] = await app.pages();
  // await page.waitForSelector("#someid")// 
  // const text = await page.$eval("#someid", element => element.innerText);
  // assert(text === "Your expected text");
  // await page.close();
};

run()
  .then(() => {
    // Do something
  })
  .catch(error => {
    // Do something
    kill(pid, () => {
      process.exit(1);
    });
  });

Getting the pid and using kill is optional. For running the script on some CI platform it does not matter, but for local environment you would have to close the electron app manually after each failed try.

Please see this sample repo.

Upvotes: 0

Md. Abu Taher
Md. Abu Taher

Reputation: 18866

You cannot use electron executable with Puppeteer directly without some workarounds and flag changes. They have tons of differences in the API. Specially electron doesn't have all of the chrome.* API which is needed for chromium browser to work properly, many flags still doesn't have proper replacements such as the headless flag.

Below you will see two ways to do it. However you need to make sure of two points,

  • Make sure the puppeteer is connected before the app is initiated.
  • Make sure you get the correct version puppeteer or puppeteer-core for the version of Chrome that is running in Electron!

Use puppeteer-in-electron

There are lots of workarounds, but most recently there is a puppeteer-in-electron package which allows you to run puppeteer within electron app using the electron.

First, install the dependencies,

npm install puppeteer-in-electron puppeteer-core electron

Then run it.

import {BrowserWindow, app} from "electron";
import pie from "puppeteer-in-electron";
import puppeteer from "puppeteer-core";

const main = async () => {
  const browser = await pie.connect(app, puppeteer);

  const window = new BrowserWindow();
  const url = "https://example.com/";
  await window.loadURL(url);

  const page = await pie.getPage(browser, window);
  console.log(page.url());
  window.destroy();
};

main();

Get the debugging port and connect to it

The another way is to get the remote-debugging-port of the electron app and connect to it. This solution is shared by trusktr on electron forum.

import {app, BrowserWindow, ...} from "electron"
import fetch from 'node-fetch'

import * as puppeteer from 'puppeteer'

app.commandLine.appendSwitch('remote-debugging-port', '8315')

async function test() {
    const response = await fetch(`http://localhost:8315/json/versions/list?t=${Math.random()}`)
    const debugEndpoints = await response.json()

    let webSocketDebuggerUrl = debugEndpoints['webSocketDebuggerUrl ']

    const browser = await puppeteer.connect({
        browserWSEndpoint: webSocketDebuggerUrl
    })

    // use puppeteer APIs now!
}

// ... make your window, etc, the usual, and then: ...

  // wait for the window to open/load, then connect Puppeteer to it:
  mainWindow.webContents.on("did-finish-load", () => { 
    test()
  })

Both solution above uses webSocketDebuggerUrl to resolve the issue.

Extra

Adding this note because most people uses electron to bundle the app.

If you want to build the puppeteer-core and puppeteer-in-electron, you need to use hazardous and electron-builder to make sure get-port-cli works.

Add hazardous on top of main.js

// main.js
require ('hazardous');

Make sure the get-port-cli script is unpacked, add the following on package.json

"build": {
  "asarUnpack": "node_modules/get-port-cli"
}

Result after building:

Upvotes: 19

Related Questions