Appium 中 iOS 下的 Hybrid.docx
- 文档编号:26404411
- 上传时间:2023-06-19
- 格式:DOCX
- 页数:19
- 大小:25.79KB
Appium 中 iOS 下的 Hybrid.docx
《Appium 中 iOS 下的 Hybrid.docx》由会员分享,可在线阅读,更多相关《Appium 中 iOS 下的 Hybrid.docx(19页珍藏版)》请在冰豆网上搜索。
Appium中iOS下的Hybrid
Appium中iOS下的Hybrid
Android上的Hybrid的一些知识可以看下@qddegtya的文章:
webviewv36支持的RemoteDebugging特性以及与appium的关系chromemobileemulation及周边漫谈和相关应用+想法今天我们只说Appium中iOS下的Hybrid。
UIAutomation众所周知,Appium上的iOS自动化使用的是苹果自带的instruments的UIAutomation。
它可以操控继承自UIKIT的native的界面控件。
那对于webview(UIAWebView)里面的元素,会把一部分html元素隐射成native的元素,比如:
textFields/links/buttons。
我们可以从logElementTree方法中看下:
vartarget=UIATarget.localTarget();
varapp=target.frontMostApp();
varmWin=target.frontMostApp().mainWindow();mWin.textFields()["EnterURL"].textFields()["EnterURL"].tap();
mWin.textFields()["EnterURL"].textFields()["EnterURL"].setValue("");
mWin.buttons()["Go"].tap();varwebview=mWin.scrollViews()[0].webViews()[0];webview.textFields()[0].tap();
target.delay
(2);
UIATarget.localTarget().frontMostApp().keyboard().typeString("helloworld!
");
webview.buttons()["XX一下"].tap();webview.logElementTree();但是html的元素和场景都非常多,在复杂场景下UIAutomation就捉襟见肘了(当然暴力坐标系除外)。
那Appium是怎么做的呢?
Webkit远程调试协议在了解Appium怎么做之前,我们需要了解下Webkit远程调试协议。
用Chrome的人应该都知道ChromeDevTools。
很多人都以为它是Chrome的一个组件。
事实上开发者工具(DevTools)是一个独立的Web应用程序(HTML+CSS+Javascript),被集成在浏览器中,通过远程调试协议(remotedebuggingprotocol)和浏览器内核进行交互。
什么是远程调试协议?
远程调试协议基于WebSocket,利用WebSocket建立连接DevTools和浏览器内核的快速数据通道。
浏览器拥有多个Tab,并为每个Tab单独提供Websocket的EndpointURI每个DevTool实例只能检视一个Tab,即只能与一个Tab保持通讯我们先来体验一把远程调试彻底关闭当前Chrome进程打开调试接口open-aGoogle\Chrome--args--remote-debugging-port=9999在开启的浏览器里打开任意网页——在其他浏览器或者Chrome本身打开http:
//localhost:
9222点击XX,会得到类似这样的界面:
http:
//localhost:
9999/devtools/devtools.html?
ws=localhost:
9999/devtools/page/D85B7DF6-FF3D-490D-9770-B372C8F1F1C4注意看地址栏,我们访问的是一个标准的HTTP协议下的网页,不是chrome的私有协议,其中ws=localhost:
9999/devtools/page/D85B7DF6-FF3D-490D-9770-B372C8F1F1C4告诉你连接的Websocket。
我们打开这个页面的devtools界面。
cmd+option+i从整个调试过程中的Websocket通讯可以看出,这个接口里面有两种通讯模式:
request/response:
就如同一个异步调用,通过请求的信息,获取相应的返回结果。
这样的通讯必然有一个messageid,否则两方都无法正确的判断请求和返回的匹配状况。
request:
{"id":
1,"method":
"Page.canScreencast"}
response:
{"id":
1,"result":
{"result":
false}}notification:
和第一种不同,这种模式用于由一方单方面的通知另一方某个信息。
和“事件”的概念类似。
{"method":
"Network.loadingFinished","params":
{"requestId":
"14307.143","timestamp":
1424097364.31611,"encodedDataLength":
0}}域远程调试协议把操作划分为不同的域domain,比如DOMDebuggerNetworkConsoleTimeline可以理解为DevTools中的不同功能模块。
每个域(domain)定义了它所支持的command和它所产生的event(就是上面讲的两种通讯方式)。
每个command包含request和response两部分,request部分指定所要进行的操作以及操作说要的参数,response部分表明操作状态,成功或失败。
command和event中可能涉及到非基本数据类型,在domain中被归为Type,比如:
'frameId':
<FrameId>,其中FrameId为非基本数据类型。
至此,不难理解:
domain=command+event+type远程调试协议结构command结构如下:
Page.navigate
request:
{
"id":
<number>,
"method":
"Page.navigate",
"params":
{
"url":
<string>
}
}
response:
{
"id":
<number>,
"error":
<object>
}执行Page.navigate操作,需要参数url,id可以随意指定,不过要确认全局的唯一性,因为需要通过id关联request和response。
event结构如下:
Page.loadEventFired
{
"method":
"Page.loadEventFired",
"params":
{
"timestamp":
<number>
}
}Pagedomain派发loadEventFired事件结构数据(通过WebSocket的onmessage获取),并包含参数timestamptype结构如下:
Frame:
object
id(string)
Frameuniqueidentifier.
loaderId(Network.LoaderId)
Identifieroftheloaderassociatedwiththisframe.
mimeType(string)
Framedocument'smimeTypeasdeterminedbythebrowser.
name(optionalstring)
Frame'snameasspecifiedinthetag.
parentId(optionalstring)
Parentframeidentifier.
securityOrigin(string)
Framedocument'ssecurityorigin.
url(string)
Framedocument'sURL.Frametype为包含id,loaderId,mimeType,name,parentId,securityOrigin和url字段的Object数据类型,其中loaderId为另外一个定义在Networkdomain中的type简单来说,远程调试协议就是利用WebSocket建立连接DevTools和浏览器内核的快速数据通道。
那么我们也可以自己打开这个websocket,遵从它的协议来发送消息。
回到之前我们打开的http:
//localhost:
9999,返回的是一系列的tab,其实还可以返回json数据——http:
//localhost:
9999/json。
这是一个数组,每个数组元素都是一个页面的信息,数组按照最近刷新时间排序。
[
{
description:
"",
devtoolsFrontendUrl:
"/devtools/devtools.html?
ws=localhost:
9999/devtools/page/72F441DF-6CAB-4D6B-82FD-F264154C7FBD",
id:
"72F441DF-6CAB-4D6B-82FD-F264154C7FBD",
thumbnailUrl:
"/thumb/72F441DF-6CAB-4D6B-82FD-F264154C7FBD",
title:
"Inspectablepages",
type:
"other",
url:
"http:
//localhost:
9999/",
webSocketDebuggerUrl:
"ws:
//localhost:
9999/devtools/page/72F441DF-6CAB-4D6B-82FD-F264154C7FBD"
},
{
description:
"",
devtoolsFrontendUrl:
"/devtools/devtools.html?
ws=localhost:
9999/devtools/page/D9509075-5DF6-4DEA-B590-0025C2D957FC",
id:
"D9509075-5DF6-4DEA-B590-0025C2D957FC",
title:
"打开新的标签页",
type:
"page",
url:
"http:
//localhost:
9999/json",
webSocketDebuggerUrl:
"ws:
//localhost:
9999/devtools/page/D9509075-5DF6-4DEA-B590-0025C2D957FC"
},
....
....
]我们可以通过json数据得到websocket的url,从而创建自己的通道,比如:
#!
/usr/bin/envnodevarrequest=require("request");request("http:
//localhost:
9999/json",function(e,res,data){
data=JSON.parse(data);
varurl=data[0].webSocketDebuggerUrl;//得到首个tab的websocket的链接
if(!
url){
thrownewError("nourl");
}varcommands=[
'{"id":
1,"method":
"Network.enable","params":
{}}',
'{"id":
2,"method":
"Page.enable","params":
{}}',
'{"id":
3,"method":
"Page.navigate","params":
{"url":
""}}'//跳转淘宝
]
varWebSocket=require('ws');
console.log('open'+url);
varws=newWebSocket(url)
console.log('opened'+url);
varcount=0ws.on('open',function(){
console.log('connected');
if(count<commands.length){
console.log('send'+commands[count])
ws.send(commands[count++])
}
});
ws.on('close',function(){
console.log('disconnected');
ws.close()
});
ws.on('message',function(data,flags){
console.log('recv%s',data)
if(count<commands.length){
console.log('send'+commands[count])
ws.send(commands[count++])
}else{
//ifwesentPage.enable,wecouldlistenforPage.loadEventFiredto
//seeifournavsucceeded.
ws.close()
}
});
});该脚本通过远程调试协议,使浏览器跳转,脚本亲测可用。
Safari远程调试服务讲了那么多,终于到苹果了。
Safari6(只能OSX)之后,你不但可以使用Safari开发者工具远程调试iOS设备上的移动Safari网页,而且可以远程调试任何UIWebView里的网页,比如PhoneGap应用。
Safari的远程调试中使用到的调试协议,与Google开放的ChromeDebugProtocol有牵丝万缕的关系:
这两套协议本质都是WebkitDebugProtocol的衍生产物。
大部分的实际功能是一模一样的,例如DOM检视、网络请求监控、Console等。
自从Google与Webkit项目撇清关系,分道扬镳后生下亲儿子blink后,自己的调试功能越来越强大,并逐渐产生了一些和Webkit调试协议不一样的功能,故自成一套ChromeDebugProtocol。
AppleSafari虽然主导Webkit,但是在实际产品使用中,却并没有直接使用WebkitDebugProtocol,不仅仅使用了binaryplist来作为序列化方法,还抛弃了websocket的通讯手段。
没有文档、没有代码,我们姑且叫这个不舍得露面的东西叫做Safari调试协议吧。
webinspectord和lockdown协议webinspectordislaunchedbySafariwhentheDeveloppermenuisenabled.webinspectord是一个守护进程,负责远程调试的通讯。
这个进程暴露了一个服务接口,供外部应用(例如桌面端的Safari调试工具)使用。
iOS上所有的服务(文件浏览、消息推送、app安装等)都是使用lockdown协议连接上的。
调试工具透过Usbmux,再通过lockdown协议,连接到webinspector服务。
由于苹果的封闭,所以基本上没有资料可查,大家可以去http:
//www.libimobiledevice.org看看这些接口的开源实现,另外也可以去看看。
Safari远程调试服务概述抛开Usbmux和lockdown协议不谈,Safari远程调试服务所使用的协议本身其实就是Webkit调试协议的二次包装。
也就是共享了Webkit调试协议的大部分功能。
先分析这个协议里面的主体:
iOS设备,iPad、iPhone等物理设备UDID:
以40位UDID字符串唯一识别一个设备Application,iOS设备上运行的开启了WebView应用程序。
设备上可以同时运行多个ApplicationIdentifier,应用标示符BundleIdentifier,应用的mainbundle标示符Page,每个Application可以打开多个页面Identifier,页面标示符TitleURL还有一些概念字段:
ConnectionId,标示当前连接到webinspector服务的连接SenderId,标示请求方(例如Devtool)实体大体可以看出,这个调试服务的接口是有状态的。
设备和Safaridevtool建立连接后,拥有可以复用的链接作为后续通讯的通道。
假设我们需要发送一条调试指令到iOS上的某个Webkit内核,它的整个编码流程应该是是这样:
将Webkit能识别的消息对象转化为JSON字符串构建Safari调试协议中使用的bplist消息体,来包装之前得到的字符串。
这里用到的selector就是_rpc_forwardSocketData将bplist消息体通过socket传输到iOS上的调试服务iOS上调试服务识别消息,并解析bplist,得倒Webkit能识别的消息对象将上一步得倒的消息对象传输给Webkit所有的消息,都是以Apple自己定义的RPC消息提格式进行的。
但它实际传输的有效数据,还是Webkit能够识别的指令。
另外,Safari没有如同Chrome那样,使用了websocket作为暴露出去的应用层协议。
它选择了最基本的socket通讯方式和bplist作为传输格式。
调试消息过程下面以一个具体的消息作为例子,来说说这整个过程。
JSON消息和Safari的RPC协议假设我们要开启网络监控这个面板,需要发送一个JSON指令。
指令序列化之后我们得到:
{"id":
0,"method":
"Network.enable"}就像之前说的,Safari调试协议不直接使用JSON字符串作为传输的序列化方案。
Safari远程调试协议有自己的RPC规范,所有的消息都都有__selector和__arguments两个字段:
前者说明调用的方法,后者说明调用时的参数。
常见的一些方法(其实是ObjCselector的字符串表达)如下:
_rpc_reportIdentifier:
:
向webinspector服务注册当前链接(传输connectionId)_rpc_getConnectedApplications:
:
要求获取连接到webinspector的iOS应用列表_rpc_forwardGetListing:
:
获取某个应用的页面列表(传输connectionId,appId)_rpc_forwardSocketSetup:
:
注册当前会话(传输connectionId、senderId)_rpc_forwardSocketData:
:
利用某个会话传输数据(传输connectionId、senderId、data)。
Webkit调试协议所传输的JSON就是通过这个方法传递的——JSON字符串的二进制表达被通过这个接口传递到iOS设备上的调试服务。
另一方面,iOS端也会传过来很多消息,同样遵循基本消息提的格式,常见的__selector有:
_rpc_reportConnectedApplicationList:
:
回报连接到webinspector的应用列表_rpc_applicationSentListing:
:
回报某个应用的页面列表_rpc_applicationConnected:
:
某个iOS应用连接到了调试服务_rpc_applicationDisconnected:
:
某个iOS应用从调试服务断开Safari不选择websocket作为传输协议应该是从安全性、复杂性的角度去考虑。
而选择bplist作为传输格式是由于lockdown协议的关系。
JSON到plist/bplist的转换plist和bplist都是Apple的通讯格式。
其中plist非常常见。
加入你做过iOS或者Mac开发,你一定写过不少plist。
plist就是一种拥有自有DTD的XML文档类型。
说白了,它就是XML文档。
例如之前的JSON指令,转换为Safari调试协议能够理解的plist文档:
<?
xmlversion="1.0"encoding="UTF-8"?
>
<!
DOCTYPEplistPUBLIC"-//Apple//DTDPLIST1.0//EN""
<plistversion="1.0">
<dict>
<key>__selector</key>
<string>_rpc_forwardSocketData:
</string>
<key>__argument</key>
<dict>
<key>WIRConnectionIdentifierKey</key>
<string>e0e68c53-5cc9-4dd4-9ebb-a7e69e98ef74</string>
<key>WIRApplicationIdentifierKey</key>
<string>PID:
1300</string>
<key>WIRPageIdentifierKey</key>
<integer>1</integer>
<key>WIRSenderKey</key>
<string>50c2e189-a91f-4df5-b33a-741225e9bd85</string>
<key>WIRSocketDataKey</key>
<data>eyJpZCI6MCwibWV0aG9kIjoiTmV0d29yay5lbmFibGUifQ==</data>
</dict>
</dict>
</plist>可以看到plist拥有多种标签来定义数据类型,例如di
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Appium iOS 下的 Hybrid