WDA原理分析

简介: 1、什么是WDA WebDriverAgent是Facebook 在17年的 SeleniumConf 大会上推出了一款新的iOS移动测试框架。 下面摘录一段官方对于WebDriverAgent的介绍字段:(官方文档:https://github.com/facebook/WebDriverAgent) WebDriverAgent 在 iOS 端实现了一个 WebDriver server ,借助这个 server 我们可以远程控制 iOS 设备。

1、什么是WDA

WebDriverAgent是Facebook 在17年的 SeleniumConf 大会上推出了一款新的iOS移动测试框架。

下面摘录一段官方对于WebDriverAgent的介绍字段:(官方文档:https://github.com/facebook/WebDriverAgent

WebDriverAgent 在 iOS 端实现了一个 WebDriver server ,借助这个 server 我们可以远程控制 iOS 设备。你可以启动、杀死应用,点击、滚动视图,或者确定页面展示是否正确。This makes it a perfect tool for application end-to-end testing or general purpose device automation.(它说它是iOS上一个完美的e2e的自动化解决方案) It works by linking XCTest.framework and calling Apple’s API to execute commands directly on a device.(链接XCTest.framework调用苹果的API直接在设备上执行命令) WebDriverAgent is developed and used at Facebook for end-to-end testing and is successfully adopted by Appium. (Appium封装工作正在进行中,如果一旦封装好,那么以后就可以直接用Appium提供的binding了。)It is currently maintained by Marek Cirkos and Mehdi Mulani。

4ae2ab63efbdd14a71de6d572830b482e73a6a2e

2、简单原理图

WebDriver之所以能够实现与浏览器进行交互,是因为浏览器实现了Mobile JSON Wire Protocol Specification协议,这个协议是使用JOSN通过HTTP进行传输。

它的实现使用了经典的Server-Client架构(C/S),客户端发送一个requset,服务器端返回一个response。

6399320e8b509af1a32da9407640553c893f4d10
在开始下面的内部实现细节的讲解前,我们下明确几个概念:

1、WDAClient

WDAClient是基于WebDriverAgent实现的WDA的客户端。facebook-wda 就是 WDA 的 Python 客户端库,通过直接构造HTTP请求直接跟WebDriverAgent通信。

2、WDAServer

运行WDA App的机器,实现了WebDriver的通讯协议

3、Session

服务器端需要维护客户端的Session,客户端首次发送请求的字符串是'/session/sessionId/urlurlurlsessionId/url′。服务器端根据url打开对应的url地址,同时将sessionId解析成真实的值。然后返回给客户端。以后客户端再向浏览器发送请求时,将会携带session值一起发送。

1
2
3
4
5
6
7
8
[debug] [BaseDriver] Creating session with W3C capabilities: { "alwaysMatch" :{"platformNa...
[BaseDriver] Session created with session  id :  7a1d8eca - 8c48 - 4a94 - 8256 - ab283e2af4c3
 
[Appium] New AndroidUiautomator2Driver session created successfully, session  7a1d8eca - 8c48 - 4a94 - 8256 - ab283e2af4c3 added to master session  list
[debug] [BaseDriver] Event  'newSessionStarted'   logged at  1540198593998   ( 16 : 56 : 33   GMT + 0800   (中国标准时间))
[debug] [W3C] Cached the protocol value  'W3C'   for   the new session  7a1d8eca - 8c48 - 4a94 - 8256 - ab283e2af4c3
[debug] [W3C] Responding to client with driver.createSession() result: { "capabilities" :{ "platform" : "LINUX" , "webStorageEnabled" :false, "takesScreenshot" :true, "javascriptEnabled" :true, "databaseEnabled" :false, "networkConnectionEnabled" :true, "locationContextEnabled" :false, "warnings" :{}, "desired" :{ "platformName" : "Android" , "unicodeKeyboard" :true, "command_executor" : "http://127.0.0.1:4723/wd/hub" , "noReset" :true, "appActivity" : "com.didi.sdk.app.launch.DidiLoadDexActivity" , "automationName" : "uiautomator2" , "newCommandTimeout" : 300 , "deviceName" : "68de2f65" , "recreateChromeDriverSessions" : "true" , "platformVersion" : "7.0" , "appPackage" : "com.sdu.didi.psnger" }, "platformName" : "Android" , "unicodeKeyboard" :true, "command_executor" : "http://127.0.0.1:4723/wd/hub" , "noReset" :true, "appActivity" : "com.didi.sdk.app.launch.DidiLoadDexActivity" , "automationName" : "uiautomator2" , "newCommandTimeout" : 300 , "deviceName" : "68de2f65" , "recreateChromeDriverSessions" : "true" , "platformVersion" : "7.0" , "appPackage" : "com.sdu.didi.psnger" , "deviceUDID" : "68de2f65" , "deviceScreenSize" : "1440x2560" , "deviceScreenDensity" : 640 , "deviceModel" :"SM...
[HTTP] < - -   POST  / wd / hub / session  200   27897   ms  -   1238
1
2
3
[debug] [JSONWP Proxy] Matched  '/session'   to command name  'createSession'
[debug] [JSONWP Proxy] Proxying [POST  / session] to [POST http: / / localhost: 8200 / wd / hub / session] with body: { "desiredCapabilities" :{ "platform" : "LINUX" , "webStorageEnabled" :false, "takesScreenshot" :true, "javascriptEnabled" :true, "databaseEnabled" :false, "networkConnectionEnabled" :true, "locationContextEnabled" :false, "warnings" :{}, "desired" :{ "platformName" : "Android" , "unicodeKeyboard" :true, "command_executor" : "http://127.0.0.1:4723/wd/hub" , "noReset" :true, "appActivity" : "com.didi.sdk.app.launch.DidiLoadDexActivity" , "automationName" : "uiautomator2" , "newCommandTimeout" : 300 , "deviceName" : "68de2f65" , "recreateChromeDriverSessions" : "true" , "platformVersion" : "7.0" , "appPackage" : "com.sdu.didi.psnger" }, "platformName" : "Android" , "unicodeKeyboard" :true, "command_executor" : "http://127.0.0.1:4723/wd/hub" , "noReset" :true, "appActivity" : "com.didi.sdk.app.launch.DidiLoadDexActivity" , "automationName" : "uiautomator2" , "newCommandTimeout" : 300 , "deviceName" : "68de2f65" , "recreateChromeDriverSessions" : "true" , "platformVersion" : "7.0" , "appPackage" : "com.sdu.didi.psnger" , "deviceUDID" : "68de2f65" , "deviceScreenSize" : "1440x2560" , "deviceScreenDensity" : 640 ,"deviceMod...
[debug] [JSONWP Proxy] Got response with status  200 : { "sessionId" : "eef20bb5-f3ed-4cbc-9977-b32f8be4eea9" , "status" : 0 , "value" : "Created Session" }

4、WebElement

WebDriverAPI中的对象,代表页面上的一个DOM元素。

5、JsonWireProtocol

JsonWireProtocol(以下简称JWP)是通过使用webdriver与remote server进行通信的 web service 协议 。通过http请求,完成和remote server的交互。

6、Mobile JSON Wire Protocol Specification

移动端自动化协议

7、iOS Accessibility

3、执行流程

1.  启动webdriveragent

6295c3f199bed239fde51c98bb1bab6da2dec71a

2.  启动App

向WebdriverAgent发送post请求 ,请求参考WDA项目中 FBSessionCommands.m

请求地址:url=http://#{ip}:8100/session,WevDriverAgent会响应启动app,并返回session数据;

3. 启动app后,定位元素以及操作元素

定位元素 post请求:url+/session/element, 请求参数是定位元素标签以及值 参考 FBFindElementCommands.m;响应会返回elementId

操作*元素post请求:url+/session/element/id/* 参考项目中文件:/Commands/FBElementCommands.m 里面介绍了很多元素操作的方法 进行相应的转换即可。
c224f351902f3a4a72eeb99d28578ea014c34f10

4、测试代码与Webdriver的交互

接下来我会以获取界面元素这个基本的操作为例来分析两者之间的关系。
在测试代码中,我们第一步要做的是新建一个webdriver类的对象:

5a2530a8266addeed22e25d5e26fbddd36e39818

这里新建的driver对象是一个webdriver.Remote()类的对象,而webdriver.Remote()类的本质是

5bbb7426e8852dbd47e70a260db510f42fdc2e88

也就是一个来自Remote的WebDriver类。这个.remote.webdriver是继承了selenium.webdriver.remote.command

59d8c734228dbab33273f97469dcaa5fa4052a2e

以python为例,在selenium库中,通过ID获取界面元素的方法是这样的:

ac4f9db82f601e0717bd567b9a2e2e63aaade352

DATest对其进行二次封装后是这样的:

7d81d03c8a4af85c0060dbcfd9fa92fa56ddc476

find_elements_by_id是selenium.webdriver.remote.webdriver.WebDriver类的实例方法。在代码中,我们直接使用的其实不是selenium.webdriver.remote.webdriver.WebDriver这个类,而是针对各个浏览器的webdriver类,例如webdriver.Chrome()、webdriver.Remote()。
所以说在测试代码中执行各种浏览器操作的方法其实都是selenium.webdriver.remote.webdriver.WebDriver类的实例方法。


接下来我们再深入selenium.webdriver.remote.webdriver.WebDriver类来看看具体是如何实现例如find_element_by_id()的实例方法的。

通过Source code可以看到:

2db0bc099973c83108ed4c0414923d51732d06ce

这个方法最后call了一个execute方法,方法的定义如下:
8fcf4176d7ebabc0ecb6e2007c3e0ffbd83f2bba

如注释中提到的,其中的关键在于一个名为command_executor的对象执行了execute方法。

response = self.command_executor.execute(driver_command, params)

名为command_executor的对象是RemoteConnection类的对象,并且这个对象是在新建selenium.webdriver.remote.webdriver.WebDriver类对象的时候就完成赋值的

self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)


结合selenium.webdriver.remote.webdriver.WebDriver类的类注释来看:
4fc5d8c70f1a7067ed7b231729c3b4fff6174c13

WebDriver类的功能是通过给一个remote server发送指令来控制浏览器。而这个remote server是一个运行WebDriver wire protocol的server。而RemoteConnection类就是负责与Remote WebDriver server的连接的类。

可以注意到有这么一个新建WebDriver类的对象时候的参数command_executor,默认值='http://127.0.0.1:4444/wd/hub'。这个值表示的是访问remote server的URL。因此这个值作为了RemoteConnection类的构造方法的参数,因为要连接remote server,URL是必须的。

现在再来看RemoteConnection类的实例方法execute。

ff50cf9e56cb9e1e503b51d1caa2ad50642b0f37

这个方法有两个参数:

  • command
  • params

command表示期望执行的指令的名字。通过观察self._commands这个dict可以看到,self._commands存储了selenium.webdriver.remote.command.Command类里的常量指令和WebDriver wire protocol中定义的指令的对应关系。


55b1233cf75fa7ccf73aefa9cf703df1cdc7446c

以FIND_ELEMENT为例可以看到,指令的URL部分包含了几个组成部分:

  • HTTP请求方法。WebDriver wire protocol中定义的指令是符合RESTful规范的,通过不同请求方法对应不同的指令操作。

  • sessionId。Session的概念是这么定义的:

    The server should maintain one browser per session. Commands sent to a session will be directed to the corresponding browser.

    也就是说sessionId表示了remote server和浏览器的一个会话,指令通过这个会话变成对于浏览器的一个操作。

  • element。这一部分用来表示具体的指令。

selenium.webdriver.remote.command.Command类里的常量指令又在各个具体的类似find_elements的实例方法中作为execute方法的参数来使用,这样就实现了selenium.webdriver.remote.webdriver.WebDriver类中实现各种操作的实例方法与WebDriver wire protocol中定义的指令的一一对应。
selenium.webdriver.rmote.webelement.WebElement中各种在WebElement上的操作也是用类似的原理实现的。

实例方法execute的另一个参数params则是用来保存指令的参数的,这个参数将转化为JSON格式,作为HTTP请求的body发送到remote server。
remote server在执行完对浏览器的操作后得到的数据将作为HTTP Response的body返回给测试代码,测试代码经过解析处理后得到想要的数据。


bb9880e0ab73def3e4f54bfda1b886d44414642b
通过对python selenium库的分析,希望能够帮助大家对selenium和webdriver的实现原理有更进一步的了解,在日常的自动化脚本开发中更加快捷的定位问题和解决问题。


附录:
相关文章
|
6月前
|
缓存
KVCache原理简述
KVCache原理简述
195 0
|
6月前
|
存储 Dubbo 算法
SpringCloud原理分析 | 简介
SpringCloud原理分析 | 简介
87 0
|
3月前
|
存储 负载均衡 API
服务发现原理分析与源码解读
服务发现原理分析与源码解读
|
3月前
|
Prometheus 算法 Cloud Native
熔断原理分析与源码解读
熔断原理分析与源码解读
|
3月前
|
存储 前端开发 JavaScript
EaselJS 源码分析系列--第四篇
EaselJS 源码分析系列--第四篇
|
3月前
|
存储 缓存 前端开发
EaselJS 源码分析系列--第三篇
EaselJS 源码分析系列--第三篇
|
4月前
|
前端开发 Java 应用服务中间件
SpringBoot异步接口怎么实现?
### 前言 Servlet 3.0以前,每个HTTP请求由单一线程全程处理;3.0版本引入异步处理,允许提前释放容器线程,提升系统吞吐量。
|
存储 资源调度 Kubernetes
K8S | 核心应用原理分析
K8S作为开源的容器编排引擎,用来对容器化应用进行自动化部署、 扩缩和管理;
165 0
K8S | 核心应用原理分析
|
存储 应用服务中间件 API
Session使用和原理分析图与实现原理
Session使用和原理分析图与实现原理
179 0
|
XML 缓存 Java
十一.Spring源码剖析-事务源码之@Transactionl解析
在上一章我们分析了Spring的AOP的源码,本篇文章是对事务的源码分析,我们都知道事务的管理是基于AOP实现的,所以有了上一篇的铺垫这一章会比较简单一点。 事务的源码我会分两章写,一张写Transcational的解析,一张写事务的执行流程。先上一个图,待会儿可以根据这个图来看源码