浏览器自动化测试初探 - 使用phantomjs与casperjs

首先要解释一下为什么叫浏览器自动化测试,因为本文只关注发布后页面功能的自动化测试,也就是UI层面的自动化。

浏览器测试有别于js代码的单元测试,后者一般是发布前的代码功能逻辑测试,在这方面已经有很多比较成熟的方案,如jasmine mocha Qunit...

为什么要做自动化

个人认为自动化测试的主要出发点有两点:

  • 减少重复的工作。让机器自动帮我们完成需要的交互操作,验证我们的页面功能。
  • 自动监控。通过自动回归我们的页面功能,可以在功能出错的时候提供报警,为我们手动排除问题提供参考。

开胃菜

说到浏览器自动化测试,不得不介绍大名鼎鼎的phantomjscasperjs。phantomjs可以理解为一个无界面的浏览器,可以通过流水线式的代码来驱动其页面的浏览行为,而后者是前者在易用性API上的一些封装。

这里演示下使用casperjs截取百度首页

关于这两个东西的安装,有兴趣体验的建议去看官方文档,其实很简单,这里不一一赘述。

首先创建一个js文件baidu.js

var casper = require('casper').create();
casper.start();
casper.thenOpen('http://www.baidu.com/', function () {
    casper.captureSelector('baidu.png', 'html');
});
casper.run();

以上代码主要做了三件事:

  1. 创建一个casperjs实例require('casper').create(),可以理解为一个浏览器进程
  2. 打开一个页面casper.thenOpen(...)
  3. 截取页面图像casper.captureSelector

在命令行运行

casperjs baidu.js

看看此脚本生成的图片结果

等等!为什么这个图只有400X300的大小?

原因是我创建了一个浏览器进程去加载页面,但是没有指明用什么浏览器去加载。所以在创建casper实例的时候,可以指定浏览器的窗口大小,甚至我们可以通过指定userAgent的方式冒充手机端的浏览器。例如我们将其指定为iPhone5的safari,并设置窗口大小:

var casper = require('casper').create({
    pageSettings: {

        // 冒充浏览器
        userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53'
    },

    // 浏览器窗口大小
    viewportSize: {
        width: 320,
        height: 568
    }
});

再次运行后我们的是这样,是不是手机浏览时的样子了?

简单应用

以上的例子,可以知道了怎么使用一个无界面的浏览器去加载页面,并获得页面的界面截图。

我们可以不打开浏览器,一行命令就可以知道页面长啥样了,所以每次我们只要运行这个casperjs脚本,通过截图就能看到我们页面是不是正常的。

但是,通过肉眼去判断,肯定是有违“自动化”的初衷的,所以必须要借助工具来帮我们分析。

最简单直观的办法就是“像素对比”,也就是把两次或多次的截图,逐一对比每一像素或一定范围区域,这样就能产出图片的差别了,如下图:

实际应用中,可以指定一个图片作为基准图,每一次我们截取的页面图与之对比,如果不一样,就可以说明线上的页面出现了异常。

像素对比这样的工具已经比较成熟,这里介绍一个与前端开发非常亲近的方案:Resemble.js

为什么说它和前端亲近,因为它使用的是canvas。我们知道,每一个图片的每一像素,都可以通过RBGA(red,blue,green,alpha)三个值来确定:

Resemble.js的主要原理就是把我们需要对比的图片绘制到canvas中,读取需要对比的像素点(或区域)的image data,从而确定图片的差异。

为了与phantomjs/casperjs更好的结合,Resemble.js作者同时做了基于Resemble.js的封装phantomcss

phantomcss使用了简单的API来做图片对比:

phantomcss.screenshot( "#CSS .selector1", screenshotName1);
phantomcss.screenshot( "#CSS .selector2", screenshotName2);
phantomcss.compareFiles(screenshotName1, screenshotName2);

