上篇文章《移动 H5 广告的性能自动化测试方案》末尾我提到了几个问题,是我们在探索移动端 H5 性能自动化测试遇到的几个比较棘手的坑:
本文主要介绍我们的解决思路,以供读者参考,如有问题欢迎大家轻喷。。
在介绍新方案之前,想先扯一些前端的背景知识。
目前手机上的浏览器可谓琳琅满目,譬如 360 安全浏览器,UC 浏览器,QQ 浏览器,欧朋浏览器,百度手机浏览器,谷歌浏览器,搜狗手机浏览器,猎豹浏览器等等,给测试同学的适配带来了巨大的工作量。另外最近 “原创的红芯浏览器” 可谓刷爆了各大朋友圈,在此也想小科普一些浏览器内核的知识。
早期全球仅有四个独立的浏览器内核,分别为微软 IE 的 Trident、网景最初研发后卖给 Mozilla 基金会并演化成火狐的 Gecko、KDE 的开源内核 Webkit 以及 Opera(欧朋) 的 Presto。其中,Presto 是历史最悠久的内核。
早期的时候 chrome 和 safari 都采用了 webkit 内核,不过 Google 发布了 chrome 浏览器后,将使用的浏览器内核更名为 chromium,是 webkit 开源项目的一个分支,随后中2013年4月3日,谷歌的 Chromium Blog 发表了一篇博客,表示后续 Chromium 项目将采用 Blink 渲染引擎。
Android 原生浏览器、苹果的 Safari、谷歌的 Chrome(Android4.0 使用) 都是基于 Webkit 开源内核开发的。
用户界面: 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
浏览器引擎: 在用户界面和呈现引擎之间传送指令。
渲染引擎: 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
网络层: 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
用户界面后端: 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
JavaScript 解释器: 用于解析和执行 JavaScript 代码。
数据存储: 这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了 “网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。
以 WebKit 内核为例,看一下浏览器渲染的过程
W3C performance API
前文有提到影响用户体验的三个性能指标分别是:
这几个关键时间节点,可以通过 W3C performance API 中的 Navigation Timing API 来获得。
W3C 组织在 2010 年成立了 web 性能工作组,专门开发了一个支持将浏览器暴露给 JavaScript 的 API,该 API 为浏览器开发人员提供了一种精确度量和分析 web 页面性能的有效手段。整套 performance API 有 10 种
我们需要统计的时间指标可以通过以下公式换算出来:
DNS查询耗时 :domainLookupEnd - domainLookupStart
TCP链接耗时 :connectEnd - connectStart
request请求耗时 :responseEnd - responseStart
解析dom树耗时 : domComplete - domInteractive
白屏时间 :responseStart - navigationStart
DOMReady时间 :domContentLoadedEventEnd - navigationStart
onload时间 :loadEventEnd - navigationStart
负责性能数据监控的同学在开发过程中发现,白屏数据按照上面时间点抓取存在问题,不太准确,有的时候拿到的截图已经开始有 dom 数据加载了,为此还发生过争论,带着这个问题仔细的考证了一下,从 W3C 开源项目的 issue 列表中看到了项目成员的解答:
问:为什么没把first paint time 写进标准里呢?希望您能把他加进去。
答:因为不能准确定义它,这个时间标准是模糊的。比如一些网页,他本来就是空白的啊,怎么来定义这个paint的时间呢?而且很多人并不在乎,什么时候画出第一个像素,他们想跟踪一下具体某个元素是什么时候画出来的。但是每个网站都不一样,我们不可能来定义太细的接口。所以不能加。
Frame Timing 可以让你跟踪画出来的时间,如果你真想追踪first paint(白屏) time,你得等我们有一些 Houdini pieces(巧妙的规避办法 –-牛津大字典) 才能有比较好的解决办法。
问:那为什么DevTools里面有呢?为什么不加个时间戳(从hidden到visible)? 我就是想知道,从点击进去到看到内容,用户等了多久,这个值是模糊的么?
答:在DevTools里有,并不是意味着他是一个好的标准。如果想加时间戳来记录,你自己加就行了呗。我只是认为,first paint 不够分量来写进标准,frame Timing 应该更有意义。但是需要时间。
如果读者有不错的白屏时间计算方法,欢迎做文章下面留言。
以下几种方案是我们内部讨论过程中梳理过的几种可能性,有些方案可能不适合我们当前的需求,但是适合读者自身,所以观点仅仅给大家作为参考。
Tcpdump 缺点:需要 root、高版本手机 root 成功率极低、容易被还原导致自动化测试任务无法进行,无法解析 https 请求。
MimtProxy 优点
MitmProxy 是什么这里就不做赘述,读者可以自己看一下官网介绍 (https://mitmproxy.org; ),它本来是被黑客拿来做 “中间人攻击” 用的利器,我们主要用到的是它对 https 数据截获的功能。不同于 tcpdump 的是我们可以对 mitmproxy 抓取到的数据做高度定制化处理。
MimtProxy 缺点
由于其代理的特征,会对传输数据有一定性能上的损耗,不过从版本横向比较的角度来说可以忽略不计;另外 mitmproxy 获取不到 UDP 数据包;另外还有一些其他坑就不铺开说了。
灵感来自 (https://github.com/jwcqc/WebViewMonitor;) 项目;
目前支持 chrome 内核和 QQ 内核
设计流程图
基于 STFService APK 的改造
由于我们整体的测试方案是基于 OpenSTF 进行定制的,所以我们选择在 STFService APK 上进行改造,当然我们在开发过程中做了解耦,方便以后升级。
注入代码如下:
public void onProgressChanged(int newProgress) {
if (newProgress == 100) {
try {
if (mTimerFinish != null) {
mTimerFinish.cancel();
mTimerFinish = null;
}
} catch (Exception e) {
}
if (!isDone) {
mTimerFinish = new Timer();
mTimerFinish.schedule(new TimerTask() {
@Override
public void run() {
//TODO collector.js文件的地址,收集的功能主要在这里面实现
String injectJs = "https://thisis.inject.js";
String js = "javascript:" +
" (function() { " +
" var script=document.createElement('script'); " +
" script.setAttribute('type','text/javascript'); " +
" script.setAttribute('src', '" + injectJs + "'); " +
" document.head.appendChild(script); " +
" script.onload = function() {" +
" startWebViewMonitor();" +
" }; " +
" }" +
" )();";
onLoadInjectJS(js);
}
}, 10000);
}
}
}
性能数据处理代码
public long[] parsePerformanceData(String data) {
long[] result = null;
try {
JSONObject jsonObject = new JSONObject(data);
jsonObject = jsonObject.getJSONObject("payload");
JSONObject timeJsonObj = jsonObject.getJSONObject("navigationTiming");
final long timeStart = timeJsonObj.optInt("navigationStart");
final long timeWhite = timeJsonObj.optInt("responseStart") - timeStart;
final long timeLoad = timeJsonObj.optInt("loadEventEnd") - timeStart;
final long timeDNS = timeJsonObj.optInt("domainLookupEnd") - timeJsonObj.optInt("domainLookupStart");
final long timeTCP = timeJsonObj.optInt("connectEnd") - timeJsonObj.optInt("connectStart");
final long timeDOMParse = timeJsonObj.optInt("domComplete") - timeJsonObj.optInt("domInteractive");
final long timeDOMReady = timeJsonObj.optInt("domContentLoadedEventEnd") - timeJsonObj.optInt("navigationStart");
result = new long[]{timeWhite, timeLoad, timeDNS, timeTCP, timeDOMParse, timeDOMReady};
} catch (Exception e) {
result = null;
}
return result;
}
针对 OpenSTF 服务端做了扩展
通过以上方式拿到性能数据后,需要对 pacap 文件进行转化,一开始用的是开源工具 python 版本的 pcap2har,开源地址(https://github.com/andrewf/pcap2har; )后来为了更好的集成到平台中,我们尝试写了一套 node 版本的,具体参考公众号之前的文章《nodejs 实现 pcap 转换 har》,没错,上面的技术方案也是团队两个小伙子捣鼓出来的,是不是非常棒?
通过开源项目 harviewer(地址请参考我上篇文章)将 json 格式的结果文件快速转换成前端 timline 格式的报告,当然为了满足我们项目的需要,一样对 harview 进行了大量的改造,代码细节就不截图了。
测试报告如下:
前端性能优化方案,请关注我们部门前端团队奇舞团的公众号,有专业的文章讲解,比如 l 来自 W3C 成员的文章《CSS 性能优化的 8 个技巧》等等。
https://blog.csdn.net/TMQ1225/article/details/53204476
https://segmentfault.com/a/1190000004010453
http://www.cocoachina.com/ios/20170717/19882.html?utm_source=itdadao&utm_medium=referral
https://github.com/jwcqc/WebViewMonitor
https://github.com/w3c/navigation-timing/issues/21
https://www.w3.org/Submission/first-screen-paint/
https://www.cnblogs.com/littlelittlecat/p/6810294.html