# selenium的历史
1. selenium1.x:这个时候的selenium,使用的是JavaScript注入技术与浏览器打交道,需要Selenium RC启动一个Server,将操作Web元素的API调用转化为一段段Javascript,在Selenium内核启动浏览器之后注入这段Javascript。Javascript可以获取并调用DOM的任何元素,自如的进行操作。由此才实现了Selenium的目的:自动化Web操作。这种Javascript注入技术的缺点是速度不理想,而且稳定性大大依赖于Selenium内核对API翻译成的Javascript质量高低。
2. selenium2.x:相比于selenium1.x,2.x版本整合了webdriver以及原版selenium,两个项目合二为一,虽然名字还叫selenium,但也可以叫Webdriver。这个版本的selenium是利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)。由于使用的是浏览器原生的API,速度大大提高,而且调用的稳定性交给了浏览器厂商本身,显然是更加科学。然而带来的一些副作用就是,不同的浏览器厂商,对Web元素的操作和呈现多少会有一些差异,这就直接导致了Selenium WebDriver要分浏览器厂商不同,而提供不同的实现。
**发展史请看下图:**
![Alt](https://testerhome.com/uploads/photo/2017/548944bd-b5f6-4243-b74b-d2cd8dac7412.png!large)

# 以下进入正题
## 结构
要通过selenium实现自动化测试,最最主要是需要三种东西。
- 测试需要用的代码
- webdriver
- 浏览器
今天想要分享的也是这三者关系。

### 代码
selenium支持多种语言(java/c#/python/ruby)。测试工程师通过编程语言,调用浏览器对应API实现需要的功能。
### webdriver
webdriver,就像是一个媒介,代码驱动webdriver。上文提过,不同浏览器有不同的webdriver,例如火狐的FirefoxDriver,谷歌的 ChromeDriver.
### 浏览器

不同的浏览器对应不同的webdriver。

![](/uploads/photo/2019/6fe3febe-fb16-4417-9d54-921a703e15d6.png!large)

从上图,测试代码输入操作给webdriver,webdriver再去控制浏览器,最终达到的效果就是代码实现对浏览器的操作。

## 代码与webdriver的交互
以下python为例

```python
from selenium import webdriver
driver = webdriver.Chrome()
```

这里driver是webdriver.Chrome()的对象,我们查看webdriver.Chrome()的源码,发现本质是
**from .chrome.webdriver import WebDriver as Chrome** 从目录名可知这来自chrome的webdriver,再次对这个**WebDriver**溯源,发现它是继承了一个**RemoteWebDriver**类,注释的含义是:控制ChromeDriver并允许驱动浏览器。

![](/uploads/photo/2019/71df64b3-7bd5-4b95-a857-a3e940695839.png!large)

再次对继承的**RemoteWebDriver**类溯源,发现其继承了**selenium.webdriver.remote.webdriver.WebDriver**

![](/uploads/photo/2019/6816dc54-8320-40b3-8ae7-8dc40566ebcf.png!large)
注释的含义是:通过向远程服务器发送命令来控制浏览器。 该服务器应该运行WebDriver有线协议。这里先停一下,等会我们会再回来继续了解这个类。

以python为例,我们在在selenium库中,通过ID获取界面元素的方法是这样的:
```python
ele = driver.find_element_by_id('id')
```
以上同方法,对代码溯源,**find_elements_by_id**是**selenium.webdriver.remote.webdriver.WebDriver**类的实例方法。在代码中,我们直接使用的其实不是**selenium.webdriver.remote.webdriver.WebDriver**这个类,而是针对各个浏览器的webdriver类,例如webdriver.Chrome()。所以说在测试代码中执行各种浏览器操作的方法其实都是**selenium.webdriver.remote.webdriver.WebDriver**类的实例方法。接下来我们再深入**selenium.webdriver.remote.webdriver.WebDriver**类来看看具体是如何实现例如**find_element_by_id()**的实例方法的。

![](/uploads/photo/2019/cd6a3798-21dd-4c6e-a6f5-ad45abf92b14.png!large)

这个find_element方法最后调用了一个excute方法,我们再来看看这个excute方法:
```python
def execute(self, driver_command, params=None):
"""
Sends a command to be executed by a command.CommandExecutor.
发送一个命令由command.CommandExecutor执行。

:Args:
- driver_command: The name of the command to execute as a string.
- params: A dictionary of named parameters to send with the command.

:Returns:
The command's JSON response loaded into a dictionary object.
"""
if self.session_id is not None:
if not params:
params = {'sessionId': self.session_id}
elif 'sessionId' not in params:
params['sessionId'] = self.session_id

params = self._wrap_value(params)
response = self.command_executor.execute(driver_command, params)
if response:
self.error_handler.check_response(response)
response['value'] = self._unwrap_value(
response.get('value', None))
return response
# If the server doesn't send a response, assume the command was
# a success
return {'success': 0, 'value': None, 'sessionId': self.session_id}
```
正如注释中提到的一样,其中的关键在于
```python
response = self.command_executor.execute(driver_command, params)
```
一个名为**command_executor**的对象执行了**execute**方法。
名为**command_executor**的对象是**RemoteConnection**类的对象,并且这个对象是在新建**selenium.webdriver.remote.webdriver.WebDriver**类对象的时候就完成赋值的**self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)**。
结合**selenium.webdriver.remote.webdriver.WebDriver**类的类注释来看:**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**。
```python
def execute(self, command, params):
"""
Send a command to the remote server.

Any path subtitutions required for the URL mapped to the command should be
included in the command parameters.

:Args:
- command - A string specifying the command to execute.
- params - A dictionary of named parameters to send with the command as
its JSON payload.
"""
command_info = self._commands[command]
assert command_info is not None, 'Unrecognised command %s' % command
data = utils.dump_json(params)
path = string.Template(command_info[1]).substitute(params)
url = '%s%s' % (self._url, path)
return self._request(command_info[0], url, body=data)
```
这个方法有两个参数
- command
- params

**command**表示期望执行的指令的名字。打开**self._commands**这个dict,查看Command.FIND_ELEMENT的value.
指令的URL部分包含了几个组成部分:
- HTTP请求方法。WebDriver wire protocol中定义的指令是符合RESTful规范的,通过不同请求方法对应不同的指令操作。
- sessionId. sessionId表示了remote server和浏览器的一个会话,指令通过这个会话变成对于浏览器的一个操作。
- element 这一部分用来表示具体的指令。

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

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

## 总结
![](/uploads/photo/2019/ef6dc9df-864a-4ab3-b718-c9ad573bdc1b.png!large)

**初学者文中难免可能有疏漏之处,希望各位大佬指正**

通过源码看原理之 selenium的更多相关文章

  1. 通过源码看android系列之AsyncTask

    整天用AsyncTask,但它的内部原理一直没有特意去研究,今天趁着有时间,码下它的原理. 具体用法就不再说明,相信大家已经用得很熟练了,我们今天就从它怎么运行开始说.先新建好我们的AsyncTask ...

  2. 通过源码看android系列之multidex库

    我们在开发项目时,喜欢引入好多的第三方包,大大的方便了我们的开发,但同时,因为android方法总数的限制,不能超过65k,然而呢,随着我们的开发,65k最终还是会超过,所以,google就给出了这个 ...

  3. 通过源码了解ASP.NET MVC 几种Filter的执行过程

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...

  4. 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”

    通过源码了解ASP.NET MVC 几种Filter的执行过程   一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...

  5. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

  6. Kafka详解六:Kafka如何通过源码实现监控

    问题导读: 1.kafka的消费者组的消费偏移存储,kafka支持两个版本?        2.ConsumerOffsetChecker类的作用是什么?        3.Kafka如何通过源码实现 ...

  7. 通过源码安装PostgresSQL

    通过源码安装PostgresSQL 1.1 下载源码包环境: Centos6.8 64位 yum -y install bison flex readline-devel zlib-devel yum ...

  8. Linux下通过源码编译安装程序

    本文简单的记录了下,在linux下如何通过源码安装程序,以及相关的知识.(大神勿喷^_^) 一.程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件: ...

  9. 在centos6.7通过源码安装python3.6.7报错“zipimport.ZipImportError: can't decompress data; zlib not available”

    在centos6.7通过源码安装python3.6.7报错: zipimport.ZipImportError: can't decompress data; zlib not available 从 ...

随机推荐

  1. JavaScript Math.floor() 方法

    定义和用法: floor() 方法可对一个数进行下舍入. 语法: Math.floor(x); x:必须参数,可以是任意数值或表达式: 返回值: 小于等于 x,且与 x 最接近的整数. 说明: flo ...

  2. ssh scp命令详解

    --查看版本 $ ssh -V OpenSSH_3.9p1, OpenSSL 0.9.7a Feb 19 2003 --用SSH登录到远程主机 localhost$ ssh -l jsmith(用户名 ...

  3. TableLayout 里的TextView等组的LayoutParams参数问题

    TableLayout 里的TextView等组的LayoutParams参数不能是LinearLayout.LayoutParams这样来定义, 只能是用TableRow.LayoutParams ...

  4. Gym 101201J Shopping (线段树+取模)

    题意:给定 n 个物品,然后有 m 个人买东西,他们有 x 元钱,然后从 l - r 这个区间内买东西,对于每个物品都尽可能多的买,问你最少剩下多少钱. 析:对于物品,尽可能多的买的意思就是对这个物品 ...

  5. [转]使用WCF 4.0 构建 REST Service

    本文转自:http://www.cnblogs.com/lanvige/archive/2010/12/03/set_up_rest_service_with_wcf_4.html 用过一段时间的Ru ...

  6. [label][IDE] Develop Node.js Project With WebStorm

    WebStorm 是一个支持 Node.js,CoffeeScript, TypeScript, Dart, Jade, Sass, LESS and Stylus 这些最新 web 开发技术的集成开 ...

  7. Oracle EBS Export File Format

    Profile Option Name Site Application Responsibility Server Server Org User Remark Export MIME type t ...

  8. Bootstrap变形记

    bootstrap审美疲劳了,想个招换换样子,THINKING... 变形 >>> 哈,不用改已有代码,添加我的Harley.js即可,有空在玩... 真实好久不玩博客园了,200字 ...

  9. Kafka与.net core(一)安装

    1.安装JDK 目前官网不能直接下载,在网上找到1.8.0版本安装包下载到本地. 1.1.下载jdk并解压 [root@iz2zei2y693gtrgwlibzlwz java]# ls jdk1.. ...

  10. windows下简单验证码识别——完美验证码识别系统

    此文已由作者徐迪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 讲到验证码识别,大家第一个可能想到tesseract.诚然,对于OCR而言,tesseract确实很强大,自带 ...