通过WebElement.sendKeys()来研究wire协议

引入:

其实熟悉 selenium 的人肯定都对 wire 协议不陌生,因为我们知道,当我们在代码中使用 WebDriver API 做一些操作的时候,它最终会转为一个基于 wire 协议的命令(Command) 发送到浏览器,并且请求的内容都封装在 json 对象中 , 通过 WebService调用浏览器, 从而所有 WebDriver API 的调用都最后转为对浏览器的 Web Service 调用。

我们这里就通过最简单的输入文本内容 (WebElement.sendKeys(String)) 来研究下wire 协议。以及上述所有细节。

Wire 协议的参考规范如下:

http://code.google.com/p/selenium/wiki/JsonWireProtocol

粗略看了下,这个 wire 协议可强大了,几乎可以操作自然人对浏览器能做的任何事情,比如打开,关闭,点击,关闭,定位,上传文件,最大最小化等等。

它是一套基于 RESTful 风格的 web service.

调试实战:

比如说页面上有个输入框 id 叫那么叫 policy-name , 然后我们要输入的值在dataProvider 对象中,那么自动化测试代码是:

085750757.png

sendKeys()方法如下:

085908579.png

当调用 sendKeys 方法的时候 ,它会吧我们要输入的内容值转为一个字符数组,这个很好理解,因为任何字符串都是一组键盘的输入的集合。 所以我们的 policyName的值被转为:

090035463.png

然后,它去在 87 行调用 execute() 方法,并且使用了 Command 设计模式,把我们的调用 sendKeys(String) 方法名转为了一个命令DriverCommand.SEND_KEYS_TO_ELEMENT( 也就是字符串"sendKeysToElement"), 然后把我们要发送的内容 keysToSend变量 通过ImmutableMap 进行打包 , 这样做的目的是为了让我们的输入的内容不可改变。

因为在我们例子中,我们使用的是 Linux 操作系统上的 Firefox 所以,它会调用FirefoxWebElement 上的 execute() 方法,并且我们的输入内容中被 ImmutableMap包装后加上了 WebElement  id 

090313245.png

然后它接着会调用父类的 execute() 方法来完成这个操作:

090355732.png

这是最重要的方法,我们仔细分析:

从宏观上看,首先,在第 436 行会吧当前 WebDriver到它 启动的浏览器的所创建的session 对应的 sessionId, 以及命令字符串(也就是上文中传递过来的DriverCommand.SEND_KEYS_TO_ELEMENT字符串 常量),还有发送的字符串内容的包装体,都封装在一个 Command 对象中,封装后这个 Command 对象如下:

090550145.png

值得一提的是:这个 sessionId  WebDriver 每次启动浏览器时候分配的唯一的会话 id, 从而保证多线程并行运行时候不会出现问题,而总是吧请求发送到正确的浏览器所包含的 webservice 中。(关于这一点,我们在精华分析1中会讲到)

然后,它会在 446 行用 CommandExecutor  execute() 方法来执行 Command 从而吧命令发送到sessionId指定的浏览器 内含的 web service 服务中,最终它使用HttpCommandExecutor 来完成这个任务

(执行命令的细节,是我们所探索的最主要目的,它反映了基于wire协议的web service调用,这点我们在精华分析2中讲到)

最后,在第 455-456 行对于执行结果的返回进行一些后处理,于是你就可以在页面上看到自动化测试的动画了。

精华分析1:sessionId是如何产生的?

因为我们使用的是Firefox浏览器做的测试(其他浏览器也一样),当WebDriver启动浏览器的时候,它会调用 webDriver = new FirefoxDriver(firefoxBinary,firefoxProfile)方法,

因为 FirefoxDriver 继承自 RemoteWebDriver ,所以调用FirefoxDriver构造器时候会调用 RemoteWebDriver 的构造器, 其最后一行会调用startSession()方法如下 

091549861.png

而startSession会在开始就用Command模式,调用execute()方法创建一个新的session:

091938227.png

而这个execute方法,最终被HttpCommandExecutor来执行,和前面叙述一样,它会发送一个Http请求,并且所有请求细节都在Command对象中。 可以从下面调试信息看到,Command是如下的信息:

092956823.png

它的命令name是newSession,而sessionId为空,因为还没有创建嘛。

然后info对象中包含了要发送的请求url,这里可以看出,它发送到的请求url是/session

092557178.png

最后从httpMethod对象中,可以看出httpMethod用的是HttpPost

092717313.png

所以联系以上的信息就知道,在RemoteWebDriver中,其实它是以HttpPost方法发送了一个请求对象到/session中,并且请求对象中包含了命令"newSession"还有一些desiredCapabilities信息。

