PhantomJS-前端自动化测试 – 网易用户产品&前端群博客

前端自动化测试向来是一件很困难的事情,特别是涉及UI层面的测试时更是爱莫能助,目前还没有很成熟的Web UI层自动化测试方案,我们这时候就必须借助于很多第三方的工具。

PhantomJS便是这么一个为自动化而生的利器,它本质上是一个基于webkit内核的无界面浏览器,并可使用JavaScript或CoffeeScript进行编程。由于没有界面,它的使用就有点像curl, lynx之类的命令行式文本浏览器。但PhantomJS远不是文本浏览器那么简单,由于它是基于webkit内核的,因此拥有的完善的Javascript解析、页面渲染功能,你完全可用它来模拟一个现代浏览器在加载网页时所做的各种事件。

我们来看看PhantomJS能做的事件吧:

  1. 不借助于浏览器的前端测试,并支持Jasmine, Capybara, QUnit, WebDriver, YUI Test, BusterJS, FuncUnit, Robot Framework等诸多单元测试框架

  2. 页面操作,通过标准DOM API或借助jQuery库对页面进行修改

  3. 页面截图,甚至支持页面中的SVG和Canvas元素,可作为图片导出引擎,支持导出为pdf或png

  4. 网络测试,可轻松生成网络测试报告(HAR格式),用于绘制瀑布图

举例来说,比如我们希望将Canvas绘制的内容导出为png图片,这通过PhantomJS来做就再合适不过了。但首先你得编写Javascript操作脚本,下面是PhantomJS自带的一个demo( colorwheel.js):

var page =newWebPage;
page.viewportSize ={ width:400, height :400};
page.content ='<html><body><canvas id=”surface”></canvas></body></html>’;
page.evaluate(function(){
var el = document.getElementById(‘surface’),
context = el.getContext(‘2d’),
width = window.innerWidth,
height = window.innerHeight,
cx = width /2,
cy = height /2,
radius = width  /2.3,
imageData,
pixels,
hue, sat, value,
i =0, x, y, rx, ry, d,
f, g, p, u, v, w, rgb;

el.width = width;
el.height = height;
imageData = context.createImageData(width, height);
pixels = imageData.data;

for(y =0; y < height; y = y +1){
for(x =0; x < width; x = x +1, i = i +4){
rx = x – cx;
ry = y – cy;
d = rx * rx + ry * ry;
if(d < radius * radius){
hue =6*(Math.atan2(ry, rx)+Math.PI)/(2*Math.PI);
sat =Math.sqrt(d)/ radius;
g =Math.floor(hue);
f = hue – g;
u =255*(1- sat);
v =255*(1- sat * f);
w =255*(1- sat *(1- f));
pixels[i]=[255, v, u, u, w,255,255][g];
pixels[i +1]=[w,255,255, v, u, u, w][g];
pixels[i +2]=[u, u, w,255,255, v, u][g];
pixels[i +3]=255;
}
}
}

context.putImageData(imageData,0,0);
document.body.style.backgroundColor =’white’;
document.body.style.margin =’0px’;
});

page.render(‘colorwheel.png’);
phantom.exit();

我们只需执行 phantomjs colorwheel.js,便可按自定义的canvas操作得到最终的图片,如下:

 

如果我们只是想截图,那就更简单了,phantomJS同样提供了一个demo脚本:rasterize.js,我们只要执行:

phantomjs rasterize.js http://raphaeljs.com/polar-clock.html clock.png

便可轻松得到:

   

再看看怎么做简单的页面测试,比如只想知道一个页面的总加载时间(包括下载所有静态资源、页面渲染),可简单地使用自带的pagetest.js

phantomjs loadspeed.js http://auto.163.com/special/browserupgrade/

得到结果如下:

Page title is 低版本IE升级提示
Loading time 1147 msec

够简单吧,如果你想进一步做网页性能分析,得到资源加载的详细瀑布图,phantomJS还可以为你生成HAR(HTTP Archive)报表。同样借助一下自带的netsniff.js:

