# 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. 进入docker的4种方式

    在使用Docker创建了容器之后,大家比较关心的就是如何进入该容器了,其实进入Docker容器有好几多种方式,这里我们就讲一下常用的几种进入Docker容器的方法. 进入Docker容器比较常见的几种 ...

  2. 莫队算法详解和c实现

    解析和实现 摘要:        莫队算法是一个对于区间.树或其他结构离线(在线)维护的算法,此算法基于一些基本算法,例如暴力维护,树状数组,分块,最小曼哈顿距离生成树,对其进行揉合从而产生的一个简单 ...

  3. RocketMQ broker jvm 监控

    1. jps 获取要监控broker jvm 的进程ID jsp 2. nohup 输出监控日志 nohup jstat -gc -t [pid] [interval] -t 会在每一条记录前加时间戳 ...

  4. [Selenium With C#基础教程] Lesson-03 超级链接

    作者:Surpassme 来源:http://www.jianshu.com/p/83809943e751 声明:本文为原创文章,如需转载请在文章页面明显位置给出原文链接,谢谢. 超级链接或链接是We ...

  5. visual studio 2013 git 记住密码

    原有配置: C:\Users\Administrator 下.gitconfig内容为 [user] name = lijf4 email = lijf4@lenovo.com 删除,修改为 [cre ...

  6. lambda distinct

    public ActionResult Index() { IList<RegisterModel> regList = new List<RegisterModel>() { ...

  7. IPv4&&IPv6地址结构分析

    IPv4套接字地址结构: 套接字都需要有一个指向套接字地址结构的指针作为参数.每个协议簇都定义它自己的套接字地址结构.这些结构的名字均已sockaddr_开头,并以对应每个协议族的唯一后缀结尾. wi ...

  8. Buffer Pool--内存总结2

    按内存划分: 1.DATABASE CACHE 用于存放数据页面的缓冲区,8KB每页 2.各项组件 A)数据库连接(CONNECTION) B)通用数据,如果事务上下文,表和索引的元数据 C)执行计划 ...

  9. Programmatically Disable Event Firing on List Item Update in SharePoint 2010

    1. Microsoft.SharePoint.dll Create EventFiring.cs 1.Right-click on the project, select Add and click ...

  10. spring的父子上下文容器及配置

    本文由作者张远道授权网易云社区发布. spring父子容器 spring总的上下文容器有父子之分.父容器和子容器.父容器对子容器可见,子容器对父容器不可见. 对于传统的spring mvc来说,spr ...