Electron 中的 Puppeteer:错误:传递的函数不能很好地序列化

2024-02-13

尝试为 Puppeteer 项目设计一个 GUI。 我想过使用 Electron,但遇到错误:

Error: Passed function is not well-serializable!

当运行 Puppeteer 函数时,例如:

await page.waitForSelector('.modal', { visible: true });

我在处理时找到了一种正确的序列化方法page.evaluate()但如果出现以下情况该怎么办page.waitForSelector()?

是否有解决办法可以在需要时正确序列化 Puppeter 的 API 函数?

EDIT

我决定重写

await page.waitForSelector('.modal', { visible: true });

使用 page.evaluate,代码如下:

// first recreate waitForSelector
  const awaitSelector = async (selector) => {
    return await new Promise(resolve => {
      const selectorInterval = setInterval(() => {
        if ($(selector).is(':visible')) {
          console.log(`${selector} visible`);
          resolve();
          clearInterval(selectorInterval);
        };
      }, 1000);
    });
  }

然后使用 page.evaluate() 调用该函数:

// remember to pass over selector's name, in this case it is ".modal"
await page.evaluate('(' + awaitSelector.toString() + ')(".modal");');

首先上下文: 一般来说,你不能从浏览器环境运行puppeteer,它只能在nodejs中运行。 Electron提供了2个进程:renderer和main。每当你想使用节点时,你必须在主节点中进行。

关于两个进程之间的通信,您可以在文档中阅读,有很多处理方法。据我所知,最佳实践是在预加载中声明它并使用 ipc 桥。其他解决方案违反了 contextIsolation 规则。

我在一个问题和另一个问题之间徘徊:比如不可序列化的函数、未定义的需求等等。

最后我从头开始重写了所有内容,它的工作原理是我的解决方案:

main.js

const { app, BrowserWindow } = require('electron')
const path = require('path')
const { ipcMain } = require('electron');
const puppeteer = require("puppeteer");

function createWindow() {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: true,
    },
  })
  ipcMain.handle('ping', async () => {
    await checkPup()
  })

  async function checkPup() {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://example.com');
    page
      .waitForSelector('h1', { visible: true })
      .then(() => {
        console.log('got it')
      });
    const [button] = await page.$x("//button[contains(., 'Create account')]");
    if (button) {
      console.log('button: ', button)
      await button.click();
      await page.screenshot({ path: 'tinder.png' });

      const [button] = await page.$x("//button[contains(., 'Create account')]");
      if (button) {
        console.log('button: ', button)
        await button.click();
        await page.screenshot({ path: 'tinder.png' });
      }
    }
    await browser.close();
  }
  // and load the index.html of the app.
  mainWindow.loadFile('index.html')
  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
  createWindow()

  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})


// Attach listener in the main process with the given ID
ipcMain.on('request-mainprocess-action', (event, arg) => {
  // Displays the object sent from the renderer process:
  //{
  //    message: "Hi",
  //    someData: "Let's go"
  //}
  console.log(
    arg
  );
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

预加载.js

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const type of ['chrome', 'node', 'electron']) {
    replaceText(`${type}-version`, process.versions[type])
  }
})

contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron,
  ping: () => ipcRenderer.invoke('ping'),
  // we can also expose variables, not just functions
})

渲染器.js

const information = document.getElementById('info')
const btn = document.getElementById('btn')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`

btn.addEventListener('click', () => {
    console.log('habad!!!!!')
    func()
})


const func = async () => {
    const response = await window.versions.ping()
    information.innerText = response;
    console.log(response) // prints out 'pong'
  }
  

抱歉有点混乱,我希望它能帮助某人找到其他问题的解决方案

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Electron 中的 Puppeteer:错误:传递的函数不能很好地序列化 的相关文章

随机推荐