Selenium 工做原理

Selenium 的发展经历了三个阶段:Selenium Core、Selenium RC 和 Selenium WebDriver。本文将依次介绍每一个阶段的工做原理,若有错误,请及时指正。html

提示:Selenium Core 用户不直接接触,而 Selenium RC 已通过时,不感兴趣的同窗能够直接看第三节 Selenium WebDriver。

术语列表:java

术语 全称 中文全称/简介
AUT Application Under Test 被测应用
Selenium Selenium 一款跨浏览器自动化工具
Selenium Core Selenium Core Selenium 第一代版本,简称 Selenium 1.0,于 2004 年发布
Selenium RC Selenium Remote Control 基于 Selenium Core 的改进版,属于 Selenium 1.0
WebDriver WebDriver 和 Selenium Core 相似的跨浏览器自动化工具,于 2007 年首次发布源码
w3c WebDriver w3c WebDriver 由 WebDriver 发展而来的浏览器自动化协议,有时简称为 WebDriver 协议/规范/API
Selenium WebDriver Selenium WebDriver 2009年8月由 Selenium 1.0 和 WebDriver 项目合并而成,遵循 w3c WebDriver 协议,早期又称做 Selenium 2.0
Driver Driver/Browser Driver 浏览器驱动,实现了 w3c WebDriver 接口,每一个浏览器都有本身的驱动程序
DriverService DriverService 驱动服务,运行驱动程序后所起的 HTTP 服务,接收 WebDriver 接口调用后操做浏览器

Selenium Core

在 2004 年 Internet Explorer 有 93.25% 的市场占有率,当时的开源测试工具要么关注单个浏览器的测试(如 IE),要么是浏览器模拟工具(如 HttpUnit )。没有开源自动化测试工具能支持多浏览器的测试,手工测试执行须要耗费大量的时间和精力。nginx

提示:HttpUnit 是浏览器模拟工具,测试脚本与 HttpUnit 交互,HttpUnit 和真实服务端交互,不通过真实的浏览器,即不是模拟用户操做真实的浏览器。

初步想法

不过全部浏览器都支持 JavaScript,这为开发支持多浏览器的自动化工具提供了可能。受 Fit: Framework for Integrated Test 启发,ThoughtWorks 的 Jason Huggins 和他的团队想到了一个方案,使用基于表格的关键字驱动语法来编写测试用例。相比原生的 JavaScript 脚本,用户只须要有限的编程能力,另外用例也更容易理解和方便维护。git

基于表格的关键字语法(一个典型的登陆操做):github

Command Target Value
open http://example.com/page1.html
type username testUser
type password testPasword
clickAndWait submitButton
verifyTextPresent Welcome, testUser!
  • 第一列:命令的名称,如 open、type、click、submit 等等。
  • 第二列:目标,如元素定位符、URL 等。
  • 第三列:可选的值(如 click 命令不须要值,type 命令可能须要输入一些值)。

如何实现

开发团队里有几位成员对 Closure Library 很是熟悉,并且 Closure Library 编译的惟一输出语言是 JavaScript 。因此使用 Closure Library 来开发 Selenium Core 是个合适的选择。web

与不少大型项目同样,Selenium Core 采用了分层构建的架构。
最底层是 Google 开源的 Closure Library,它是一个模块化的、跨浏览器的底层 JavaScript 库,模块化使每一个源文件能够聚焦某个功能而且尽量小,跨浏览器能够帮其屏蔽掉不少浏览器兼容性问题。
中间层是一个包含大量函数的工具层,提供了从简单到复杂的操做,如:获取元素属性值、判断一个元素对用户是否可见、使用合成事件模拟用户点击。该层能够被看做是浏览器自动化的最小操做单元,所以也被称为浏览器自动化原子 Atomsatoms
最上层是由 Atoms 组成的适配层,用于实现 Selenium Core 约定的 API,即前面表格里的命令。chrome

至此,只要将表格内容解析为 Selenium Core 的 API,一个完整的跨浏览器自动化工具就实现了。编程

用于实战

Selenium Core 使用纯 JavaScript 编写,而 JavaScript 存在同源策略的问题,所以使用时须要将 Core 和测试用例部署在与被测应用相同的服务器上(只要被测应用和测试脚本同源就能够)。这也意味着,你没法测试别人的网站,好比 https://www.baidu.comsegmentfault

同源策略:跨域

