技术雷达快讯:自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代替。
也许很多人对无头浏览器还是很陌生,我们先来看看维基百科的解释:
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测试?
以phantomjs为例
那么Headless Chrome与上面提到fully functional headless browser又有什么不同呢?
什么是Headless Chrome?
Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有Chrome支持的特性,在命令行中运行你的脚本。相比于其他浏览器,Headless Chrome 能够更加便捷的运行web自动化测试、编写爬虫、截取图等功能。
有的人肯定会问:看起来它的作用和phantomjs没什么具体的差别?
对,是的,Headless Chrome 发布就是来代替phantomjs。
(http://t.cn/RQMkEuK)
能带给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'],
原因一,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仍然存在一些问题,还需要不断完善,我们应该拥抱变化,适应它,让它给我们的工作带来更多帮助。