以下是10up首席前端工程师Jon Bellah的来宾帖子。 乔恩(Jon)向我们提供了有关视觉回归测试这一概念的写作,视觉回归测试是CSS测试的一种形式(即确保您不会偶然搞砸自己的网站)。 我认为用例特别有趣:重新构造CSS(转换为Sass,拆分文件等),并确保在此过程中不进行任何回归。 在这里,乔恩(Jon)将通过巧妙的解决方法来解决视觉测试的所有挑战和挑战(例如更改内容会改变视觉结果)。
从新客户那里继承代码库是我在代理机构工作时面临的最常见,最困难的挑战之一。 在某些情况下,由于前一个代理商没有生产高质量的作品,因此客户正在过渡到新代理商。 在几乎每种情况下,前一家代理商都没有像我那样做。
我经常发现自己处于这种情况。 并非每个客户都有从头开始重建的需求,欲望或预算。
最近,我的团队从一个新客户端继承了一个代码库,其任务是进行一些清理并Swift过渡到构建新功能。 在深入研究时,我们认为可以通过将他们的样式表转换为Sass来改善他们的代码库,并为更简单的前进道路做好准备。
尽管我们当然可以重命名文件并将它们包含在一个预先编译的样式表中(而不进行任何清理),但是我们认为通过重新设计它们的样式可以收获很多。 这样做会花费更多的前期费用,但我们认为这最终将为他们节省大量的时间和金钱。 最重要的是,它将使我们能够更有信心地更快地进行迭代。
过去,我认为这样的冒险是相当高的风险 。 毕竟,CSS中的C确实代表了级联……顺序绝对重要。 重组少数样式表意味着更改事物显示的顺序,这自然会带来破坏事物的高风险。
结果,它总是要么经过手动(缓慢)测试,要么被认为是成本过高。
但是,这一次,我们决定构建一个视觉回归测试套件。
视觉回归测试最近已经开始流行,这是有充分理由的。 从最基本的角度讲,它是贯穿您的网站的一系列测试,获取各个组件的屏幕快照,将这些屏幕快照与基准进行比较,并在情况发生变化时向您发出警报。
对于某些人来说,这听起来可能违反直觉。 我们更改CSS是因为我们希望事情看起来有所不同。 为什么我们要一个构建过程告诉我们,每次对样式表进行更改时我们都会破坏某些东西?
无论您是重新设计客户的样式还是与团队合作,都很容易对我们认为仅影响一个组件CSS进行更改,然后才发现我们在完全不同的页面上破坏了该组件。
为了真正理解为什么视觉回归测试可以带来好处,我认为了解什么使人对这项工作不利有帮助。
人与机器
事实证明,我们人类发现视觉刺激的变化实际上非常糟糕。 实际上,我们无法注意到变化已成为越来越多研究的生理和心理现象。
我们甚至用它来制作游戏。 您还记得旧的“发现差异”图片吗?
![](https://i1.wp.com/css-tricks.com/wp-content/uploads/2015/11/spot-difference-globe.jpg)
心理学家渴望理解许多现实世界中的问题,例如这些现象如何影响目击者的证词或驾驶能力。 但是在他们的研究中发现了很多知识,可以应用于我们在Web开发中的工作。
我觉得与谈话特别相关的一种现象是变化盲目性。
改变失明
关于变更盲目的概念的研究可以追溯到1970年代。 但是,在1996年,伊利诺伊大学香槟分校的两位教授George McConkie和Christopher Currie进行了一系列研究,这些研究引起了人们对变革盲目现象的浓厚兴趣。
变化盲是一种知觉缺陷,因此视觉刺激的改变可以在观察者没有注意到的情况下进行。 它与任何视觉缺陷无关,纯粹是心理上的。
在McConkie&Currie的研究中,他们发现,在某些情况下,图片区域多达五分之一的变化可能经常会被忽略。 该视频提供了一个很好的例子,说明了如果您不寻找它,可能会错过多少更改。
工具
在构建测试套件时,有多种工具可供选择。 我总是建议环顾四周,比较工具并找出最适合您的方法。
考虑到这一点,我选择了PhantomCSS作为视觉回归测试的首选工具。 我选择它有两个原因。
首先,因为它在GitHub上拥有相对活跃和健康的社区。 对于开源软件,我总是喜欢检查并确保仍在积极开发工具或库。 依赖放弃软件很快就会成为一种痛苦。
我选择PhantomCSS的第二个原因是因为它有一个方便的Grunt插件 ,可以轻松地与我现有的工作流程集成。
PhantomCSS的核心是三个关键组件的组合:
- PhantomJS或SlimerJS –无头浏览器。 PhantomJS是WebKit的无头版本,而Slimer是Firefox使用的Gecko引擎。
- CasperJS – Casper是JavaScript导航脚本和测试实用程序。 它允许我们定义在无头浏览器中发生的一组操作。
- ResembleJS – Resemble是一个JavaScript / HTML5库,用于进行图像比较。 它将根据基准测试我们的新测试,并提醒我们两者之间的任何差异。
最后,如前所述,我们将使用Grunt来运行测试。
实施
现在我们知道了什么以及为什么,让我们逐步完成设置和实施可视回归测试套件的步骤。
设置咕unt声
首先,我们需要设置Grunt来运行我们的测试套件,因此您需要确保已安装Grunt 。
在命令行中, $ cd /path/to/your-site
并运行:
$ npm install @micahgodbolt/grunt-phantomcss --save-dev
打开项目的`Gruntfile`并加载PhantomCSS任务,并在grunt.initConfig()
定义任务,如下所示:
grunt.loadNpmTasks('@micahgodbolt/grunt-phantomcss');
grunt.initConfig({
phantomcss: {
desktop: {
options: {
screenshots: 'baselines/desktop',
results: 'results/desktop',
viewportSize: [1280, 800]
},
src: [
'tests/phantomcss/start.js',
'tests/phantomcss/*-test.js'
]
}
}
});
测试不同的断点
我喜欢使用Sass MQ管理我的断点。 这种方法的另一个好处是给我所有断点的列表,我可以轻松地使用它们来设置测试。
使用PhantomCSS,您可以在实际的测试定义中操纵浏览器的宽度,但是我更喜欢将其从测试中抽象出来,以便为可视化测试套件提供更多的灵活性。 相反,选择在我的Grunt任务中定义它。
使用grunt-phantomcss,我们可以定义一组在不同断点处运行的测试,并作为一项额外的奖励将它们保存到不同的文件夹中。
为了使事情更具组织性和语义,我还为每个测试子任务命名,以匹配其对应的Sass MQ断点。
因此,例如:
grunt.initConfig( {
pkg: grunt.file.readJSON('package.json'),
phantomcss: {
desktop: {
options: {
screenshots: 'baselines/desktop',
results: 'results/desktop',
viewportSize: [1024, 768]
},
src: [
'tests/phantomcss/start.js',
'tests/phantomcss/*-test.js'
]
},
mobile: {
options: {
screenshots: 'baselines/mobile',
results: 'results/mobile',
viewportSize: [320, 480]
},
src: [
'tests/phantomcss/start.js',
'test/phantomcss/*-test.js'
]
}
}
});
在这里,我们正在运行同一组测试,但是要在不同的断点处运行它们,并将它们保存到基准和结果内的子文件夹中。
设置测试套件
在Grunt定义中,您可以看到我们通过运行`tests / phantomcss / start.js`开始运行。 此文件启动Casper(然后启动我们的脚本工具和无头浏览器),其外观应类似于:
phantom.casperTest = true;
casper.start();
现在,回到Grunt定义中,您可以看到我们然后运行了test / phantomcss /目录中所有以-test.js结尾的文件。 Grunt将按字母顺序循环遍历每个文件。
如何组织测试文件完全取决于您。 我个人喜欢为站点中的每个组件创建一个测试文件。
编写您的第一个测试
设置好“ start.js”文件后,就该编写第一个测试了。 我们将其称为“ header-test.js”。
casper.thenOpen('http://mysite.dev/')
.then(function() {
phantomcss.screenshot('.site-header', 'site-header');
});
在文件顶部,我们告诉Casper打开根URL,然后在我们的第一个测试中,抓取整个.site-header
元素的屏幕快照。 第二个参数是屏幕截图文件的名称。 我更喜欢将屏幕快照命名为它们负责的元素或组件,因为它使我的测试套件更具语义,并且更易于与队友共享。
以最简单的形式,这就是您为第一个测试编写的全部内容。 但是,我们可以构建一个更强大的测试套件,涵盖更多的实际元素,页面和应用程序状态。
脚本交互
Casper使我们能够自动执行无头浏览器中的交互。 例如,如果我们要测试按钮的悬停状态,可以将其写为:
casper.then(function() {
this.mouse.move('.button');
phantomcss.screenshot('.button');
});
您还可以测试登录和注销状态。 在我们的“ start.js”文件中,我们可以编写一个小函数,以便在启动Casper实例后立即填写WordPress登录表单。
casper.start('http://default.wordpress.dev/wp-admin/', function() {
this.fill('form#loginform', {
'log': 'admin',
'pwd': 'password'
}, true);
this.click('#wp-submit');
console.log('Logging in...');
});
您会注意到,我们在casper.start()
上运行它,而不是在它自己的单独测试中运行它。 在您的`start.js`文件中的casper.start()
上设置会话使该会话可用于测试套件中的其他文件,因为它始终会首先运行。
我建议看一下Casper文档 ,以获取有关脚本交互的更多信息。
运行测试
现在我们已经构建了基本的测试套件,是时候运行我们的测试了。 在命令行中,运行$ grunt phantomcss
。
PhantomCSS会自动将首次运行时的屏幕截图设置为基准,以与以后的所有测试进行比较。
![](https://i2.wp.com/css-tricks.com/wp-content/uploads/2015/11/bash.png)
如果您的测试确实失败,如上面的测试一样,PhantomCSS将向您的结果文件夹输出三个不同的屏幕截图。 它将输出原始文件,.diff.png和.fail.png。
例如,我们更改了文章页面中文本的字体大小,但无意中减小了存档视图中的字体大小。 PhantomCSS将给我们这些差异进行比较:
![](https://i2.wp.com/css-tricks.com/wp-content/uploads/2015/11/output.png)
挑战
构建视觉回归测试套件当然并非没有挑战。 我遇到的两个最大挑战是动态内容和在团队中分发测试。
动态内容
我遇到的第一个挑战(在某种程度上也是最困难的挑战)是如何正确处理动态内容。 测试套件遍历所有这些页面,并截取屏幕截图并进行比较。 如果内容不同,则测试将失败。
如果您与团队合作,那么每个人都有可能针对自己的本地环境进行测试。 在单个暂存环境中进行测试并不总是能够解决问题,因为内容可能在那里仍然会发生变化。 即,随机排列的一组相关帖子。
为了解决此问题,有两种方法可供我使用。
第一种,也是我最喜欢的方法是使用Javascript用一组有代表性的演示内容替换您正在测试的元素中的内容。
由于这些测试不应部署到您的生产服务器,因此您不必担心XSS漏洞。 因此,在获取屏幕快照之前,我喜欢在测试中使用.html()
将动态内容替换为我包含在代码存储库中的JSON对象中的静态内容。
第二种方法是使用称为Hologram或mdcss的工具,该工具允许您在CSS中使用注释来创建自动生成的样式指南。 这种方法的开销更大,因为它需要最大程度地改变工作流程,但是还具有为前端组件创建出色文档的额外好处。
分配
我在回归测试中遇到的第二个主要挑战是确定在工程师团队之间分配这些测试的最佳方法。 到目前为止,在我们的测试中,我们已经对测试URL进行了硬编码,这将在与每个人都可能在其本地环境中使用不同URL的团队合作时引起问题。
为了解决这个问题,我的团队和我已经将$ grunt test
任务注册为接受--url
参数,然后使用grunt.log将其保存到本地文件中。
// All a variable to be passed, eg. --url=http://test.dev
var localURL = grunt.option( 'url' );
/**
* Register a custom task to save the local URL, which is then read by the PhantomCSS test file.
* This file is saved so that "grunt test" can then be run in the future without passing your local URL each time.
*
* Note: Make sure test/visual/.local_url is added to your .gitignore
*
* Props to Zack Rothauser for this approach.
*/
grunt.registerTask('test', 'Runs PhantomCSS and stores the --url parameter', function() {
if (localURL) {
grunt.log.writeln( 'Local URL: ' + localURL );
grunt.file.write( 'test/visual/.local_url', localURL );
}
grunt.task.run(['phantomcss']);
});
然后,在测试文件的顶部,您将使用:
var fs = require('fs'), siteURL;
try {
siteURL = fs.read( 'test/visual/.local_url' );
} catch(err) {
siteURL = (typeof siteURL === 'undefined') ? 'http://local.wordpress.dev' : siteURL;
}
casper.thenOpen(siteURL + '/path/to/template');
现在,您的套件将在每次运行时查找.local_url文件,但是如果该文件不存在,则默认使用http://local.wordpress.dev。
收盘时
视觉回归测试可以为您的项目带来很多好处。 快速迭代和持续集成日益成为当今开发人员的口头禅,只有建立自己的安全网才有意义。
视觉回归测试套件也非常适合与开源项目中的人员合作。 实际上,WordPress项目正在努力建立带有随附的回归测试套件的模式库 。 该测试套件将提供基础,使WordPress项目可以继续制定计划以恢复其样式表的完整性。
备择方案
PhantomCSS并不是唯一可用的工具,它只是我认为适合我,我的团队和我们的工作流程的工具。 如果视觉回归测试听起来很酷,但是PhantomCSS听起来不像您喜欢的东西,或者您只是对替代品感兴趣,那么我建议您看一下:
翻译自: https://css-tricks.com/visual-regression-testing-with-phantomcss/