Selenium Core 的使用步骤:

  1. 下载 Selenium Core 的压缩文件并解压,如:selenium-core-1.0.1.zip
  2. 复制 core 文件夹到应用服务器的目录。
  3. 访问 http://<webservername>:<port>/[path/]core/TestRunner.html 页面运行测试。被测应用有两种运行方式,单窗口模式:在 TestRunner 页面下方使用 iframe 嵌入被测应用,多窗口模式:在新窗口中打开被测应用。访问 https://www.qadoc.org/edu/cat... 查看示例。
扩展:使用 HTA 模式(将 TestRunner.html 重命名为 TestRunner.hta )能够测试其余网站,但 HTA 仅支持 Windows 上的 IE 浏览器。

最后总结一下:

  1. 在与 core 文件夹同级的 tests 文件夹下,使用 HTML 编写基于表格的测试用例页面,并加入测试套件页面。
  2. 部署 coretests 文件夹到应用服务器的目录,避开同源策略限制,也所以没法测试其余非同源网站,包括页面跳转后的非同源网站。
  3. 访问 http://<webservername>:<port>/[path/]core/TestRunner.html 页面准备运行测试。
  4. 选择一个测试套件或测试用例,点击 Run All testsRun the Selected test 运行测试用例。
  5. Selenium Core 获取测试套件的全部测试用例或当前测试用例,解析表格的每一行(固定三列),按行依次调用 Selenium Core API 执行。
  6. 每一个 API 调用 Atoms 层函数,Atoms 层调用 Closure Library 函数,由于 Closure LibraryJavaScript 都是跨浏览器的,因此 Selenium Core 支持多个浏览器。
扩展:虽然 Selenium Core 自己受限于同源策略,没法测试其余非同源网站,但幸运的是,浏览器插件的 JavaScript 代码并无这个限制,因此基于 Selenium Core 的 Selenium IDE 支持任何网站的测试。

Selenium RC

上节咱们了解到,Selenium Core 虽然知足了跨浏览器自动化的基本需求,但仍然有一些不足:

  • 使用 HTML 编写测试用例,因为没有专门针对 HTML 的用例编写工具,当用例规模较大时,编写和维护都是一件头疼的事。
  • 测试工具和测试用例须要部署在应用服务器上,这对被测应用有必定的侵入性,另外不是全部状况下都能在应用服务器上部署测试代码。
  • 没法测试与被测应用非同源的网站,这个缺点在今天,显得更为明显,现今很多网站都流行使用子域名,这致使页面跳转后将没法继续执行测试。

淘宝部分子域名示例:

为了解决这些问题,Selenium RC 随之诞生。

如何避开同源策略

为了解决上面的问题,开发团队为 Selenium 写了一个 HTTP 代理服务器,这样 Selenium 能够拦截一切 HTTP 请求。

HTTP 代理: HTTP 代理原理及实现(一)

关于 Selenium RC 架构,官网有详细的描述,这里偷下懒,作下翻译和补充。

代理注入

下载解压后,启动 Selenium RC Server:

java -jar selenium-server.jar -interactive -browserSideLog -proxyInjectionMode -debug -log seleniumrc.log

当咱们执行测试用例(使用编程语言编写的测试脚本)时,下面的步骤将发生:

  1. 客户端驱动(即 Selenium 的某个编程语言的客户端库)与 Selenium RC 服务器创建链接。
  2. Selenium RC 服务器使用特定的 URL 启动浏览器(或重复使用以前的浏览器实例),该页面引入了 Selenium-Core 的全部文件(JS、CSS 文件)。
  3. 客户端驱动经过 HTTP 发送一条 Selenium 命令给 RC 服务器。
  4. 服务器解释命令,并触发相应的 JavaScript 在浏览器中执行命令。第一条命令,一般是 open AUT 的一个页面。
  5. 浏览器收到 open 请求后,向 RC 服务器(做为浏览器的代理)请求页面内容。
  6. RC 服务器向 Web 服务器(AUT 的服务器)请求页面,请求完成后,RC 服务器将 Selenium Core 注入该页面并发送给浏览器(这让 Selenium Core 看起来和该页面同源)。
  7. 浏览器接收该页面并在 frame 或 window 中渲染页面。

补充几点官网没有解释的内容:

Selenium RC 如何启动浏览器

使用命令行方式启动浏览器,启动命令的格式以下:

# 格式:浏览器程序文件路径 + 命令参数

# 示例
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
--disable-hang-monitor
--disable-metrics
--disable-popup-blocking
--disable-prompt-on-repost
--proxy-server="localhost:4444"
--start-maximized
--user-data-dir="C:\Users\Think\AppData\Local\Temp\customProfileDir7a0b6e794a9445338159f582e1b8e3aa"
http://oktools.xyz/selenium-server/core/RemoteRunner.html?sessionId=7a0b6e794a9445338159f582e1b8e3aa&multiWindow=false&baseUrl=http%3A%2F%2Foktools.xyz%2F&debugMode=true