假如对比的图片有不一致的地方,会生成一张对比图,同时有差异的地方会用显眼的颜色标出,类似这样:

注意

页面截图对比出现不一致,并不能证明我们的页面就出现了异常,例如广告位等,这些变化频繁的区域,每一次对比都有可能出现差异,所以对广告位或其他经常变化的位置不宜所差异对比。 实际应用中,对整个页面进行截图对比是不推荐的,这样的方式过于简单粗暴,我们更应该对页面的各个区域进行细分对比,做细粒度的监控。

cookie

既然是浏览器测试,不能没有cookie的参与,casperjs没有对cookie的操作作封装,可以使用phantomjs直接“种”cookie:

phantom.addCookie({
    name: 'cookie',
    value: 'value',
    domain: '.xx.com',
    path: '/',
    secure: false,
    httponly: false,
    expires: Date.now() + (1000 * 60 * 60 * 24 * 5)
});

在前面的开胃菜中,我们访问到的页面都是没有登录态的,这里通过手动植入百度帐号登录态的cookie值来实现登录访问。

在PC端chrome中打开百度首页,并用你的帐号登录,在开发者工具中复制百度帐号关键cookie BDUSS的值

并hard code到你的casperjs脚本中:

phantom.addCookie({
    name: 'BDUSS',
    value: '你复制的cookie值',
    domain: '.baidu.com',
    path: '/',
    secure: false,
    httponly: false,
    expires: Date.now() + (1000 * 60 * 60 * 24 * 5)
});

完整代码看这里 运行之后我们再看一下结果

Done!右上角已经有用户名,说明此时我们已经登录了!

交互

简单的截图+对比还远远达不到我们的测试要求,对于自动化原则来说,为我们实现自动化的页面交互才是王道,别急,这就来。

前面介绍了手动种植cookie的方式实现登录,下面看下怎么实现手机端百度的登录过程。

先预览下整个脚本login.js的代码,下面解释一下整个过程:

1. 创建实例。与开胃菜中的配置基本一致,这里为了更快,实例化的配置选择了不加载图片
loadImages: false
2. 加载页面
3. 截取无登录态的页面:
casper.captureSelector('1.png', 'html');

这一步会得到图片1.png,并且右上角是没有用户名的(未登录):

4. 读取当前的所有cookie并输出
var cookies = phantom.cookies;
for (var i = 0, len = cookies.length; i < len; i++) {
    console.log(cookies[i].name + ': ' + cookies[i].value);
}

这一步输出到命令行的结果:

可以看到,当前的cookie中还没有百度账号的关键cookie BDUSS。

5. 点击登录按钮
casper.mouse.click('#login');

casperjs(phantomjs)支持了非常丰富的可以支持复杂交互的鼠标事件:

  • click
  • doubleclick
  • rightclick
  • down
  • up
  • move

鼠标事件支持指定操作目标的CSS3路径

6. 点击登录后,会跳转到一个填写用户名和密码的登录页,这里为了方便,强行等待3秒确保登录页加载完
casper.wait(3000);
7. 截取登录页界面
casper.captureSelector('2.png', 'html');
8. 填写表单
casper.evaluate(function () {
    document.querySelector('[name=username]').value = '***';
    document.querySelector('[name=password]').value = '***';
});

这里使用了一个非常有用的方法evaluate

9. 截取填写登录表单后的样子
10. 点击登录按钮
11. 等待跳转回首页
12. 截取登录后的首页界面
13. 逐一读取cookie并显示到命令行中

最后,运行测试脚本casperjs login.js能得到4张截图,分别记录了整个登录交互过程中关键步骤的交互效果:

对比图1和图4,区别在于图4右上角的用户名:

同时,在命令行中最后还读取到了登录后的BDUSS cookie值:

再来点猛料

  • iframe里的操作

phantomjs(casperjs)不仅可以在当前页面操作,还可以把当前context切换到iframe里进行操作,这点给嵌入iframe的页面测试带来了很多方便。

  • 操作区域

