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

1,166 阅读9分钟
原文链接: www.qcloud.com

作者:yangchunwen

首先要解释一下为什么叫浏览器自动化测试,因为本文只关注发布后页面功能的自动化测试,也就是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脚本,通过截图就能看到我们页面是不是正常的。

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

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

diff

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

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

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

RBGA

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);

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

注意

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

既然是浏览器测试,不能没有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的值

cookie

并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)
});

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

login

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

交互

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

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

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

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

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

var cookies = phantom.cookies;
for (var i = 0, len = cookies.length; i < len; i++) {
    console.log(cookies[i].name + ': ' + cookies[i].value);
}

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

可以看到,当前的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. 截取登录后的首页界面

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

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

logined

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

再来点猛料

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

vcode

这时候就很难借助机器来帮我们做登录了,所以在前面我要介绍通过手动植入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

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

test

两个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. 设备兼容。
    在各种手机等终端设备良莠不齐的情况下,服务端的无界面浏览器在这点上更难以做到模拟所有的软硬件环境。

原文链接:ivweb.io/topic/55e46…