更多细节能够翻阅 org.openqa.selenium.server.browserlaunchers.AbstractBrowserLauncher#launch 实现类对应的方法。

Selenium RC 如何和浏览器通讯

这个问题乍看起来好像不是问题,但仔细想,浏览器并非服务端,即没有处理请求的能力,因此 RC 没法通知浏览器。有人可能会想到使用 websocket,但 websocket 2011 年才成为浏览器标准,而在 2004 年的时候,Selenium RC 显然还没法使用它。

当时有一种叫 Comet 的技术,Selenium RC 最终选用的是 XMLHttpRequest 长轮询,用于保持和浏览器的链接。下图是一个简单的示例,客户端(图示中使用的是 Selenium Sever 的交互模式,功能和客户端相同)前后发起了两个命令:getNewBrowserSessionopen

服务器收到 getNewBrowserSession 命令后,启动浏览器,浏览器访问 RemoteRunner.html,该页面经过 script 等标签引入了 Selenium Core。

  1. 当浏览器加载 RemoteRunner.html 页面时触发 onload 事件, Selenium Core 发起一个 seleniumStart 的 HTTP 请求,服务器返回一个 getTitle 命令。
  2. Selenium Core 收到响应后,执行响应中的命令,继续发出请求(携带刚才的命令执行结果),服务器返回 setContext 命令,通知 Selenium Core 设置上下文。
  3. Selenium Core 收到响应后,执行响应中的命令,发出重试请求。服务器若是 10001ms 内没有收到客户端的命令,便结束这次请求,返回 retryLast 命令,即继续重试。
  4. Selenium Core 收到响应后,重复第三步,直到客户端给服务器发送新命令。

整个 XMLHttpRequest 长轮询的过程至关因而一个 Response/Request 模式,服务器将要通知浏览器的内容放入 Response Body 中,浏览器将本次 Response 的处理结果放入下次的 Request URL 和 Body中,所以对于服务器来讲,每次 HTTP 响应至关于服务器的请求,每次 HTTP 请求至关于服务器的响应。

因为文章篇幅有限,open 命令的执行过程就不介绍了。上图的 uniqueId 是什么用途呢?每当页面跳转或刷新后,意味着上个页面会关闭,新页面须要继续保持和服务器的链接,这里的 uniqueId 能够理解成页面的标识,每当上个页面关闭后,下个页面就会使用新的 uniqueId 发起请求,同时 sequenceNumber 被重置为 0。

提高浏览器权限

该模式和代理注入很是相似,主要区别在于浏览器以一个被称为 Heightened Privileges 的特殊模式启动,它容许网站作一些一般不被容许的事(好比 XSS、填充文件上传输入框以及对 Selenium 颇有用的东西)。该模式下,Selenium Core 能够直接打开 AUT 并读取它的内容或与它的内容交互,而没必要经过 Selenium RC 服务器访问整个 AUT。

Selenium WebDriver

早期的 WebDriver

早期的 WebDriver 是和 Selenium Core 几乎同时期的来自 ThoughtWorks 的浏览器自动化工具,也是基于 Atoms 构建,这和后来咱们所熟悉的 w3c WebDriver 协议有些不一样。

Selenium CoreWebDriver 的设计理念有很大不一样。WebDriver 的开发人员倾向于向用户隐藏其并不关心的不少细节,提供尽量简单的 API,好让用户聚焦在用例设计和发现 Bug 上,正如他们擅长的那样,而 Selenium Core 则是给用户提供低级别的 API 。如下是 Selenium Core 用于设置 input 元素值的方法:

  • type
  • typeKeys
  • keydown
  • keypress
  • keyup
  • keydownNative
  • keypressNative
  • keyupNative
  • attachFile

它们等价于 WebDriversendKeys API,正如前面说的同样,WebDriver 努力模仿访问被测应用的用户。xxx 和 xxxNative 的区别在于,前者使用合成事件,后者经过 java.awt.Robot 模拟键盘输入来模拟用户操做。

w3c WebDriver 协议

讲述 Selenium WebDriver 原理以前,咱们先来看下 w3c WebDriver 协议,这里以 ChromeDriver 为例。

术语解释:

  • W3C WebDriver 是一个浏览器协议,又称 WebDriver 协议 / WebDriver 规范 / WebDriver API
  • Driver 是 WebDriver API 的特定实现,好比 Chrome 浏览器的 ChromeDriver。
  • ChromeDriver 是一个能够独立运行的服务器程序,它实现了 WebDriver 协议。
  • Selenium WebDriver 是一个基于 WebDriver 协议的 Web 自动化框架。

