背景
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
(看項目源碼,笑!)。