Selenium是ThoughtWorks公司研发的一个强大的基于浏览器的开源自动化测试工具,它通常用来编写web应用的自动化测试。早期也即Selenium1.x时期主要使用Selenium RC(Selenium Remote Control)进来自动化测试。Selenium2.x集成了Selenium和WebDriver的功能。
一、Selenium RC
1. Selenium RC的组成
下图所示为Selenium RC的工作原理:
(1)Selenium Server
Selenium Server负责控制浏览器行为,总的来说,Selenium Server主要包括3个部分:Launcher、Http Proxy、Selenium Core。其中Selenium Core是被Selenium Server注入到浏览器页面中的,它其实就是一堆Javascript函数的集合。自动化测试的过程是:Selenium RC启动一个Selenium Server,将操作web元素的API调用转化为一段段Javascript,在Selenium内核启动浏览器之后注入这段Javascript函数即Selenium Core,通过这些Javascript函数,我们才可以实现用程序对浏览器进行操作。(Javascript可以获取并调用页面的任何元素,自如的进行操作)
(2)Client Libraries
写测试用例时用来控制Selenium Server的库。测试用例通过调用Client Libraries来编写相关的代码。
2. Selenium RC与Testcase的关系
工作流程如下图所示:
具体过程为:
(1)测试用例通过Client Libraries的接口向Selenium Server发送Http请求,要求和Selenium Server建立连接
(2)Selenium Server的Launch启动浏览器,把Selenium Core加载入浏览器页面中,并发浏览器的代理设置为Selenium Server的Http Proxy。
(3)测试用例通过Client Libraries的接口向Selenium Server发送Http请求,Selenium Server对请求进行解析,然后通过Http Proxy发送JS命令通知Selenium Core执行操作浏览器的动作。
(4)Selenium Core接收到指令后,执行操作
(5)浏览器收到新的页面请求信息,于是发送Http请求,请求新的web页面。Selenium Server会接收到所有由它启动的浏览器发动的请求。
(6)Selenium Server接收到浏览器发送的Http请求后,自己重组Http请求,获取对应的web页面
(7)Selenium Server的Http Proxy把接收的Web页面返回给浏览器
3. Selenium RC的缺点
(1)Selenium RC不能处理本机键盘和鼠标事件
(2)Selenium RC不能处理弹出框、对话框(基本身份认证、文件上传/下载)事件
(3)Selenium RC使用Javascript注入技术,速度不够理想,稳定性大大依赖于Selenium内核对API翻译成的Javascript质量高低。
二、WebDriver
WebDriver提供了另外一种方式与浏览器进行交互。那就是利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)。由于使用的是浏览器的原生API,速度大大提高,而且调用的稳定性交给了浏览器厂商本身,显然是更加科学。然而带来的一些副作用就是,不同的浏览器厂商,对Web元素的操作和呈现存在不同程度的差异,这就要求Selenium WebDriver要分浏览器厂商的不同,提供不同的实现,例如Chrome有专门的ChromeDriver,Firefox有FirefoxDriver等等。
WebDriver Wire协议是通用的,也就是说不管是FirefoxDriver还是ChromeDriver,启动之后都会在某一个端口启动基于这套协议的Web Service。例如ChromeDriver初始化成功之后,默认会从http://localhost:46350开始,而FirefoxDriver从http://localhost:7055开始。后续我们调用WebDriver的任何API,都需要借助一个ComandExecutor发送一个命令,实际上是一个HTTP request给监听端口上的Web Service。在我们的HTTP request的body中,会以WebDriver Wire协议规定的JSON格式的字符串来告诉Selenium我们希望浏览器接下来做什么事情。
工作流程如下图所示:
具体过程如下:
(1)实例化WebDriver,Selenium首先会确认浏览器的native component是否存在可用而且版本匹配。若匹配则在目标浏览器里启动一整套Web Service。这套Web Service使用了Selenium自己设计定义的协议,名字叫做The WebDriver Wire Protocol。这套协议非常之强大,几乎可以操作浏览器做任何事情,包括打开、关闭、最大化、最小化、元素定位、元素点击、文件上传等等
(2)发送请求时,用WebDriver的HttpCommandExecutor类将命令转换为URL作为value,命令作为key一起存入map作为request,同时会在request的body中存放相应的By Xpath、id、name。实际发送的URL都是相对路径,后缀多以/session/:sessionId开头,这也意味着WebDriver每次启动浏览器都会分配一个独立的sessionId,多线程并行的时候彼此之间不会有冲突和干扰。比如我们常用到的find_element_by_class_name这个接口,会转化为/session/:sessionId/element这个url,然后在发出Http Request Body内再附上具体的参数,比如class name的值。比如我们要访问某一个网站,请求地址为:http://localhost:46350/session/e9c37ea97fe23d3026d06e4a5d109707/url,请求json内容:{"url":"http://www.qq.com","sessionId": "e9c37ea97fe23d3026d06e4a5d109707"}。比如查找一个xpath为//input[@type='text']的元素,请求地址后缀为/session/e9c37ea97fe23d3026d06e4a5d109707/element,json内容{"using": "xpath", "value": "//input[@type='text']", "sessionId": "e9c37ea97fe23d3026d06e4a5d109707"}。
(3)收到并执行了这个操作之后,也会回复一个Http Response。内容也是Json,会返回找到的element的各种细节,比如text、CSS selector、tag name、class name等等。比如:
{"sessionId":"XXXXX","status":0,"state":"success","value":{"ELEMENT":"2"},"class":"XXX","hCode":"XXX"}
我们可以把WebDriver驱动浏览器类比成出租车司机开出租车。
在开出租车时有三个角色:
乘客:他/她告诉出租车司机去哪里,大概怎么走
出租车司机:他按照乘客的要求来操控出租车
出租车:出租车按照司机的操控完成真正的行驶,把乘客送到目的地
在WebDriver中也有类似的三个角色:
工程师写的自动化测试代码:自动化测试代码发送请求给浏览器的驱动(比如火狐驱动、谷歌驱动)
浏览器的驱动:它来解析这些自动化测试的代码,解析后把它们发送给浏览器
浏览器:执行浏览器驱动发来的指令,并最终完成工程师想要的操作。
所以在这个类比中:
1. 工程师写的自动化测试代码就相当于是乘客
2. 浏览器的驱动就相当于是出租车司机
3. 浏览器就相当于是出租车
下面再从技术上解释下WebDriver的工作原理:
从技术上讲,也同样是上面的三个角色:
1. WebDriver API(基于Java、Python、C#等语言)
对于java语言来说,就是下载下来的selenium的Jar包,比如selenium-java-3.8.1.zip包,代表Selenium3.8.1的版本
2. 浏览器的驱动(browser driver)
每个浏览器都有自己的驱动,均以exe文件形式存在
比如谷歌的chromedriver.exe、火狐的geckodriver.exe、IE的IEDriverServer.exe
3. 浏览器
浏览器当然就是我们很熟悉的常用的各种浏览器。
那在WebDriver脚本运行的时候,它们之间是如何通信的呢?为什么同一个browser driver即可以处理java语言的脚本,也可以处理python语言的脚本呢?让我们来看一下,一条Selenium脚本执行时后端都发生了哪些事情:
对于每一条Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动
浏览器驱动中包含了一个HTTP Server,用来接收这些http请求
HTTP Server接收到请求后根据请求来具体操控对应的浏览器
浏览器执行具体的测试步骤
浏览器将步骤执行结果返回给HTTP Server
HTTP Server又将结果返回给Selenium的脚本,如果是错误的http代码我们就会在控制台看到对应的报错信息。
为什么使用HTTP协议呢?
因为HTTP协议是一个浏览器和Web服务器之间通信的标准协议,而几乎每一种编程语言都提供了丰富的http libraries,这样就可以方便的处理客户端Client和服务器Server之间的请求request及响应response,WebDriver的结构中就是典型的C/S结构,WebDriver API相当于是客户端,而小小的浏览器驱动才是服务器端。
那为什么同一个浏览器驱动即可以处理Java语言的脚本,也可以处理Python语言的脚本呢?
这就要提到WebDriver基于的协议:JSON Wire protocol。
JSON Wire protocol是在http协议基础上,对http请求及响应的body部分的数据的进一步规范。
我们知道在HTTP请求及响应中常常包括以下几个部分:http请求方法、http请求及响应内容body、http响应状态码等。
常见的http请求方法:
GET:用来从服务器获取信息。比如获取网页的标题信息
POST:向服务器发送操作请求。比如findElement,Click等
http响应状态码:
在WebDriver中为了给用户以更明确的反馈信息,提供了更细化的http响应状态码,比如:
7: NoSuchElement
11:ElementNotVisible
200:Everything OK
现在到了最关键的http请求及响应的body部分了:
body部分主要传送具体的数据,在WebDriver中这些数据都是以JSON的形式存在并进行传送的,这就是JSON Wire protocol。
JSON是一种数据交换的格式,是对XML的升级与替代,下面是一个JSON文件的例子:
{
"firstname": "Alex",
"lastname": "Smith",
"moble": "13300000000"
}
下面的例子是WebDriver中在成功找到一个元素后JSON Wire Protocol的返回:
{"status" : 0, "value" : {"element" : "123422"}}
所以在Client和Server之间,只要是基于JSON Wire Protocol来传递数据,就与具体的脚本语言无关了,这样同一个浏览器的驱动就即可以处理Java语言的脚本,也可以处理Python语言的脚本了。