使用puppeteer一键启动本地开发环境

背景

puppeteer是一个通过Devtools协议来提供操控chrome/chromium浏览器的高阶API的NodeJS库

我负责的一个项目的启动本地开发环境是这样的:使用npm run dev指令运行webpack-dev-server服务。暴露出访问地址:http://localhost:1314。然后登陆部署到内部项目环境下的应用。获取到账户cookie的entrance键值对。然后访问本地的开发环境地址,通过document.cookie语法写入相应的cookie的entrance键值对。刷新本地开发页面。此时才可正常访问相应的服务端接口。

看到puppeteer库的功能,尝试以上过程通过puppeteer库的自动化功能实现,以达到一键启动本地开发环境的目的。

过程

我的打算是通过puppeteer在当前的chrome浏览器中进行相关的操作。puppeteer.connect(options)将 Puppeteer 添加到已有的 Chromium 实例。但是options参数中的browser.wsEndpoint字段表示一个浏览器 websocket 端点链接,该链接是由browser.wsEndpoint()获取。

正常不能获取到正常运行的浏览器的wsEndpoint

How do I access the browser target?
The endpoint is exposed as webSocketDebuggerUrl in /json/version. Note the browser in the URL, rather than page. If Chrome was launched with --remote-debugging-port=0 and chose an open port, the browser endpoint is written to both stderr and the DevToolsActivePort file in browser profile folder.

HTTP Endpoints: If started with a remote-debugging-port, these HTTP endpoints are available on the same port.

因此,我决定通过puppeteer.launch语法开启一个新的浏览器实例,然后在这个新的浏览器上进行自动化操作。相关的启动参数如下:

const browser = puppeteer.launch({
    executablePath: 'C:\\chrome.exe',
    headless: false, // 关闭无头模式
    ignoreHTTPSErrors: false, // 在导航期间忽略 HTTPS 错误
    args: ['--start-maximized', '--disable-extensions-expect=D:\\vue-devtools'] // 最大化启动,开启vue-devtools插件
    defaultViewport: { // 为每个页面设置一个默认视口大小
        width: 1920,
        height: 1080
    }
})

后续是将背景节中的描述的操作使用puppeteer实现出来,代码如下:

// run-browser.js
const main = async () => {
    // ... 启动浏览器实例的代码
    const page = await browser.newPage()
    await page.goto('http://inner.develop.net/index.html/#home')

    await page.click('.to-login-btn') // 打开登陆弹窗
    await page.type('input.phone', '18888888888', { delay: 20 }) // 输入账户
    await page.type('input.pwd', '123456', { delay: 20 }) // 输入密码
    await page.click('button.login') // 点击登陆按钮
    await page.waitForNavigation({ waitUntil: 'networkidle0' }) // 等待页面导航结束

    const entrance = await page.evaluate(() => document.cookie.split(';')[0].split('=')[1]) // 获取页面cookie中的entrance值

    const devPage = await browser.newPage()
    await devPage.goto('http://localhost:1314') // 导航到开发页面
    await devPage.waitForNavigation({ waitUntil: 'networkidle0' })
    // 设置开发页面的cookie
    await devPage.evaluate((entranceValue) => {
        document.cookie = `entrance=${entrance}`
    }, entrance)
    // 刷新页面
    await devPage.reload()
}

使用node run-browser.js指令运行,效果很好。但又出现一个问题:我开启本地开发环境,先要运行npm run dev启动webpack-dev-server服务,还要运行npm run browser执行run-browser脚本文件,并没有之前说好的一键启动呀。

这时,我的做法是利用npm的脚本钩子特性,有如下代码:

{
    "scripts": {
        "dev": "webpack",
        "postdev": "node run-browser.js"
    }
}

结果并没有得到预期结果,细思原因,猜测npm run dev指令在启动webpack本地服务后,并没有释放当前的node进程执行权限,因为它需要时刻监听项目文件以及时编译文件。而运行postdev拿不到node执行环境,所以才会毫无反应。

我想到node有一个语法spawn,可以在当前node进程中生成一个子进程。于是,我在node执行完npm run dev指令后,再使用spawn语法手动执行node browser指令。现在的问题是我如何确认webpack本地服务执行结束的时机。

最终我在webpack-dev-servergit仓库中找到了解决方案:

// webpack-dev-server/test/cli/cli.test.js
    cp.stdout.on('data', (data) => {
      const bits = data.toString();
      const portMatch = /Project is running at http:\/\/localhost:(\d*)\//.exec(
        bits
      );

      if (portMatch) {
        runtime.cp.port = portMatch[1];
      }

      if (/Compiled successfully/.test(bits)) {
        expect(cp.pid !== 0).toBe(true);
        cp.kill('SIGINT');
      }
    });

我只要在初次监听到data事件中正则匹配到Compiled successfully,则说明webpack本地服务开启成功,所以最后的脚本代码是这样的:

const { spawn } = require('child_process')

const npmDev = spawn('npm', ['run', 'dev']) // 启动本地webpack服务

npmDev.on('data', (data) => {
    const bits = data.toString()

    if (/Compiled successfully/.test(bits) && process.env.WEBPACK_DEV_STATUS !== 'initialed') {
        spawn('npm', ['run', 'browser']) // 运行run-browser.js脚本
        // 设置WEBPACK_DEV_STATUS状态,以避免在修改项目文件后,webpack编译成功后触发`npm run browser`指令
        process.env.WEBPACK_DEV_STATUS = 'initialed'
    }
})

结语

这下,终于可以真的一键启动本地开发环境了,开心!

另,这个功能实现过程中踩的几个坑:

  1. 考虑如何将Puppeteer 添加到已有的 Chromium 实例,结果:很难做,还是打开一个新的浏览器实例来的好。
  2. 启动的浏览器窗口没有最大化,也没有最重要的vue-devtools插件,结果:使用启动参数:args: ['--start-maximized', '--disable-extensions-expect=D:\\vue-devtools']
  3. npm启动webpack服务不能使用 npmpost钩子,结果:使用spawn语法替代。
  4. 如何确认webpack本地服务执行结束的时机,结果:监听data事件,正则匹配到Compiled successfully(看项目源码,笑!)。

参考链接

  1. puppeteer 中文文档
  2. browser endpoint
  3. child_process 模块中文文档
posted @ 2020-03-29 23:07  西河  阅读(95)  评论(0编辑  收藏  举报