photomjs netsniff.js http://auto.163.com/special/browserupgrade/

将生成的json格式报告放到HAR viewer( http://www.softwareishard.com/har/viewer/  )中,便可轻松得到资源加载瀑布图:

   

事实上,phantomjs自带的用例就有近40个,好好挖掘一下,你肯定能得到意想不到的惊喜,如果你想要做单元测试,那就更要潜心研究一下了:http://code.google.com/p/phantomjs/

Backbone案例的初略理解

http://juntype.com/junlab/99.html

最近发现自己越来越不行了,做前端也有些岁月了,神马HTML5,CSS3等层出不穷,如果再不及时Update一下自己,很快也会被这个行业给淘汰了!废话不多说了,今天主要是记一下自己学习Backbone的笔记。
国内有用到这技术的有豆瓣说 阿尔法城 可能是他们比较出名。。。
前端MVC 主要讲的是在前端方面实现MVC的模板,在前端也有自己的模块,视图,控制器,这结合RESTful来做是个不错的选择,我在瘦服务端架构TSA(Thin Server Architecture)记录了一下
而Backbone只是通向前端MVC其中的一条路而已。。。
Backbone.js API中文文档
我下面的源码出自 让我们用Backbone.js来写一个HelloWorld程序 我也是通过这源码结合API进行学习 上面那篇博文有些错误,我已经修正。
这里说一下Backbone的执行顺序
路由(Backbone.Router)-> 模型(Backbone.Model)-> 视图(Backbone.View)

路由告诉你要去哪里。

App.Controllers.Routes = Backbone.Router.extend({  
    routes: {  
    "!/hello" : "hello",//使用#!/hello驱动路由 这里会去分行下面hello这个函数 
    },  
    hello : function(){  
        //新建一个模型,模型向后端请求更新内容成功后根据模型渲染新页面
        var helloModel = new App.Models.Hello; //调用模板
        helloModel.fetch({  
            success: function(model){  //成功拿到数据,调用视图渲染页面
                var helloView = new App.Views.Hello({model: model});  
                helloView.trigger('change');  //绑定change事件
            }
        });
    }
});


模型告诉该干些什么。这里是去拿数据,set是提供一个设置初始数据

App.Models.Hello = Backbone.Model.extend({  
    url: function() {  
        return '/index/test'; // 获得数据的后台地址。
    },  
    initialize: function() {  
        this.set({'message':'hello world'}); // 前端定义一个message字段,name字段由后端提供。
    }  
});


最后你就知道了有什么了

App.Views.Hello = Backbone.View.extend({  
    el: "body",  //在哪里显示
    template: $("#hello-container-template").html(),  //获取模板 模板是用Mustache
    initialize: function(options){  
        this.options = options;  
        this.bind('change', this.render);  
        this.model = this.options.model;  
    },
    render: function(){ // render方法,目标只有两个:填充this.el,返回this以便链式操作。
        $(this.el).html(Mustache.to_html(this.template,this.model.toJSON()));
        return this;
    }
});


上面就是MVC的核心了,下面就是一个管理着 视图/控制/模型 的全局类

var App = {  
    Models: {},  
    Views: {},  
    Controllers: {},  
    Collections: {},  
    initialize: function() {  
        new App.Controllers.Routes();  
        Backbone.history.start() // 要驱动所有的Backbone程序,Backbone.history.start()是必须的。  
    }  
};


调用,完事啦。。。

App.initialize();


其实我只知道了这么点皮毛,接下来就要在实践中多去学习了,期待有后续。。。
给自己加个油,提个醒!
PS 接下来,我将试着在 doulaipk.com 去实践,求PHP程序猿.

nginx根据host判断root的 with passenger配置

server {
  server_name ~^(.*\.)?(?<app>[^.]+)\.dev$;
  root /opt/apps/$app/public;
  rails_env development;
  passenger_enabled on;
}

Nginx 0.8.25+ 应该都能用,参考 server_name

需要跑哪个Rails/Rack app就在/opt/apps下面建立个symbol link (link到含config.ru的根目录)

cd /opt/apps
ln -s /path/to/myapp

当然http里需要设置下passenger_rootpassenger_ruby

www.myapps.dev 和 myapps.dev 都会访问 /opt/apps/myapp

如果你连/etc/hosts都不想改,想像pow那样,在 Mac 下可以用这个脚本来启动pow里的DNS server

Linux有Hoof可以用(我还没试过),使用nsswitch来自动把指定的根域名解析到本机

github更新自己fork的代码,从原作者分支更新代码

github上有个很方便的功能叫fork,将别人的工程一键复制到自己账号下。这个功能很方便,但有点不足的是,当源项目更新后,你fork的分支并不会一起更新,需要自己手动去更新。下面记录下网上找到的更新的方法

1、在本地装好github客户端,或者git客户端

2、clone 自己的fork分支到本地,可以直接使用github客户端,clone到本地,如果使用命令行,命令为:

git clone git@github.com:break123/three.js.git three.js

3、增加源分支地址到你项目远程分支列表中(此处是关键),命令为:

git remote add mrdoob git://github.com/mrdoob/three.js.git

此处可使用git remote -v查看远程分支列表

4、fetch源分支的新版本到本地

[master]> git fetch mrdoob

5、合并两个版本的代码

[master]> git merge mrdoob/master

6、将合并后的代码push到github上去

[master]> git push origin master

通过github更新自己fork的代码 

Zepto 使用中的一些注意点

前段时间完成了公司一个产品的 HTML5 触屏版,开发中使用了 Zepto 这个著名的 DOM 操作库。
为什么不是 jQuery 呢?因为 jQuery 的目标是兼容所有主流浏览器,这就意味着它的大量代码对移动端的浏览器是无用或者低效的。
而 Zepto 只针对移动端浏览器编写,因此体积更小、效率更高,更重要的是,它的 API 完全仿照 jQuery ,所以学习成本也很低。

但是在开发过程中,我发现 Zepto 还远未成熟,其中包含了一些或大或小的“坑”,与 jQuery 的差距还是很明显的,所以写篇文章记录下,希望对后来者有帮助
注意,本文撰写时 Zepto 版本为 1.0 正式版

从哪里下载 Zepto

这个问题看起来很蠢,从官网下载不就行了嘛!可是你有没有发现下载链接上面有行小字呢?

There are more modules; a list of all modules is available in the README.

在这个 README 里面你会惊奇地发现,Zepto 源码中有 14 个模块,而官网提供的标准版里面只有 7 个模块!而且居然不包含对移动端开发非常重要的 touch 模块(提供对触摸事件的支持)!
所以我的建议是,不要从官网下载,而是从 Github 下载了源代码之后自己 Build 一个版本,这样你可以自行挑选适合的模块。比如我挑选的模块是这么几个:

  • polyfill,zepto,detect,event,ajax,form,fx 这7个就是标准版包含的模块
  • fx_methods 有了这个模块之后,.show() .hide() 等几个方法才能支持动画了,比如 .show('fast')
  • data 提供对 .data() 方法的完整支持,像 jQuery 一样用内存对象存储
  • assets 移除 img 元素后做一些特殊处理,用来清理内存
  • selector 更多的选择器的支持,后面会提到
  • touch 对触摸事件的支持,比如 tap 事件

如果你对 Node 不了解不知道如何 Build 的话,可以下载我的版本

不要用 click 事件,用 tap 代替

这个估计已经广为人知了,因为 click 事件有 200~300 ms 的延迟,为了更快的响应,最好用 Zepto 提供的 tap 事件
不相信的话,可以用以下代码测试一下

var t1,t2;
$('#id').tap(function () {
    t1 = Date.now();
});
$('#id').click(function () {
    t2 = Date.now();
    alert(t2 - t1);
});

Zepto 对 CSS 选择器的支持

郑重提醒,:text :checkbox :first 等等在 jQuery 里面很常用的选择器,Zepto 不支持!
原因很简单,jQuery 通过自己编写的 sizzle 引擎来支持 CSS 选择器,而 Zepto 是直接通过浏览器提供的document.querySelectorAll 接口。
这个接口只支持标准的 CSS 选择器,而上面提到的那些属于 jQuery 选择器扩展,所以仔细看看这个网页,注意一下这些选择器。

当然也有好消息,就是上面提到的 selector 模块,如果有这个模块的话,能够支持 部分 的 jQuery 选择器扩展,列举如下:

  • :visible :hidden
  • :selected :checked
  • :parent
  • :first :last :eq
  • :contains :has

元素的尺寸计算

首先 Zepto 没有 .innerHeight() .outerWidth() 等四个方法,其次,它的 .height()/.width() 方法也不完善,对于display:none 的元素,计算出的高宽都是 0
而这在 jQuery 里面是没有问题的,因为 jQuery 针对这种元素,会先设置其 css 样式设置为position: "absolute", visibility: "hidden", display: "block" 
计算完高宽后再恢复,参见 https://github.com/jquery/jquery/blob/master/src/css.js#L460
如果遇到这种特殊情况,可以参考 jQuery 写一个类似的方法

.prop() 方法的陷阱

有次我要把一个文本框置为只读,写了这么一行 $('#text').prop('readonly', true) 结果死活不工作
找了半天才发现,正确的写法是这样 $('#text').prop('readOnly', true) ,如果你居然看不出两者的差别,那么悄悄提示你:注意大小写!
翻了一下相关的文档,原来只读属性的正确拼法确实是 readOnly,可是在 jQuery 里面上一段代码却能正常工作
于是到 jQuery 源码里面一找才发现,还有这么一段https://github.com/jquery/jquery/blob/master/src/attributes.js#L466

jQuery.each([
    "tabIndex",
    "readOnly",
    "maxLength",
    "cellSpacing",
    "cellPadding",
    "rowSpan",
    "colSpan",
    "useMap",
    "frameBorder",
    "contentEditable"
], function() {
    jQuery.propFix[ this.toLowerCase() ] = this;
});

从这里也能看到,jQuery 的成熟度真是难以超越,因为他把我们都惯坏了……
考虑到这段代码比较简单,我厚颜无耻地抄袭了一下然后给 Zepto 提了一个 pull request ,如果你们喜欢这种无脑的用法,可以去评论表达支持(记得用英文)

2013-11-25 这个 PR 已经被 Merge

.show() 的动画效果

如果没有 fx_mehods 模块的话,.show() 方法是不支持动画的,不过有了这模块后,动画的支持还是有点小问题,比如这么一段 HTML

<div style="background:black;opacity:0.7;display:none">
    test
</div>

如果你调用 $('div').show('fast') ,那么动画完成后你看到的不会是一个半透明的元素,而是全黑不透明的
因为 Zepto 的 .show() 动画实现的很简单,没有高宽的变化,而是将透明度从 0 逐渐变为 1,所以元素上原来设置的透明度就被替代了。
这种情况下,可以用 .fadeIn() 方法来替代 .show()

结语

看到这里相信你已经了解为什么我说” Zepto 还远未成熟“,目前它其实还仅仅处于“能用”,远未达到 jQuery “好用”的地步
最后,关于整个 HTML5 触屏版的前端开发,我有篇 Slide 做了总结,本文只是其中关于 Zepto 部分的详细阐述,感兴趣的可以去看看

http://chaoskeh.com/blog/some-experience-of-using-zepto.html

2010 Web前端技术趋势及总结 Facebook摘全明星MVP

摘要:经过这段时间国内及国外各大公司的集中自曝,我们可以从中总结出 2010 Web前端技术的一些趋势。总的来说,随着后端技术的成熟,各大公司已经把重点从后端架构调整/建设转移至前端。

导读:经过这段时间国内(百度,淘宝,新浪等)及国外(Facebook,Youtube,Yahoo等)各大公司的集中自曝,我们可以从中总结出 2010 Web前端技术的一些趋势。总的来说,随着后端技术(存储,并发,分布式)的成熟,各大公司已经把重点从后端架构调整/建设转移至前端(TTI时间,快速发布,带宽利用率)。

作为明星技术的HTML 5/CSS 3,都未正式成为各公司的考虑重心,虽有所尝试,但在关键功能上,均未成为主力。这也W3C对当前HTML 5/CSS 3标准现状的表述:“不适宜用作生产环境”一致。

Web前端技术的范围

1. 编程语言/技术(HTML,JavaScript,CSS等)

2. 跨浏览器兼容性/支持(JS Framework,CSS Library)

3. 网络传输性能(并行下载,带宽利用率)

4. 浏览器渲染时间/性能(TTI即用户可交互前等待时间,JS执行性能)

个人感觉,Facebook无疑又成为了技术上的明星,在大家还在感慨其对于PHP的重大改进HipHop(Blocked inside China mainland)的时候,今年Facebook又在前端技术方面给大家带来了惊喜。

Facebook面临的问题

500M(Million)注册用户,50%每天至少访问一次,用户平均每日在线时间为5小时25分钟。带宽及服务器压力均很大。

Facebook的解决方案

Quickling

Facebook提出了一个新名词Ajaxify,顾名思义,就是将传统的POST/GET转换为Ajax请求。优点显而易见,首先减少了不必要的 HTML传输,只请求和渲染页面需要更新的部分,这就相应减少了所需传输的内容加快了内容送达至用户的时间。并且也减少了服务端对HTML的不必要的渲染。Facebook也提到了可以减少session的重复load/unload。

使用Ajax也许不是什么新鲜的新闻,大家拒绝这项技术的原因可能很大程度基于SEO的需求。解决方案也很简单,将Ajax只是作为提高用户体验的手段,而不是浏览网站必须的方法,即可解决SEO的问题(P.S. Facebook不需要SEO)。

整套方案包括:Link Controller, HistoryManager, BootLoader, Busy Indicator, CSS Unloading, Permanent link support, Resetting timer functions。这些方案本身没有什么特殊的,大部分都可以顾名思义,需要解释一下的可能是link controller,其含义是将标准的HTML LINK请求转换为Ajax请求(通过绑定click事件)。Facebook的可贵之处是提供了这一整套完整的解决方案,最大程度上保证了网站的可用性。

效果:

提高了10%-30%的网站传输时间,并提高了20%-30%的服务端页面渲染速度。

使用范围:

45%的Facebook页面使用了此项技术。

PageCache

简单的说,就是将访问过的页面缓存在客户端。但我们知道,作为Facebook这样交互性很强的网站,需要保障用户能尽早的获得更新后的信息,而不是给用户展示一个毫无意义的过期页面。

Facebook设计了一个框架来识别一个页面是否来自于缓存(猜测:页面首次加载完毕后将所有Ajax的Callback和Result缓存在本地。Facebook页面是基于Ajax获取页面内容),若来自于缓存,通过Ajax来更新所需更新的模块(猜测:通过JavaScript预先定义本页面所需更新的div Id及对应的callback handler,并在页面下载时同时下载下来)。

其提到了三种更新类型:增量更新,用户复写(例如用户在页面上回复了一则评论)及跨页更新(例如在消息详细页面将一则消息标识为已读,需将首页的未读消息数进行更新)。核心思路还是依据Ajax进行更新。具体思路为:

增量更新:只要页面来自于缓存,即更新所有预定义的需增量更新的模块。

用户复写:通过HistoryManager记录用户操作并在cache页面读取后重放所有被标记为“replayable”的操作。

跨页更新:通过服务端Database API发送信号至客户端将过期缓存标识为invalid(不清楚如何实现。也许是DB端提供一个开放的webservice,客户端通过Ajax持续访问此API来获得此信息)。获得了缓存过期信号后,通过Ajax更新需要更新的信息。

Facebook顺带提到了一个更新Ajax内容避免页面变化/闪烁的小技巧,就是先将需更新的地方设置为blank,而非直接更新其内容。

效果:

加速了10倍的网站响应时间并节约了20%的服务端页面渲染成本。

BigPipe

此项技术通过将页面分割为各个Pagelets的方式,将整张页面的获取/渲染变成了并行的方式(感觉非常像iframe sets,但Facebook使用Ajax实现。)。此项技术是Quickling和PageCache的基石。此技术包含了服务端/客户端两方面,在前后端均打破了以往页面的渲染形式。

实现细节:

Pagelet的Response为JSON格式,包括id,css,js,content,onload等属性及相应内容,收到后会通过预定义好的JS function来进行渲染。

Pagelet提供的高级功能:Pagelet的继承,Phased Rendering(猜测:依据规则渲染,也就是依据Pagelet的Response进行渲染),跨Pagelet依赖(数据依赖,显示依赖,JS依赖)。

BigPipe的三种模式:

◆一次渲染模式:即普通模式,支持搜索引擎,用来支持那些不支持JS的客户端。

◆管线模式:即并行模式,并行请求,并即时渲染。

◆并行模式:并行请求,但在获得所有请求的结果后再渲染。

效果:

提高了2倍的页面响应时间。

扩展阅读:世界最大的PHP站点 Facebook后台技术探秘

YouTube面临的问题

每天2Billion的访问。每分钟上传35小时的内容。可YouTube需要即时播放视频!越快越好。

YouTube解决方案

1. 将JavaScript引用位置从页首移至页尾。

2. 直接嵌入Flash Player(YouTube之前使用JS来加载Flash Player)。通过页尾的JS来判断客户端的Flash版本(或不支持Flash),来替换预先嵌入的Flash Player或内容(如果需要的话),用来支持特定的客户群。

效果:页面渲染时间从~400ms降低为~200ms。Flash播放时间从~1200ms降低为~1100ms。

3. 预加载视频连接: 通过使用JavaScript创建Image引用视频内容来与解析DNS并预开启一个connection供之后使用。

效果:建立视频连接的总时间从~260ms降低为~180ms。

4. 提供简化版:这个很无聊,就是提供一个简版。

效果:页面加载时间从~1750ms降低为~1100ms。

5. UIX Widget系统:延迟加载非关键内容。其实整段没什么新意,大部分省略,无非是通过Ajax在页面渲染完后再来动态加载非关键内容。比较特别的是利用 JS的事件冒泡,在最上层用一个handler来处理各种事件(优点不详。。也许只是代码比较简洁集中吧),通过CSS来标识和识别对应的 handler。

Yahoo Mail

Yahoo如何构建下一代的Mail系统?答案就是通过YUI3。Yahoo的技术绝对是最优的,其已经将Web前端技术发展到一个非常成熟的地步,照顾到Web的方方面面(数据压缩,模块化,高效CSS,非阻碍式JavaScript加载,静态内容提供,利用浏览器Cache等等),所以也鲜有创新了。某种程度上来说,Facebook的一些所谓创新也不过是后知后觉,Yahoo早已考虑并实现了这些方案,只是也许不是那么有针对性而已。

Baidu

感觉整体倾向于组织结构介绍及一些比较过时的内容。

Taobao

还在讨论一些何时使用Ajax,何时不使用的问题。

相反的,淘宝的精益测试倒是引起了我的兴趣,出自微软的淘宝员工鹤云讲述了淘宝是如何进行CI(持续集成)的。有一些经验例如代码覆盖率测试也给人一些启发。

新浪博客

也是一些组织架构,开发方式的内容。介绍了一下新浪自己的JavaScript框架。并无太多亮点。

大概就总结了这么多吧,感觉还是国外在主导。国内也在越来越重视这个方向,一些有实力的企业也做出了一些成绩,但还是与国际潮流有差距,也许是重视程度的区别吧。欢迎大家补充讨论。

原文链接:http://developer.51cto.com/art/201012/237980.htm