phantomjs(casperjs)支持使用CSS3选择器及XPath的方式对我们需要操作的目标进行操作(点击、截图等),还可以通过指定区域边界的来操作,例如可以指定x/y坐标/width/height来进行点击或截图等:

casper.capture('capture.png', {
    x: 200,
    y: 300,
    width: 200,
    height: 300
});

交互的一些局限

现实世界的web里,有些交互功能不是机械的操作,有时候出于安全或其他因素考虑,页面会做一些限制,要求我们的交互需要根据一些动态输出,这种功能是很难做到完全自动化的,例如,上面的百度登录功能,有时候会出现验证码的情况:

这时候就很难借助机器来帮我们做登录了,所以在前面我要介绍通过手动植入cookie的方式实现登录。

单元测试

通过前面的介绍,使用phantomjs(casperjs)已经能实现很多自动化的功能,在此基础上,实现单元测试就很简单了。

casperjs提供了相对比较完善的单元测试API

单元测试中,每一个testsuite都被包装在一个闭包中:

casper.test.begin('your testsuite', 0, function (test) {
    // 单元测试代码
});

例如下面是一个测试百度页面title及log位置是否正确的一组测试用例:

casper.test.begin('test demo', 0, function (test) {

    casper.start();

    casper.thenOpen('http://www.baidu.com/', function () {

        // 测试页面title是否正确
        casper.test.assertTitle('百度一下', 'page title');

        // 测试logo的位置信息

        // 获取x/y/width/height
        var layout = casper.getElementBounds('#logo');
        layout = JSON.stringify(layout);

        // 验证是否符合
        casper.test.assertEquals(layout, '{"height":87,"left":0,"top":53,"width":320}', 'logo\'s boundary');

    });

    casper.run(function () {
        casper.test.done();
    });

});

完整代码test.js在这里

运行casperjs test test.js

可以在命令行中看到以下输出:

两个case都已pass!

与前面的截图肉眼查看的方式相比,单元测试为我们提供了更加简洁的测试结果。

另外,casperjs的test模块还可以在测试后产出XML结果,例如上面那个例子的结果如下:

<?xml version="1.0" encoding="UTF-8" ?>
<testsuites time="2.228">
    <testsuite name="test demo" tests="2" failures="0" errors="0" time="2.228"
    timestamp="2015-08-31T13:30:12.462Z" package="test">
        <testcase name="page title" classname="test" time="2.223">
        </testcase>
        <testcase name="logo's boundary" classname="test" time="0.005">
        </testcase>
        <system-out>
        </system-out>
    </testsuite>
</testsuites>

利用这个XML结果,与报警等系统结合,可以实现各种强大的自动化功能。

问题

  1. 浏览器兼容。 说到底,phantomjs(casperjs)提供的还是一个无界面的webkit内核浏览器,所以无法覆盖IE浏览器。目前Gecko内核的无界面浏览器已经有解决方案SlimerJS,并且支持与phantomjs一模一样的API。
  2. 设备兼容。 在各种手机等终端设备良莠不齐的情况下,服务端的无界面浏览器在这点上更难以做到模拟所有的软硬件环境。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

8668
来自专栏陈仁松博客

ASP.NET Core 'Microsoft.Win32.Registry' 错误修复

今天在发布Asp.net Core应用到Azure的时候出现错误InvalidOperationException: Cannot find compilati...

5228
来自专栏落花落雨不落叶

canvas画简单电路图

84611
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

3005
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2997
来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.4K7
来自专栏张善友的专栏

Mix 10 上的asp.net mvc 2的相关Session

Beyond File | New Company: From Cheesy Sample to Social Platform Scott Hansel...

2787
来自专栏杨龙飞前端

scrollto 到指定位置

2944
来自专栏java 成神之路

使用 NIO 实现 echo 服务器

5567
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3675

扫码关注云+社区