2004年,当时在ThoughtWorks工作的Jason Huggins开发了Selenium(Selenium RC)的第一版。2006年,Google工程师基于Selenium开发了WebDriver。2008年,Selenium和WebDriver合并,形成了Selenium2(Selenium WebDriver)。目前,Selenium WebDriver的模式已经升级到Selenium4,并有一个支线项目Selenium-Grid,能够与Selenium配合进行多任务运行(主要针对分布式执行,对于当前业务现状,使用到的可能性很小,本文不展开讲解)。
使用现状:虽然无法直接统计出每个公司的使用现状,但我们可以通过搜索趋势来侧面验证。通过Google Trends查询的结果显示,Selenium WebDriver主导的方案占据主流地位,而Selenium RC的方案正在逐步被淘汰。
Selenium RC:
组成部分:Selenium RC主要由客户端和服务器两部分组成。
工作原理:Selenium RC通过发送HTTP请求与服务器进行通信,服务器再将请求转发给浏览器执行。
缺点:Selenium RC的执行速度较慢,且需要各个浏览器厂商提供支持。
Selenium WebDriver:
组成部分:WebDriver主要由WebDriver接口和对应的浏览器驱动程序组成。
Web Driver:WebDriver提供了另一种与浏览器交互的方式,即利用浏览器原生的API,封装成一套面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏、窗口大小、启动、关闭、安装插件、配置证书等)。由于使用的是浏览器的原生API,速度大大提高,但缺点是需要各个浏览器厂商各自提供。
各种编程语言编写的客户端:向remote server发起请求。
工作原理:底层通信包含以下两个过程:
Selenium -> ChromeDriver server:这个通信过程是基于HTTP协议。
例如,我们要打开一个浏览器页面,并访问
www.google.com,先看下Selenium源码是怎么实现这个过程的。
首次建立连接的过程:
选择一个空闲的端口启动chromedriver。
具体发请求的接口:
最终的收口就是_request,发起一个http请求。
后续进行的一系列操作也是类似的过程,例如打开一个网页。
通过这个简单的访问过程,并结合self._command和_request里面的内容,可以大概明白这个流程实际上就是HTTP的请求和响应流程。
为了更进一步验证这个猜想,可以在本地通过postman发送请求模拟Selenium的行为。
首先需要运行chromedriver.exe(默认端口是9515)。
在postman请求,入参可以copy Selenium的入参即可:
POST
http://localhost:9515/sessionBody: {"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "chrome", "platformName": "any", "goog:chromeOptions": {"extensions": [], "args": []}}}, "desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY", "goog:chromeOptions": {"extensions": [], "args": []}}}
同理,基于这个sessionid进行操作:
POST
http://localhost:9515/session/933285473c4f3961a3628a0b30d9dd8d/url
Body: {"url": "google.com"}
ChromeDriver server -> Chrome:网上大部分内容都是讲解前面一部分,对于webdriver与浏览器本身的通信提的比较少,这里也简单探讨一下这个过程。
以chromedriver为例,【C++】源码(参考资料[6])和chromium放在一块,暂时无法在飞书文档外展示此内容。
通信的过程依赖于cdp(Chrome DevTools Protocol),有点类似于我们在chrome调试页面进行的操作,chrome调试页面本身就是一个前端工具,与后端的通信就是基于cdp,cdp的某些通信过程是基于websocket。
main函数里面定义了初始端口号是9515,这也解释了我们手动运行chromedriver.exe会使用9515的端口号启动服务。
接收Selenium请求的服务模块是放在./chromedriver/server/ http_handler.cc。
可以明显看到,这块内容对应上和Selenium self._command里面的内容。
在chrome网站上我们可以看到标准的浏览器websocket API定义,例如(chromedevtools.github.io...),同样,chromedriver内部也是通过访问这些API达到操作浏览器的效果。
进一步查找发送websocket的方法,可以找到在SendCommandInternal里面确实调用了socket->Send。
Chromium Embedded Framework (CEF):刚才讨论的情况主要是针对独立浏览器应用的,还有一种是内嵌于客户端的情况,简称cef。Chromium Embedded Framework (CEF)是个基于Google Chromium项目的开源Web browser控件,支持Windows、Linux、Mac平台。除了提供C/C++接口外,也有其他语言的移植版。因为基于Chromium,所以CEF支持Webkit 和 Chrome中实现的HTML5的特性,并且在性能上面也比较接近Chrome。
通信的原理和上面独立应用的情况大差不差,实际使用上需要注意的点如下:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.debugger_address = "127.0.0.1:" + str(self.cef_port)
大概的解释是:当我们指定了debugger_address 之后,chromedrive服务就会尝试使用本地端口号为self.cef_port的服务,也就是测试用例实际操作的是内嵌页面。
如下动图,执行的时候并不会重新打开一个新的edge页面,而是复用已有的页面(之前通过9222端口唤起)。
参考资料:
[1] GitHub - SeleniumHQ/selenium: A browser automation framework and ecosystem.
[2] Browser automation using ChromeDriver and Postman
[3] How ChromeDriver works in the background?
[4] test-circle/selenium_python.md at main · defnngj/test-circle
[5] Chrome DevTools Protocol
[6] chromedriver source code
[7] cnblogs.com/uncleyong/p...
[8] cloud.tencent.com/devel...
[9] einverne.gitbook.io/sel...