我们对比wire 协议:

093158381.png

正如协议中描述的,这个请求是用来创建一个新的session的,我们检查参数,请求类型,请求payload完全一致。

所以最后会发送此请求,发送完的response中会包含新创建的sessionId.

093503121.png

然后这个sessionId就可以作为每次发送请求到的目标浏览器的标识,从而保证每次请求的都是正确的浏览器了,当然,这个sessionId就必须被包含在每次请求中。

精华分析2:HttpCommandExecutor执行命令的细节.

我们看下 HttpCommandExecutor.execute() 方法:

093734559.png

首先,它会在第 279 行吧 command 的名字(命令名,也就是我们的DriverCommand.SEND_KEYS_TO_ELEMENT )转为一个 url 形式的命令。回想,我们用的是 REST ,所以命令也要用路径表达式的方式表现出来,转换后,CommandInfo 如下:

093834509.png

所以 sendKeysToElement 命令被转为 POST /session/:sessionId/element/:id/value url 形式。

我们对比 Wire 协议的说明:

093932205.png

所以,这里我们转换对了,的确我们 sendKeysToELement 的最终目的是发送一组键盘敲击动作序列到指定元素。

然后,它在第 281 行从刚才的 CommandInfo 对象中分离出 Http 动作 :

094014800.png

并且这个 getMethod 方法内部还会吧我们的 url 中由名字参数 (:sessionId),(:id) 表示的 url 全部替换为真实值,并且前面拼接上由 remoteServer 实例变量指定的服务器请求 url

因为从调试信息上看, info 中的动词 (verb) 的名字叫 ”POST”, 所以它最终会被转为httpMethod  HttpPost 。而这个 uri 被变量具体化后被转为:

094122702.png

这里可以看出(:sessionId),(:id)都被替换了,其中sessionId来自于浏览器的sessionId,具体可参见 精华分析1.

然后在 283 行吧 HttpPost 设置为 Http Accept 头。

接着 根据不同的 HttpMethod 进行不同的处理,因为我们的请求是 httpPost 请求,所以它会在第 286 行利用 BeanToJsonConverter() 吧我们封装在 Command 对象中的内容转成 json 格式的 payload ,并且接下来设置 payload 的编码格式以及 Content-Type 内容。

094325631.png

转换之后的json变为: 

094401997.png

最后,在 297 行通过调用 fallbackExecute 来发送浏览器中,从这里可以看出,这个的确是一个 RESTful  Web Service 调用。

094445356.png

当处理完之后,其结果封装在 HttpResponse 对象中,我们要对它进行后处理,从调试信息看,这个 Response 是一个标准的 HttpResonse

094524307.png

我们发现了一个很有趣的东西,这里发现这个 server  httpd.js ,这就说明,其实真正消费我们 Http 请求的是浏览器内置的一段 httpd.js 的脚本,这也和我们理论模型(浏览器包含了一段 js 来专门处理基于 wire 协议的请求)完全符合,可以猜想这段 js就是模拟输入值到输入框中的动画。

我在 selenium 官网找到了这个 js 文件,其内容在:http://code.google.com/p/selenium/source/browse/firefox/src/extension/components/httpd.js?spec=svn004f447f8b359859da694f79569d7e5b03470dd7&r=004f447f8b359859da694f79569d7e5b03470dd7

当我们拿到 Response 对象后,我们要进行后处理,我们后处理不感兴趣,就不分析了。

总结: 

从这里我们可以获取许多有用的信息

(1)从架构的角度来看,当我们用WebDriver API 调用来书写自动化测试的代码时候,最终这些方法调用都会被selenium框架内部转为一个基于wire协议的web service调用。采用的设计模式是Command模式,这个web service的服务端是在任何浏览器中都包含的,并且用于服务的其实是httpd.js这段代码。

(2)wire协议几乎可以模拟 自然人对浏览器能做的任何事情,比如打开,关闭,点击,关闭,定位,上传文件,最大最小化等等。 它是一套基于 RESTful 风格的 web service.

(3)每次web service调用的时候,都必须有一个sessionId作为请求url一部分,这个sessionId用于唯一标识请求要送到的浏览器,并且是唯一的uuid,从而保证在多线程环境中工作的正确性。它的产生在于初始化浏览器的WebDriver时候,会发送一个Command为newSession的web service到浏览器中,这个请求路径是/session,并且payload中包含了目标浏览器的desireCapability信息,这样,这个web service的调用就会返回一个sessionId,然后包含在后续的所有操作中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值