使用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 aswebSocketDebuggerUrl
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'
}
})
结语
这下,终于可以真的一键启动本地开发环境了,开心!
另,这个功能实现过程中踩的几个坑:
- 考虑如何将Puppeteer 添加到已有的 Chromium 实例,结果:很难做,还是打开一个新的浏览器实例来的好。
- 启动的浏览器窗口没有最大化,也没有最重要的
vue-devtools
插件,结果:使用启动参数:args: ['--start-maximized', '--disable-extensions-expect=D:\\vue-devtools']
- npm启动webpack服务不能使用 npm 的
post
钩子,结果:使用spawn
语法替代。 - 如何确认webpack本地服务执行结束的时机,结果:监听
data
事件,正则匹配到Compiled successfully
(看项目源码,笑!)。