齐磊
ThoughtWorks 资深质量保证咨询师,超过 7 年的软件开发与测试经验,擅长测试开发,软件开发流程的自动化,目前致力于开阔移动端测试开发领域。
自 2017 年中以来,Chrome 用户可以选择以 headless 模式运行浏览器。此功能非常适合运行前端浏览器测试,而无需在屏幕上显示操作过程。在此之前,这主要是 PhantomJS 的领地,但 Headless Chrome 正在迅速取代这个由 JavaScript 驱动的 WebKit 方法。Headless Chrome 浏览器的测试运行速度要快得多,而且行为上更像一个真正的浏览器,虽然我们的团队发现它比 PhantomJS 使用更多的内存。有了这些优势,用于前端测试的 Headless Chrome 很可能成为事实上的标准。
随着 Google 在 Chrome 59 版本放出了 headless 模式,Ariya Hidayat 决定放弃对 Phantom.js 的维护,这也标示着 Phantom.js 统治 fully functional headless browser 的时代将被 chrome-headless 代替。
Headless Browser
也许很多人对无头浏览器还是很陌生,我们先来看看维基百科的解释:
A headless browser is a web browser without a graphical user interface.
Headless browsers provide automated control of a web page in an environment similar to popular web browsers, but are executed via a command-line interface or using network communication.
对,就是没有页面的浏览器。多用于测试 web、截图、图像对比、测试前端代码、爬虫(虽然很慢)、监控网站性能等。
为什么要使用 headless 测试?
headless broswer 可以给测试带来显著好处:
headless browser 有什么缺陷?
以 phantomjs 为例
那么 Headless Chrome 与上面提到 fully functional headless browser 又有什么不同呢?
什么是 Headless Chrome?
Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有 Chrome 支持的特性,在命令行中运行你的脚本。相比于其他浏览器,Headless Chrome 能够更加便捷的运行 web 自动化测试、编写爬虫、截取图等功能。
有的人肯定会问:看起来它的作用和 phantomjs 没什么具体的差别?
对,是的,Headless Chrome 发布就是来代替 phantomjs。
我们凭什么换用 Headless Chrome?
能带给 QA 以及项目什么好处?
前端测试改进
以目前的项目来说,之前的前端单元测试以及组件测试是用 karma 在 phantomjs 运行的,非常不稳定,在远端 CI 上运行时经常会莫名其妙的挂掉,也找不出来具体的原因,自从 Headless Chrome 推出后,我们将 phantomjs 切换成 Headless Chrome,再也没有出现过异常情况,切换也非常简单,只需要把 karma.conf.js 文件中的配置改下就 OK 了。如下
customLaunchers: { myChrome: { base: 'ChromeHeadless', flags: ['--no-sandbox', '--disable-gpu', '--remote-debugging-port=9222'] } },
browsers: ['myChrome'],
UI 功能测试改进
原因一,Chrome-headless 能够完全像真实浏览器一样完成用户所有操作,再也不用担心跑测试时,浏览器受到干扰,造成测试失败
原因二,之前如果我们像要在 CI 上运行 UI 自动化测试,非常麻烦。必须使用 Xvfb 帮助才能在无界面的 Linux 上运行 UI 自动化测试。(Xvfb 是一个实现了 X11 显示服务协议的显示服务器。 不同于其他显示服务器,Xvfb 在内存中执行所有的图形操作,不需要借助任何显示设备。)现在也只需要在 webdriver 启动时,设置一下 chrome option 即可,以 capybara 为例:
Capybara.register_driver :selenium_chrome do app
Capybara::Selenium::Driver.new (app, browser: :chrome,
desired_capabilities: {
"chromeOptions" => {
"args" => [ "--incognito",
"--allow-running-insecure-content",
"--headless",
"--disable-gpu"
]}
})
end
无缝切换,只需更改下配置,就可以提高运行速度与稳定性,何乐而不为。
Google 终极大招
Google 最近放出了终极大招——Puppeteer(Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome.)
类似于 webdriver 的高级别的 api,去帮助我们通过 DevTools 协议控制无界面 Chrome。
在 puppteteer 之前,我们要控制 chrome headless 需要使用 chrome-remote-interface 来实现,但是它比 Puppeteer API 更接近低层次实现,无论是阅读还是编写都要比 puppteteer 更复杂。也没有具体的 dom 操作,尤其是我们要模拟一下 click 事件,input 事件等,就显得力不从心了。
我们用同样 2 段代码来对比一下 2 个库的区别。
首先来看看 chrome-remote-interface
const chromeLauncher = require ('chrome-launcher');
const CDP = require ('chrome-remote-interface');
const fs = require ('fs');
function launchChrome (headless=true) {
return chromeLauncher.launch ({
// port: 9222, // Uncomment to force a specific port of your choice.
chromeFlags: [
'--window-size=412,732',
'--disable-gpu',
headless ? '--headless' : ''
]
});
}
(async function () {
const chrome = await launchChrome ();
const protocol = await CDP ({port: chrome.port});
const {Page, Runtime} = protocol;
await Promise.all ([Page.enable (), Runtime.enable ()]);
Page.navigate ({url: 'https://www.github.com/'});
await Page.loadEventFired (
console.log ("start")
);
const {data} = await Page.captureScreenshot ();
fs.writeFileSync ('example.png', Buffer.from (data, 'base64'));
// Wait for window.onload before doing stuff.
protocol.close ();
chrome.kill (); // Kill Chrome.
再来看看 puppeteer
const puppeteer = require ('puppeteer');
(async () => {
const browser = await puppeteer.launch ();
const page = await browser.newPage ();
await page.goto ('https://www.github.com');
await page.screenshot ({path: 'example.png'});
await browser.close ();
})();
对,就是这么简短明了,更接近自然语言。没有 callback,几行代码就能搞定我们所需的一切。
总结
目前 Headless Chrome 仍然存在一些问题,还需要不断完善,我们应该拥抱变化,适应它,让它给我们的工作带来更多帮助。