移动端真机调试工具

   做移动开发大多数的时候跟手机界面打交道,也就是说你只能在电脑上开发写代码,最终效果是确是在另外一个终端看到的。虽然各种浏览器为开发者提供了很多模拟手机设备的功能,这些功能总体来说基本可以满足我们对于调试移动设备的需求,但是模拟毕竟是模拟,它不能真正做到实现真机一样的效果。经常遇到在chrome模拟器上样式显示正常,在手机上却出现样式错误的情况,在PC端模拟器运行正常,在真机上却报错的情况。所以,必须在想办法在真实的机子上测试我们的代码最终的执行效果。移动端真机调试有几个刚需:看log、看error信息、看网络请求。其它的如实时修改html/css/javascipt代码, 查看timeline,cookie,localstorage,网络资源和断点调试就不是那么频繁。可以沿着两条思路解决这个问题:第一,自己写一个简单的调试工具,第二:寻找专业的调试工具。

自己写工具

先介绍一下容易理解的移动端调试方法吧,比如自己写一个打印输出的信息框。

/*
 * 移动端打印
 *
 */
function debug(msg) {

   msg=new printTree().dump(msg);

    var div = document.querySelector("#logField");
    if (div) {
        div.innerHTML += msg;
    } else {
        div = document.createElement('div');
        div.id = "logField";
        div.classList.add('debug');
        // div.style['position'] = 'fixed';
        // div.style['top'] = 0;
        // div.style['left'] = 0;
        div.style['width'] = '100%';
        div.style['background-color'] = 'rgba(0,0,0,.8)';
        div.style['font-size'] = '1.4rem';
        div.style['color'] = 'yellow';
        div.style['word-break'] = 'break-all';
        div.style['line-height'] = '1.3';
        div.style['padding'] = '10px 20px';
        div.innerHTML = msg;

        var first=document.body.firstChild;
        document.body.insertBefore(div,first);
        // document.body.appendChild(div);
    }
}

      

 printTree源代码如下所示:

  1 ;(function(window){
  2 
  3   // Constructor
  4   printTree = function (config){
  5 
  6       config=config ?  config : {};
  7 
  8       this.tabKey= config.tabKey ? config.tabKey : "  ";
  9 
 10   };
 11 
 12   // Prototype
 13   printTree.prototype = {
 14 
 15     /**
 16       *
 17       * Private methods
 18       *
 19       */
 20 
 21     /**
 22     * [对函数进行格式化处理]
 23     * @param  {[类型不确定]}   obj     [数据内容]
 24     * @param  {[Number]}      indent      [缩进Tab键数量]
 25     * @param  {[Boolean]}     addComma    [是否需要在行尾加逗号]
 26     * @param  {[Boolean]}     isArray     [是不是数组]
 27     * @param  {[Boolean]}     isPropertyContent     [是不是属性内容]
 28     * @return {[String]}      print       [格式化后的对象字符串]
 29     */
 30     _printObject:function(obj, indent, addComma, isArray, isPropertyContent){
 31 
 32       var print = "";
 33 
 34       // 是否需要加逗号
 35       var comma = (addComma) ? "," : "";
 36 
 37       // 判断对象类型
 38       var type = Object.prototype.toString.call(obj);
 39 
 40       // 数组的处理
 41       if( type == "[object Array]"){
 42 
 43         //
 44         if(obj.length == 0){
 45           // isPropertyContent如果为真,说明是属性内容,不用加tab键,否则要在前面加tab键
 46           print += this._getRow(indent, "[ ]"+comma, isPropertyContent);
 47 
 48         }else{
 49 
 50           // 打印数组前面的中括号
 51           print += this._getRow(indent, "[", isPropertyContent);
 52 
 53           for(var i = 0; i < obj.length; i++){
 54             // _printObject 数组中可能嵌套这其它对象
 55             // indent + 1  每递归一次,加一个缩进符
 56             // i < (obj.length - 1) 不是数组的最后一个元素,都要加逗号
 57             print += this._printObject(obj[i], indent + 1, i < (obj.length - 1), true, false);
 58 
 59           }
 60 
 61           // 打印数组后面的中括号
 62           print += this._getRow(indent, "]"+comma);
 63 
 64         }
 65 
 66       }else if(type == "[object Object]"){
 67 
 68         if(obj == null){
 69           print += this._formatLiteral("null", "", comma, indent, isArray);
 70         }
 71         else{
 72 
 73           var numProps = 0;
 74 
 75           for(var prop in obj) numProps++;
 76 
 77           if(numProps == 0){
 78 
 79             print += this._getRow(indent, "{ }"+comma, isPropertyContent);
 80 
 81           }else{
 82 
 83             print += this._getRow(indent, "{", isPropertyContent);
 84 
 85             var j = 0;
 86 
 87             for(var prop in obj){
 88 
 89               print += this._getRow(indent + 1, prop+':'+this._printObject(obj[prop], indent + 1, ++j < numProps, false, true));
 90 
 91             }
 92 
 93             print += this._getRow(indent, "}"+comma);
 94 
 95           }
 96         }
 97 
 98       }else if(type == "[object Number]"){
 99 
100         print += this._formatLiteral(obj,comma,indent, isArray);
101 
102       }else if(type == "[object Boolean]"){
103 
104         print += this._formatLiteral(obj, comma, indent, isArray);
105 
106       }else if(type == "[object Function]"){
107 
108         obj = this._formatFunction(obj);
109 
110         print += this._formatLiteral(obj, comma, indent, isArray);
111 
112       }else if(type == "[object Undefined]"){
113 
114         print += this._formatLiteral("undefined",comma, indent, isArray);
115 
116       }else{
117 
118         print += this._formatLiteral(obj, comma, indent, isArray);
119 
120       }
121 
122       return print;
123 
124     },
125 
126     /**
127     * [对文本内容进行格式化处理]
128     * @param  {[String]}  literal   [字符内容]
129     * @param  {[String]}  comma     [是否加逗号]
130     * @param  {[Number]}  indent    [缩进的Tab键数量]
131     * @param  {[Boolean]} isArray   [是否为数组]
132     * @return  {[String]} str       [格式化后的函数字符串]
133     */
134     _formatLiteral:function(literal, comma, indent, isArray){
135 
136       var str=literal+comma;
137 
138       if(isArray){
139         str = this._getRow(indent, str);
140       }
141 
142       return str;
143 
144     },
145 
146     /**
147     * [对函数进行格式化处理]
148     * @param  {[Function]} obj     [函数内容]
149     * @return  {[String]}   str    [格式化后的函数字符串]
150     */
151     _formatFunction:function(obj){
152 
153 
154       var str = "";
155       // 以换行符对每行进行分割
156       var funcStrArray = obj.toString().split("
");
157 
158       if(funcStrArray.length){
159 
160         // 重组每行内容,除末尾行之外,每行加上换行符
161         for(var i = 0; i < funcStrArray.length-1; i++){
162           str +=  funcStrArray[i] + "
";
163         }
164         return str+funcStrArray[i];
165       }else{
166         return str;
167       }
168 
169     },
170 
171 
172     /**
173     * [给每行加上缩进和换行]
174     * @param  {[Number]}  indent              [每行需要缩进的TAB数量]
175     * @param  {[String]}  data                [属性名]
176     * @param  {[Boolean]} isPropertyContent   [是否为属性内容]
177     * @return {[String]}  tabs+data           [格式化后的行内容]
178     */
179     _getRow:function(indent, data, isPropertyContent){
180 
181       var tabs = "";
182 
183       // 计算属性名称前面的tab键数量
184       for(var i = 0; i < indent && !isPropertyContent; i++){
185         tabs += this.tabKey;
186       }
187 
188       // 给属性名加上换行(内容不为空且末尾不含换行符)
189       if(data != null && data.length > 0 && data.charAt(data.length-1) != "
"){
190         data = data+"
";
191       }
192 
193       return tabs+data;
194 
195     },
196       /**
197       *
198       * Public methods
199       *
200       */
201     dump:function(object){
202 
203       try{
204         return this._printObject(object, 0, false, false, false) ;
205       }catch(e){
206         alert("object语法错误,不能格式化,错误信息:
"+e.message);
207       }
208 
209     },
210 
211   };
212 
213   if (typeof exports !== 'undefined'){
214       exports.printTree = printTree;
215   }else{
216       window.printTree = printTree;
217   }
218 
219 })(window);
View Code

 这个方法有个缺点是无法打印[HTML DOM Element],

比如一个实际的DOM 元素为

 <input type="email" name="email" id="email" placeholder="请输入企业邮箱账号">

通过上述方式打印出来的效果为图2所示。类似的工具还有vconsole

安装方法

npm install vconsole

使用方法---在需要调试的页面引入下面的js文件

<script src="path/to/vconsole.min.js"></script>
<script>
console.log('Hello world');
</script>


这个工具的实现原理,是很简单的函数劫持。其大概原理是创建一个和现有函数同名的函数(当然首先要把原来的函数给保存下来),以覆盖掉他原本的引用,然后在函数体内先针对参数做一些自己想要实现的功能,最后再调用之前保存的原函数,实现原本的功能。

vconsole工具重写了window.XMLHttpRequest和window.console方法

使用专业的工具 

使用 Weinre 调试

weinre全称是web insperctor remote,是一种远程调试工具,可以在PC上调试运行在移动设备上的远程页面。

Weinre 是一个相当简单好用的调试工具。它会在你本地创建一个监听服务器,并提供一个 JavaScript脚本,你只需要在需要测试的页面中加载这段 JS,就可以被 Weinre 监听到,在 inspect 面板中调试你这个页面。

 weinre工作原理

如上图, Weinre由三部分组成,第一部分是运行在PC上的Debug Server, 它会与其他两部分交互,在测试页引入的那个target.js文件就存在于这个Server里

 第二部分是Debug Client, 这个就是我们上面一直在使用的运行在chrome中的调试客户端,它与Debug Server进行连接,并提供调试接口给用户。

 第三部分是Debug Target, 也就是运行在我们远程设备浏览器中的target.js, 它通过XHR与Server连接交互,将我们的代码暴露给Server, 来实现DOM Inspection与修改。

   在命令行启动weinre就开启了Debug Server, 然后在浏览器中输入http://ip:weinre端口就打开了Debug Client, 在调试页面中嵌入target.js代码, 在手机中打开页面, 就开启Debug Target。

weinre 的具体使用方法如下:

首先全局安装 weinre:

npm install -g weinre

安装完成之后,在命令行下启动weinre监听服务器,启动监听服务器之前,需要获取本机的局域网地址:

  • Mac 在终端执行 ipconfig getifaddr en0 命令。
  • Win 在命令行执行 ipconfig 命令。

这时候拿到本机IP,比如我的机器IP 为 192.168.201.54,这时候执行:

weinre --boundHost 192.168.201.54 --httpPort 10000

weinre支持的参数有:

--help : 显示Weinre的Help

--httpPort   [portNumber] : 设置Weinre使用的端口号, 默认是8080

--boundHost  [hostname | ip address | -all-] : 默认是'localhost', 这个参数是为了限制可以访问Weinre Server的设备, 设置为-all-或者指定ip, 那么任何设备都可以访问Weinre Server。

--verbose   [true | false] : 如果想看到更多的关于Weinre运行情况的输出, 那么可以设置这个选项为true, 默认为false;

--debug   [true | false] : 这个选项与--verbose类似, 会输出更多的信息。默认为false。

--readTimeout   [seconds] : Server发送信息到Target/Client的超时时间, 默认为5s。

--deathTimeout   [seconds] : 默认为3倍的readTimeout, 如果页面超过这个时间都没有任何响应, 那么就会断开连接。

开启本地监听服务器

 复制http://192.168.201.54:10000,粘贴到浏览器地址栏,打开服务器网址,

复制监听脚本到需要被监听页面

当我们有真机访问被调试页面的时候,被调试页面就会出现在监听列表中,如果有多个网页,你可以从列表中选择一个。然后就可以使用后面的 Elements、Console 等面板来进行调试操作了:

首页RemoteTab由三部分组成, Targets是注册的远程设备列表, 当前我们还没有访问测试页面, 所以Targets列表为none, Clients是Weinre客户端, 也即打开这个Weinre页面的设备列表。ServerProperties就是我们运行Weinre时的一些配置项。

    weinre 非常灵活,跨平台(Android、iOS 、Window Phone 都支持),跨浏览器(chrome,safari,国内各种浏览器都可以用), 可以让我们在电脑上直接调试运行在手机上的远程页面。在调试移动设备时需要在本地搭建一个局域网 IP 的服务器,将设备与本机网络连接成一个局域网,用移动设备访问这个网页即可。当然 Weinre 也不是万能的,相比 Chrome 的调试工具,它缺少 JavaScript 断点以及 Profiles 等常用功能,但是它兼容性强,可以实现基础调试功能。

https页面调试

weinre兼容性挺强,而且能支持微信端页面的调试,到此为止,如果页面请求使用的是http,那weinre已经可以解决调试问题了。

但是如果要调试https请求的页面,仅仅使用weinre无法解决,因为在页面中需要引入调试的js文件,weinre启动的是http服务,于是使用反向代理软件ngrok,它可以做地址映射,并支持http/https/tcp等,使用也比较简单:

 

这是官网下载地址:https://ngrok.com/download,或者可以直接使用npm下载:

npm install -g ngrok

然后启动

ngrok http 192.168.201.549999

启动后可以打开 http://127.0.0.1:4040 查看连接信息:

在html中引入下面的js文件

         

注意:

1.绑定端口一定不能与本地环境已监听的端口冲突。本地我已监听了8080端口,所以我绑定的是10000端口。

2.boundHost默认为localhost,只能本地PC上用http://localhost:8080来访问,将localhost换做本地ip就无法打开Weinre调试工具,为了能在其他设备以及本地设备用ip打开Weinre调试工具,需要设置boundHost为"-all-"或者ip.

3.监听https页面时,要先启动weinre,再启动ngrok.

使用JSconsole

JSConsole相当于一个简化版的weinre,专注于console功能,它相对于Weinre的优点就是提供了现成了线上Debug Server与Debug Client, 无需用户在PC本地运行Debug服务, 只要在需要调试的页面像Weinre一样加入一个target库, 就可以在JSConsole官网上调试这个页面了。JSConsole的网址为https://jsconsole.com/。

 

接着在打开的网页输入 :listen,将会得到一串 GUID 以及一对带有 src 属性的 Javascript 标签,如下图:

将这个 Javascript 脚本插入到需要调试的 html 页面中,比如这样:

复制代码<script src="http://jsconsole.com/remote.js?BDA15940-A201-4EAB-9482-941CD41742EC"></script>
<script>
    var a = 1
      , b = 2;

    console.log(a + b);
</script>

然后刷新你本地需要调试的页面(PC端或者移动端),如果是第一次打开的话,会弹出下图内容,大概意思就是告诉你现在引入了 JSConsole 的一段 js 进行调试,记得在上线时将它移除。

在打开 JSConsole 的页面便会输出 console 的内容;如果页面 JS 报错,一般情况下也能在 JSConsole 中进行定位。

需要注意的是,刷新的是本地页面,而并不是 JSConsole 的页面,一旦刷新 JSConsole 的页面,便会生成一个新的 GUID,这样之前生成的就没用了,调试也就失效了

使用spy-debugger调试

1、一站式页面调试工具,远程调试任何手机浏览器页面,任何手机移动端webview(如:微信,HybirdApp,手机浏览器等)。 2、spy-debugger内部集成了weinre。 3、同时支持HTTP/HTTPS页面的调试。

安装

Windows 下

npm install spy-debugger -g

Mac 下

sudo npm install spy-debugger -g

安装后在命令行下输入 spy-debugger,启动代理服务器

  设置手机代理

 https页面调试

如果要监听https页面,你还需要做如下操作:

第一步:生成证书

spy-debugger initCA

// 证书生成在用户根目录的node-mitmproxy文件夹下的
// 如: /Users/wuchangming/node-mitmproxy

第二步:安装证书

把node-mitmproxy文件夹下的 node-mitmproxy.ca.crt 传到手机上,点击安装即可。

使用fiddler调试

   Fiddler是最强大最好用的Web调试工具之一,它能记录所有客户端和服务器的http和https请求,分析请求数据、设置断点、调试web应用、修改请求的数据,甚至可以修改服务器返回的数据. 使用Fiddler无论对开发还是测试来说,都有很大的帮助。

  Fiddler的工作原理

Fiddler 是以代理web服务器的形式工作的,它是在web server 和 client 之间搭了一层 proxy,所有的请求都会经过它。在打开它的那一瞬间,它就已经设置好了浏览器的代理了。它使用代理地址:127.0.0.1, 端口:8888.当你关闭的时候,它会自动注销代理服务,不过如果Fiddler非正常退出,会造成网页无法访问。   这是因为Fiddler没有自动注销,解决的办法是重新启动下Fiddler..

下载fiddler

Fiddler的基本界面

 fiddler图标介绍

每种图标代表不同的相应类型,具体的类型包括:

   

Inspectors面板

 Inspectors tab下有很多查看Request或者Response的消息。分为上下两个部分,上半部分是请求头部分,下半部分是响应头部分。对于每一部分,提供了多种不同格式查看每个请求和响应的内容。

JPG 格式使用 ImageView 就可以看到图片,

HTML/JS/CSS 使用 TextView 可以看到响应的内容。

其中Raw Tab可以查看完整的消息,

Headers tab 只查看消息中的header. 

Auth则可以查看授权Proxy-Authorization 和 Authorization的相关信息。

Cookies标签可以看到请求的cookie和响应的set-cookie头信息。

2.配置fiddler,让fiddler可以抓取https协议

默认情况下,fiddler是不会捕获https会话的,所以需要自行设置下。启动软件,点击tools->Fiddler Options,在弹出框选择“HTTPS”,如下页面,将捕获HTTPS连接这一项前面全打钩,点击ok即可操作成功。

Tools --> Telerik Fiddler Options -->  选择https,这个面板的配置项有:

Capture HTTPS CONNECTs:捕获https连接

Decrypt HTTPS traffic:解密HTTPS通信

Ignore servercertificate errors:忽略服务器证书错误

Fiddler中设置断点修改Request

Fiddler最强大的功能莫过于设置断点了,设置好断点后,你可以修改httpRequest 的任何信息包括host, cookie或者表单中的数据。设置断点有两种方法

第一种:打开Fiddler 点击Rules-> Automatic Breakpoint  ->Before Requests(这种方法会中断所有的会话)

如何消除命令呢?  点击Rules-> Automatic Breakpoint  ->Disabled

第二种:  在命令行中输入命令:  bpu www.baidu.com   (这种方法只会中断www.baidu.com)

如何消除命令呢?  在命令行中输入命令 bpu

Fiddler中设置断点修改Response

当然Fiddler中也能修改Response

第一种:打开Fiddler 点击Rules-> Automatic Breakpoint  ->After Response  (这种方法会中断所有的会话)

如何消除命令呢?  点击Rules-> Automatic Breakpoint  ->Disabled

第二种:  在命令行中输入命令:  bpafter www.baidu.com   (这种方法只会中断www.baidu.com)

如何消除命令呢?  在命令行中输入命令 bpafter,

AutoResponder面板

Fiddler比较重要且比较强大的功能之一。可用于拦截某一请求,并重定向到本地的资源,或者使用Fiddler的内置响应。可用于调试服务器端代码而无需修改服务器端的代码和配置,因为拦截和重定向后,实际上访问的是本地的文件或者得到的是Fiddler的内置响应。当勾选allow autoresponser 并设置相应的规则后(本例中的规则是将http://blog.csdn.net/ohmygirl的请求拦截到本地的文件layout.html),如下图所示

因此,如果要调试服务器的某个脚本文件,可以将该脚本拦截到本地,在本地修改完脚本之后,再修改服务器端的内容,这可以保证,尽量在真实的环境下去调试,从而最大限度的减少bug发生的可能性。

不仅是单个url,Fiddler支持多种url匹配的方式:

I. 字符匹配

如 example可以匹配 http://www.example.com和http://example.com.cn

II. 完全匹配

以EXACT开头表示完全匹配,如上边的例子

EXACT:http://blog.csdn.net/ohmygirl

III. 正则表达式匹配

以regex: 开头,使用正则表达式来匹配URL

如:regex:(?insx).*.(css|js|php)$  表示匹配所有以css,js,php结尾的请求url

Composer(构建请求)

老版本的fiddler中叫request-builder.顾名思义,可以构建相应的请求,有两种常用的方式构建请求:

(1)Parsed 输入请求的url之后executed即可,也可以修改相应的头信息(如添加常用的accept, host, referrer, cookie,cache-control等头部)后execute.

这个功能的常见应用是刷新页面的访问量

(2)Raw。使用HTTP头部信息构建http请求。与上类似。不多叙述

QuickExec命令行的使用

Fiddler的左下角有一个命令行工具叫做QuickExec,允许你直接输入命令。

常见得命令有

help  打开官方的使用页面介绍,所有的命令都会列出来    http://docs.telerik.com/fiddler/knowledgebase/quickexec
cls 清屏 (Ctrl+x 也可以清屏) select 选择会话的命令 ?.png 用来选择png后缀的图片 bpu 截获request

Fiddler的HTTP统计视图

通过陈列出所有的HTTP通信量,Fiddler可以很容易的向您展示哪些文件生成了您当前请求的页面。使用Statistics页签,用户可以通过选择多个会话来得来这几个会话的总的信息统计,比如多个请求和传输的字节数。

选择第一个请求和最后一个请求,可获得整个页面加载所消耗的总体时间。从条形图表中还可以分别出哪些请求耗时最多,从而对页面的访问进行访问速度优化

Filter面板

主机过滤规则;

第一个选项zone有三种选择:no zone;仅显示局域网主机;仅显示互联网主机;

第二个选项Host有四种选择,可以在下面文本框输入内容,根据输入内容显示或不显示相应的主机;

“No Host Filter”不设置hosts过滤
“Hide The Following Hosts”隐藏过滤到的域名
“Show Only The Following Hosts”只显示过滤到的域名
“Flag The Following Hosts”标记过滤到的域名

Action按钮可以保存过滤规则集,载入过滤规则集等;

客户端处理过滤规则;

可以选择仅显示来自某些进程的流量;仅显示chrome流量;

 

请求头过滤;可以选择

仅显示包含某些字符的URL;隐藏包含某些字符的URL;显示头部包含某些标志的包;

还可以删除和设置请求头;此功能尚未用过;待用过之后再发文详述;

 

断点设置;

在post时中断请求;

在带查询参数的GET请求时中断;

在异步请求时中断;

获取到某种类型的响应时中断;

 

根据响应码过滤;

隐藏成功的包;

隐藏不成功的包;

2字头的状态码,代表请求已成功被服务器接收、理解、并接受;

隐藏需要用户验证的请求;

隐藏重定向请求;

 

响应类型和尺寸过滤;

默认显示全部类型;其他还有image,html,texe/css,scripts等类型;

 

响应头过滤;

捕获设置了cookie的响应头;

捕获响应头带某些字符串的包等;

 

HTTP抓包分析工具有比较多,如wireshark, FireBug,HttpWatch,Tcpdump,PAW(mac)等。在做移动端开发时,找到一款合适自己的能进行移动设备HTTP抓包的工具也是非常重要的。正所谓,工欲善其事必先利其器。

个人非常喜欢Fiddler和spy-debugger,功能强大,简单易用。

原文地址:https://www.cnblogs.com/wangpenghui522/p/7190400.html