GitLab 是如何用 Headless Chrome 测试的

Posted DevOps时代

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GitLab 是如何用 Headless Chrome 测试的相关的知识,希望对你有一定的参考价值。

本文由DevOps时代高翻院翻译发布

译者:CK星空

原文链接:https://about.gitlab.com/2017/12/19/moving-to-headless-chrome/

下面的例子介绍了GitLab如何切换到Headless Chrome


GitLab最近从PhantomJS转变为Headless Chrome,用于前端测试和RSpec功能测试(ruby测试框架)。在这篇文章中,我们会详细介绍这个变化的原因,面临的挑战,以及解决方案。我们希望这能帮助其他人也能进行类似的转变。

我们现在有一个真实可靠的方法在现代浏览器中测试GitLab。当直接运行在Chrome的时候,这个方法已经提高写测试和调试的能力。还迫使我们去面对和清理一些在测试中的hacks(技巧)。

背景

PhantomJS(http://phantomjs.org/)作为GitLab测试框架的一部分已经接近有五年(https://gitlab.com/gitlab-org/gitlab-ce/commit/ba25b2dc84cc25e66d6fa1450fee39c9bac002c5)了。它是一个非常有用的工具,在选择不多的无头(无UI)环境下运行浏览器集成测试。但是,有一些缺陷:

PhantomJS(v2.1.1)的最新版本是用三年前的QtWebKit(https://trac.webkit.org/wiki/QtWebKit)(Webkit V538.1的一个分支版本,user-agent string)编译的。就像Safari 7 运行在 macOS 10.9。貌似集成了一个现代浏览器,但事实又不是那样。它有一个不同的javascript引擎,一个老掉牙的渲染引擎,有怪癖,还缺失一些功能。

现在,GibLab支持Firefox, Chrome, Safari, 和Microsoft Edge/IE中新和旧的主要版本(https://docs.gitlab.com/ce/install/requirements.html#supported-web-browsers)。PhantomJS的能力接近或低于我们最低的标准。很多现代浏览器的功能不支持,或者需要供应商前缀(-webkit-),还有补丁也不符合浏览器的要求。我们可以在测试环境中选择性地增加这些补丁,前缀和解决方法。但是这么做会添加技术债,引起混乱,并使测试环境不能代表真实的生产环境。(PhantomJS是生活在远古时代吗?)在大多数情况下我们选择忽略它或绕过他们(下面会提到(https://about.gitlab.com/2017/12/19/moving-to-headless-chrome/#trigger-method))。

下面两张图,一张是用PhantomJS渲染的页面,第二张是用Google Chrome渲染的:

GitLab 是如何用 Headless Chrome 测试的

GitLab 是如何用 Headless Chrome 测试的

可以看到PhantomJS的过滤标签是水平渲染的,侧边栏的图标分开渲染,全局搜索区域从导航栏溢出等问题。

尽管看上去很丑,但是大部分情况下我们仍然用它运行功能测试。只要页面的元素还能看得见和能点击,只是GitLab在浏览器中的确会出现某些罕见的情况。

Headless Chrome

今年四月份,新闻报道(https://news.ycombinator.com/item?id=14101233)称Chrome 59会支持原生跨平台的无头模式(headless mode)。Chrome之前是有可能在CI/CD的环境下运行虚拟帧缓冲器(https://gist.github.com/addyosmani/5336747)来模拟Headless Chrome的,但需要大量的内存,而且额外复杂。一个原生的无头浏览器会改变测试的风云变幻格局。(我没头,不怕砍头!)开发者竟然能在现代浏览器的无头环境下进行集成测试!

说时迟,那时快,PhantomJS的首席开发者Vitaly Slobodin就宣布新项目不会再发布:

This is the end - https://t.co/GVmimAyRB5#phantomjs 2.5 will not be released. Sorry, guys!

— Vitaly Slobodin (@Vitalliumm) April 13, 2017

显然,我们需要逐渐放弃PhantomJS。所以我们提交了一个issue(https://gitlab.com/gitlab-org/gitlab-ce/issues/30876),并下载了Chrome 59 beta 版本,开始了实验。

前端测试(Karma)

我们的前端测试套件是结合Karma测试运行器和Google Chrome配合使用,意外的简单(merge request(https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12036))。从2.1.0版本开始, Karma-chrome-launcher插件非常快速地支持无头模式,而且能大部分代替掉PhantomJS launcher。一旦我们重新构建了 CI/CD build images(https://gitlab.com/gitlab-org/gitlab-build-images/merge_requests/41) 并包含Google Chrome 59(减少一些繁琐的超时设置),成功了!我们还删除了一些相当丑陋且特殊的PhantomJS hacks,Jasmine需要内嵌浏览器的功能。

后端功能测试(RSpec + Capybara)

我们的功能测试是使用RSpec+Capybara(https://github.com/teamcapybara/capybara),进行完整的数据库,后端和前端交互的端到端集成测试。在转换到headless Chrome之前,我们使用的是Poltergeist(https://github.com/teampoltergeist/poltergeist),它是一个作为Capybara的PhantomJS驱动器(driver)。它会启动一个PhantomJS浏览器实例并指导它浏览,填写表格,并在网页上点击验证等所有应该有的行为。

从PhantomJS转变到Google Chrome需要替换Poltergeist为Selenium 和 ChromeDriver, 安装简单。在macOS中,你可以用命令brew install chromedriver,在Linux中是相似的。之后添加selenium-webdrivergem 到测试依赖和配置Capybara:

require 'selenium-webdriver' Capybara.register_driver :chrome do |app|  options = Selenium::WebDriver::Chrome::Options.new(    args: %w[headless disable-gpu no-sandbox]  )  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end Capybara.javascript_driver = :chrome

Google表示disable-gpu选项是必要的(https://developers.google.com/web/updates/2017/04/headless-chrome#cli),直到一些bugs被修复。在GitLabs的CI/CD 环境下,no-sandbox选项对于Chrome运行在Docker容器也是有必要的。Google提供了headless Chrome和Selenium一起配合使用的指引(https://developers.google.com/web/updates/2017/04/headless-chrome)。

在我们最终的实施过程中,我们有条件地添加了headless选项,除非你设置了CHROME_HEADLESS=false。这样很容易在调试或写测试的时候取消无头模式。看到自己写的测试在浏览器自动运行也是很有趣的。

export CHROME_HEADLESS=false bundle exec rspec spec/features/merge_requests/filter_merage_requests_spec.rb

Poltergeist和Selenium的区别

更换驱动的过程并不像更换前端测试套件那么简单。一旦我们改变了Capybara的设置,很多测试(脚本)会失效。产生这些差异的原因是Selenium/ChromeDriver使用了Capybara driver的API,而Poltergeist/PhantomJS却没有。以下是我们遇到的挑战:

1.JavasScript的模态框不再接受自动化测试

我们经常写JavaScriptconfirm("Are you sure you want to do X?");在执行破坏性操作(如删除分支或从组中删除用户)时单击事件。在Poltergeist下,一个.click动作会自动点击alert()confirm()的模态框。但在Selenium下,你需要输入accept_alert,accept_confirm,或者dismiss_confirm中的其中一个。例如:

# Before page.within('.some_selector') do  click_link 'Delete' end # After page.within('.some-selector') do  accept_confirm { click_link 'Delete' } end

2.Selenium的Element.visible?对于空元素返回false

如果你想测试一个空的div或者span,Selenium不会认为这个是”visible”的。在我们的功能测试中,如果设置Capybara.ignore_hidden_elements = true了,就不会有太严重的问题。在Poltergeist中使用find('.empty-div')是没问题的,需要用 visible: :any去选择元素。

# Before find('.empty-div') # After find('.empty-div', visible: :any) # or find('.empty-div', visible: false)

更多在Capybara and Hidden elements(https://makandracards.com/makandra/7617-change-how-capybara-sees-or-ignores-hidden-elements)。

3.Poltergeist的Element.trigger('click')在Selenium是不可用的

在Capybara中,当你使用find('.some-selector').click时,您所点击的元素必须是可见的,并且不能被任何重叠的元素所遮盖。链接不能被点击的情况有时会出现在Poltergeist/PhantomJS中,因为它的CSS对sans-prefixes支持很弱。例如下面这个例子:

这搜索表单的布局被破坏,实际上是在“Update all”按钮的顶部放置了一个不可见的元素,使其无法点击。Poltergeist提供了一个.trigger('click')的方法来解决这个问题。这个方法时触发一个DOM事件来模拟点击,而不是实际点击元素。这并不是一个好的做法,但是我们经常会遇到类似的问题,很多开发者都习惯这样解决。这会导致一些懒惰和草率的测试用例。例如,有些人可能会使用.trigger作为快捷方式,点击一个下拉菜单后面的链接,当一个正确的书面测试应该。点击某处关闭下拉,然后点击它后面的项目。

Selenium不支持.trigger方法。现在我们使用更准确的渲染引擎不会破坏布局,许多这些实例可以通过用.click替换.trigger('click')来解决。但是由于上面提到的一些不好的用法,并不一定能解决问题。

当然会有一些技巧来代替.trigger。你可以通过聚焦元素来模拟点击和按下”return”键,或者使用JavaScript去触发点击事件。我们决定花时间纠正这些错误的测试,这样正常的.click可以再次使用。最后,如果我们的测试是为了模拟一个真正的用户与页面交互,那我们应该做出真实的用户那样的行为。

# Before find('.obscured-link').trigger('click') # After # bad find('.obscured-link').send_keys(:return) # bad execute_script("document.querySelector('.obscured-link').click();") # good # do something to make link accessible, then find('.link').click

4.Element.send_keys只适用于可聚焦元素

在代码中,我们会用find('.boards-list').native.send_keys('i')来测试快捷键。事实证明,Chrome不会允许你将send_keys(关键字)发送给任何无法“聚焦”的元素,例如链接,表单元素,document body,或者是带有tab index的元素。

我们经历过的情况下,在页面元素触发send_keys在会起作用,因为事件处理器正在监听:

# Before find('.some-div').native.send_keys('i') # After find('body').native.send_keys('i')

5.Element.send_keys不支持non-BMP 字符(例如emoji)

在一些测试中,需要填写表情符号字符。在Poltergeist会这么做:

# Before find('#note-body').native.send_keys('@

以上是关于GitLab 是如何用 Headless Chrome 测试的的主要内容,如果未能解决你的问题,请参考以下文章

一棵单树是如何用 git 组织的?

这种类型的图像显示是如何用 scikit-learn 完成的?

Vue 是如何用 Rollup 打包的?

我是如何用 ThreadLocal 虐面试官的?

我是如何用leangoo协作工具提升工作效率的

我是如何用redis做实时订阅推送的