启动 ChromeDriver

在命令行窗口执行如下命令启动 ChromeDriver。

# 为了方便,建议先切换到 exe 文件所在目录
chromedriver_80.exe -port=12345

ChromeDriver 启动成功后,将获得一个服务器访问地址 http://localhost:12345。打开任务管理器,咱们能够看到 chromedriver_80.exe 进程。

访问 ChromeDriver

一、调用 New Session 接口,建立一个 Session,返回 sessionId=be92a2485bcdb5ca0d3bc44bd0b00a8d。与此同时将打开一个 Chrome 浏览器窗口。

endpoint_webdriver= http://localhost:12345

思考题:若是再次调用该接口会发生什么呢?

二、调用 Navigate To 接口,访问百度网站,等价于 Java 客户端的 driver.get("https://www.baidu.com")

执行结果:

其余接口相似,再也不一一举例。这些步骤是否是和使用 Java/Python 编写测试步骤时似曾相识?总结起来就是两步:

  • 第一步:经过 WebDriver 驱动启动一个服务端(每一个驱动操做与之对应的浏览器,如 Chrome 驱动操做 Chrome 浏览器)。
  • 第二步:客户端调用服务端 HTTP 接口,服务端收到请求后解析请求,执行对应的浏览器操做。

浏览器驱动大部分是各浏览器厂商根据 WebDriver 规范实现的,因此 Selenium 再也不须要直接操做浏览器,而是经过 HTTP 接口向驱动发出符合 WebDriver 规范的指令。

有一点须要注意,虽然低版本的 Firefox(47及如下) 和 Safari 不须要单独下载驱动程序,但这不是说 Firefox 和 Safari 不须要 WebDriver 驱动,而是由于在 Selenium 客户端中内置了浏览器的 webdriver 扩展,这些浏览器扩展起到了和驱动相同的做用,一样遵循 W3C WebDriver 协议。

从 Selenium 的源码中能够发现这些特殊状况:

  • Selenium2和3中低版本 Firefox 使用了 webdriver.xpi,经过 HTTP 通讯。
  • Selenium2 中 Safari 使用了 client.js,经过 WebSocket 通讯。
  • Selenium3 中 Safari 使用苹果系统自带的 safaridriver,经过 HTTP 通讯。

    • Safari 发布版驱动路径:/usr/bin/safaridriver
    • Safari 技术预览版驱动路径:/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver

Selenium WebDriver

了解了 WebDriver 协议后,咱们回过头来看 Selenium WebDriver 的工做原理就简单不少了。

若是咱们有一个客户端库,负责浏览器驱动的启动、管理,HTTP 请求的组装、响应的处理等诸多和测试脚本自己无关的事,测试用例的编写将变得更加容易。而 Selenium WebDriver 就提供了一系列这样的客户端库,除此以外还提供了 Selenium Grid 用于分布式执行。

从上图中能够看出,Java 编写的测试脚本中的命令由 Java 客户端转为 HTTP 请求并访问 WebDriver 服务器(驱动服务),浏览器驱动收到 HTTP 请求后,操做浏览器,就用网站用户那样。

若是你读过上面 Selenium RC 的部分,你就会发现,Selenium WebDriver 相比 Selenium RC,从架构上来讲要简单不少,API 也更加简洁。这对用户和 Selenium 的开发者来讲,都是一个质的飞跃。若是你考虑到 Selenium 要支持 X 个操做系统、Y 种浏览器和 Z 种编程语言,你就能理解 Selenium 的开发工做量有多大了,在 Selenium RC 时代,大量代码和编程语言绑定(好比 XHR 的长轮询、SSL 证书管理),这让每次迭代更新都很痛苦。

上面的 Selenium WebDriver 原理图,等价于官网的这张图。

除此以外,还能够有不一样的部署方式,使用 Remote WebDriver,操做远程主机上的浏览器。

经过 Selenium Server 或 Selenium Grid 与驱动通讯。

Selenium firefox xpi

由于对 Firefox 的支持,已经从内置的 firefox xpi 变为和其余驱动相似的 geckodriver,因此再也不过多介绍。若是你对 Selenium 客户端中内置的 firefox xpi 插件的工做机制感兴趣,能够阅读参考资料 [1] 中的 16.6. The Remote Driver, and the Firefox Driver in Particular 部分。

参考资料:

  1. The Architecture of Open Source Applications: Selenium WebDriver
  2. Understanding the components
相关文章
相关标签/搜索