概述
引入:
其实熟悉 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 对象中,那么自动化测试代码是:
sendKeys()方法如下:
当调用 sendKeys 方法的时候 ,它会吧我们要输入的内容值转为一个字符数组,这个很好理解,因为任何字符串都是一组键盘的输入的集合。 所以我们的 policyName的值被转为:
然后,它去在 87 行调用 execute() 方法,并且使用了 Command 设计模式,把我们的调用 sendKeys(String) 方法名转为了一个命令DriverCommand.SEND_KEYS_TO_ELEMENT( 也就是字符串"sendKeysToElement"), 然后把我们要发送的内容 keysToSend变量 通过ImmutableMap 进行打包 , 这样做的目的是为了让我们的输入的内容不可改变。
因为在我们例子中,我们使用的是 Linux 操作系统上的 Firefox 所以,它会调用FirefoxWebElement 上的 execute() 方法,并且我们的输入内容中被 ImmutableMap包装后加上了 WebElement 的 id 。
然后它接着会调用父类的 execute() 方法来完成这个操作:
这是最重要的方法,我们仔细分析:
从宏观上看,首先,在第 436 行会吧当前 WebDriver到它 启动的浏览器的所创建的session 对应的 sessionId, 以及命令字符串(也就是上文中传递过来的DriverCommand.SEND_KEYS_TO_ELEMENT字符串 常量),还有发送的字符串内容的包装体,都封装在一个 Command 对象中,封装后这个 Command 对象如下:
值得一提的是:这个 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()方法如下 :
而startSession会在开始就用Command模式,调用execute()方法创建一个新的session:
而这个execute方法,最终被HttpCommandExecutor来执行,和前面叙述一样,它会发送一个Http请求,并且所有请求细节都在Command对象中。 可以从下面调试信息看到,Command是如下的信息:
它的命令name是newSession,而sessionId为空,因为还没有创建嘛。
然后info对象中包含了要发送的请求url,这里可以看出,它发送到的请求url是/session
最后从httpMethod对象中,可以看出httpMethod用的是HttpPost
所以联系以上的信息就知道,在RemoteWebDriver中,其实它是以HttpPost方法发送了一个请求对象到/session中,并且请求对象中包含了命令"newSession"还有一些desiredCapabilities信息。
我们对比wire 协议:
正如协议中描述的,这个请求是用来创建一个新的session的,我们检查参数,请求类型,请求payload完全一致。
所以最后会发送此请求,发送完的response中会包含新创建的sessionId.
然后这个sessionId就可以作为每次发送请求到的目标浏览器的标识,从而保证每次请求的都是正确的浏览器了,当然,这个sessionId就必须被包含在每次请求中。
精华分析2:HttpCommandExecutor执行命令的细节.
我们看下 HttpCommandExecutor.execute() 方法:
首先,它会在第 279 行吧 command 的名字(命令名,也就是我们的DriverCommand.SEND_KEYS_TO_ELEMENT )转为一个 url 形式的命令。回想,我们用的是 REST ,所以命令也要用路径表达式的方式表现出来,转换后,CommandInfo 如下:
所以 sendKeysToElement 命令被转为 POST /session/:sessionId/element/:id/value的 url 形式。
我们对比 Wire 协议的说明:
所以,这里我们转换对了,的确我们 sendKeysToELement 的最终目的是发送一组键盘敲击动作序列到指定元素。
然后,它在第 281 行从刚才的 CommandInfo 对象中分离出 Http 动作 :
并且这个 getMethod 方法内部还会吧我们的 url 中由名字参数 (:sessionId),(:id) 表示的 url 全部替换为真实值,并且前面拼接上由 remoteServer 实例变量指定的服务器请求 url
因为从调试信息上看, info 中的动词 (verb) 的名字叫 ”POST”, 所以它最终会被转为httpMethod 为 HttpPost 。而这个 uri 被变量具体化后被转为:
这里可以看出(:sessionId),(:id)都被替换了,其中sessionId来自于浏览器的sessionId,具体可参见 精华分析1.
然后在 283 行吧 HttpPost 设置为 Http Accept 头。
接着 根据不同的 HttpMethod 进行不同的处理,因为我们的请求是 httpPost 请求,所以它会在第 286 行利用 BeanToJsonConverter() 吧我们封装在 Command 对象中的内容转成 json 格式的 payload ,并且接下来设置 payload 的编码格式以及 Content-Type 内容。
转换之后的json变为:
最后,在 297 行通过调用 fallbackExecute 来发送浏览器中,从这里可以看出,这个的确是一个 RESTful 的 Web Service 调用。
当处理完之后,其结果封装在 HttpResponse 对象中,我们要对它进行后处理,从调试信息看,这个 Response 是一个标准的 HttpResonse
我们发现了一个很有趣的东西,这里发现这个 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,然后包含在后续的所有操作中。
最后
以上就是粗暴雨为你收集整理的通过WebElement.sendKeys()来研究wire协议的全部内容,希望文章能够帮你解决通过WebElement.sendKeys()来研究wire协议所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复