目录
1. Selenium的历史
Selenium1
早期的Selenium1.x使用的是Javascript注入技术与浏览器打交道,需要Selenium RC启动一个Server,将操作Web元素的API调用转化为一段段Javascript,在Selenium内核启动浏览器之后注入这段Javascript。开发过Web应用的人都知道,Javascript可以获取并调用页面的任何元素,自如的进行操作。由此才实现了Selenium的目的:自动化Web操作。这种Javascript注入技术的缺点是速度不理想,而且稳定性大大依赖于Selenium内核对API翻译成的Javascript质量高低。
Selenium2
相比于selenium1.x,selenium2.x版本整合了webdriver以及原版selenium。两个项目合二为一,虽然名字还叫selenium,但也可以叫Webdriver。
当Selenium2.x 提出了WebDriver的概念之后,它提供了完全另外的一种方式与浏览器交互。那就是利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)。由于使用的是浏览器原生的API,速度大大提高,而且调用的稳定性交给了浏览器厂商本身,显然是更加科学。然而带来的一些副作用就是,不同的浏览器厂商,对Web元素的操作和呈现多少会有一些差异,这就直接导致了Selenium WebDriver要分浏览器厂商不同,而提供不同的实现。例如Firefox就有专门的FirefoxDriver,Chrome就有专门的ChromeDriver(甚至包括了AndroidDriver和iOS WebDriver)。
Selenium3
其实相对于与Selenium2,Selenium3没有做太多的改动。下面给出官方的文档说明,供参考。
官方文档描述如下:
We aim for Selenium 3 to be “a tool for user-focused automation of mobile and web apps”.
What does this mean? For mobile users, the Selenium project will be hosting a suite of tests to facilitate interoperability between the many different projects available that are extending the WebDriver API to also cope with mobile. Developers from projects such as Appium, ios-driver and selendroid will be working on the suite of tests to enable this.
We’ll also be working on making the technology behind Selenium as stable and capable as possible. For this reason, Selenium 3 will see the removal of the original Selenium Core implementations, and consequently we’ll be deprecating the RC APIs too. The old versions will still be available as a separate download, but active development will cease, except for very urgent fixes. We will still be providing an implementation of the RC APIs backed by WebDriver, so you can continue running your existing tests, but now would be a great time to make the move to using the WebDriver APIs directly.
所以对于Selenium3来说最大的变动可能就是更加专注于手机和web的测试,尤其是手机的支持。
Selenium 3将删除最初的Selenium核心实现,官方不再推荐使用RC API。旧版本仍然可以单独下载,但是除了非常紧急的修复之外,活跃的开发将停止。官方仍将提供一个由WebDriver支持的RC API的实现,因此可以继续运行现有的测试,但官方推荐以后使用WebDriver API。
对于Selenium2中对于RemotControl的实现我看了下Selenium3的源码发现确实不在支持,而更多的转向了W3C standard,
2. WebDriver协议
WebDriver是W3C的一个标准,由Selenium主持。具体的协议标准可以从官方网站查看。
从这个协议中我们可以看到,WebDriver之所以能够实现与浏览器进行交互,是因为浏览器实现了这些协议。这个协议是使用JOSN通过HTTP进行传输。
它的实现使用了经典的Client-Server模式。客户端发送一个requset,服务器端返回一个response。
官方网站文档描述如下:
The following table of endpoints lists the method and URI template for each endpoint node command. Extension commands are implicitly appended to this table.
Method | URI Template | Command |
---|---|---|
POST | /session | New Session |
DELETE | /session/{session id} | Delete Session |
GET | /status | Status |
GET | /session/{session id}/timeouts | Get Timeouts |
POST | /session/{session id}/timeouts | Set Timeouts |
POST | /session/{session id}/url | Navigate To |
GET | /session/{session id}/url | Get Current URL |
POST | /session/{session id}/back | Back |
POST | /session/{session id}/forward | Forward |
POST | /session/{session id}/refresh | Refresh |
GET | /session/{session id}/title | Get Title |
GET | /session/{session id}/window | Get Window Handle |
DELETE | /session/{session id}/window | Close Window |
POST | /session/{session id}/window | Switch To Window |
GET | /session/{session id}/window/handles | Get Window Handles |
POST | /session/{session id}/window/new | New Window |
POST | /session/{session id}/frame | Switch To Frame |
POST | /session/{session id}/frame/parent | Switch To Parent Frame |
GET | /session/{session id}/window/rect | Get Window Rect |
POST | /session/{session id}/window/rect | Set Window Rect |
POST | /session/{session id}/window/maximize | Maximize Window |
POST | /session/{session id}/window/minimize | Minimize Window |
POST | /session/{session id}/window/fullscreen | Fullscreen Window |
GET | /session/{session id}/element/active | Get Active Element |
POST | /session/{session id}/element | Find Element |
POST | /session/{session id}/elements | Find Elements |
POST | /session/{session id}/element/{element id}/element | Find Element From Element |
POST | /session/{session id}/element/{element id}/elements | Find Elements From Element |
GET | /session/{session id}/element/{element id}/selected | Is Element Selected |
GET | /session/{session id}/element/{element id}/attribute/{name} | Get Element Attribute |
GET | /session/{session id}/element/{element id}/property/{name} | Get Element Property |
GET | /session/{session id}/element/{element id}/css/{property name} | Get Element CSS Value |
GET | /session/{session id}/element/{element id}/text | Get Element Text |
GET | /session/{session id}/element/{element id}/name | Get Element Tag Name |
GET | /session/{session id}/element/{element id}/rect | Get Element Rect |
GET | /session/{session id}/element/{element id}/enabled | Is Element Enabled |
GET | /session/{session id}/element/{element id}/computedrole | Get Computed Role |
GET | /session/{session id}/element/{element id}/computedlabel | Get Computed Label |
POST | /session/{session id}/element/{element id}/click | Element Click |
POST | /session/{session id}/element/{element id}/clear | Element Clear |
POST | /session/{session id}/element/{element id}/value | Element Send Keys |
GET | /session/{session id}/source | Get Page Source |
POST | /session/{session id}/execute/sync | Execute Script |
POST | /session/{session id}/execute/async | Execute Async Script |
GET | /session/{session id}/cookie | Get All Cookies |
GET | /session/{session id}/cookie/{name} | Get Named Cookie |
POST | /session/{session id}/cookie | Add Cookie |
DELETE | /session/{session id}/cookie/{name} | Delete Cookie |
DELETE | /session/{session id}/cookie | Delete All Cookies |
POST | /session/{session id}/actions | Perform Actions |
DELETE | /session/{session id}/actions | Release Actions |
POST | /session/{session id}/alert/dismiss | Dismiss Alert |
POST | /session/{session id}/alert/accept | Accept Alert |
GET | /session/{session id}/alert/text | Get Alert Text |
POST | /session/{session id}/alert/text | Send Alert Text |
GET | /session/{session id}/screenshot | Take Screenshot |
GET | /session/{session id}/element/{element id}/screenshot | Take Element Screenshot |
POST | /session/{session id}/print | Print Page |
3. Selenium驱动浏览器原理
在我们创建一个WebDrive实例的过程中,Selenium首先会确认浏览器的native component是否存在可用而且版本匹配。接着就在目标浏览器里启动一整套Web Service,这套Web Service使用了Selenium自己设计定义的协议,名字叫做The WebDriver Wire Protocol。这套协议非常之强大,几乎可以操作浏览器做任何事情,包括打开、关闭、最大化、最小化、元素定位、元素点击、上传文件等等等等。
WebDriver Wire协议是通用的,也就是说不管是FirefoxDriver还是ChromeDriver,启动之后都会在某一个端口启动基于这套协议的Web Service。例如FirefoxDriver初始化成功之后,默认会从http://localhost:7055开始,而ChromeDriver则大概是http://localhost:46350之类的。接下来,我们调用WebDriver的任何API,都需要借助一个ComandExecutor发送一个命令,实际上是一个HTTP request给监听端口上的Web Service。在我们的HTTP request的body中,会以WebDriver Wire协议规定的JSON格式的字符串来告诉Selenium我们希望浏览器接下来做社么事情。
使用Selenium实现驱动浏览器,主要涉及三个东西。
- 测试代码
测试代码就是程序员利用不同的语言和相应的selenium API库完成的代码。本文将以python为例进行说明。
Webdriver是针对不同的浏览器开发的,不同的浏览器有不同的webdriver。例如针对Chrome使用的chromedriver。
浏览器和相应的Webdriver对应。
首先我们来看一下这三个部分的关系。
对于三个部分的关系模型,可以用一个日常生活中常见的例子来类比。
对于打的这个行为来说,乘客和出租车司机进行交互,告诉出租车想去的目的地,出租车司机驾驶汽车把乘客送到目的地,这样乘客就乘坐出租车到达了自己想去的地方。
这和Webdriver的实现原理是类似的,测试代码中包含了各种期望的对浏览器界面的操作,例如点击。测试代码通过给Webdriver发送指令,让Webdriver知道想要做的操作,而Webdriver根据这些操作在浏览器界面上进行控制,由此测试代码达到了在浏览器界面上操作的目的。
理清了Selenium自动化测试三个重要组成之间的关系,接下来我们来具体分析其中一个最重要的关系。
4. 测试代码与Webdriver的交互
接下来我会以获取界面元素这个基本的操作为例来分析两者之间的关系。
在测试代码中,我们第一步要做的是新建一个webdriver类的对象:
from selenium import webdriver
driver = webdriver.Chrome()
这里新建的driver
对象是一个webdriver.Chrome()
类的对象,而webdriver.Chrome()
类的本质是
from .chrome.webdriver import WebDriver as Chrome
也就是一个来自chrome的WebDriver
类。这个.chrome.webdriver.WebDriver
是继承了selenium.webdriver.remote.webdriver.WebDriver
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
...
class WebDriver(RemoteWebDriver):
"""
Controls the ChromeDriver and allows you to drive the browser.
You will need to download the ChromeDriver executable from
http://chromedriver.storage.googleapis.com/index.html
"""
def __init__(self, executable_path="chromedriver", port=0,
chrome_options=None, service_args=None,
desired_capabilities=None, service_log_path=None):
...
以python为例,在selenium
库中,通过ID获取界面元素的方法是这样的:
from selenium import webdriver
driver = webdriver.Chrome()
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()
的实例方法的。
通过Source code可以看到:
def find_element(self, by=By.ID, value=None):
"""
'Private' method used by the find_element_by_* methods.
:Usage:
Use the corresponding find_element_by_* instead of this.
:rtype: WebElement
"""
if self.w3c:
...
return self.execute(Command.FIND_ELEMENT, {
'using': by,
'value': value})['value']
这个方法最后call了一个execute
方法,方法的定义如下:
def execute(self, driver_command, params=None):
"""
Sends a command to be executed by a 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}
正如注释中提到的一样,其中的关键在于
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
类的类注释来看:
class WebDriver(object):
"""
Controls a browser by sending commands to a remote server.
This server is expected to be running the WebDriver wire protocol
as defined at
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
:Attributes:
- session_id - String ID of the browser session started and controlled by this WebDriver.
- capabilities - Dictionaty of effective capabilities of this browser session as returned
by the remote server. See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities
- command_executor - remote_connection.RemoteConnection object used to execute commands.
- error_handler - errorhandler.ErrorHandler object used to handle errors.
"""
_web_element_cls = WebElement
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
desired_capabilities=None, browser_profile=None, proxy=None,
keep_alive=False, file_detector=None):
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
。
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
可以看到,self._commands
存储了selenium.webdriver.remote.command.Command
类里的常量指令和WebDriver wire protocol中定义的指令的对应关系。
self._commands = {
Command.STATUS: ('GET', '/status'),
Command.NEW_SESSION: ('POST', '/session'),
Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
Command.QUIT: ('DELETE', '/session/$sessionId'),
...
Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
以FIND_ELEMENT为例可以看到,指令的URL部分包含了几个组成部分:
-
HTTP请求方法。WebDriver wire protocol中定义的指令是符合RESTful规范的,通过不同请求方法对应不同的指令操作。
-
sessionId
。Session的概念是这么定义的:The server should maintain one browser per session. Commands sent to a session will be directed to the corresponding browser.
也就是说
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返回给测试代码,测试代码经过解析处理后得到想要的数据。
5. Webdriver与浏览器的关系
这一部分属于各个浏览器开发者和Webdriver开发者的范畴,所以我们不需要太关注,我们所关心的主要还是测试代码和Webdriver的关系,就好像出租车驾驶员如何驾驶汽车我们不需要关心一样。
6. Selenium Webdriver总结
最后通过这个关系图来简单的描述Selenium三个组成部分的关系。通过对python selenium库的分析,希望能够帮助大家对selenium和webdriver的实现原理有更进一步的了解,在日常的自动化脚本开发中更加快捷的定位问题和解决问题。
7. 客户端和浏览器在同一台机器上
from selenium import webdriver
import time
browser=webdriver.Chrome()
browser.get("http://www.baidu.com")
(1).browser=webdriver.Chrome()这句代码的解析如下:
通过上面代码的解析,我们已经知道我们创建了一个实例browser,该实例是WebDriver和RemoteWebDriver共同拥有的实例。通过WebDriver启动了chromedriver程序,通过RemoteWebDriver则启动了浏览器,生成session值。并规定了webdriver协议。
(2).driver.get("http://www.google.com")这句代码的解析:
该get()方法是RemoteWebDriver的方法。该方法直接又调用了RemoteWebDriver的execute()方法。execute()方法就是用来操作浏览器做各种事情的。
我们的测试代码向remote server发送了如下的请求:
POSTsession/285b12e4-2b8a-4fe6-90e1-c35cba245956/url post_data{"url":"http://google.com"}
通过post的方式请求localhost:port/hub/session/session_id/url地址,请求浏览器完成跳转url的操作。
如果上述请求是可接受的,或者说remote server是实现了这个接口,那么remote server会跳转到该post data包含的url,并返回如下的response
{"name":"get","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":""}
该response中包含如下信息
name:remote server端的实现的方法的名称,这里是get,表示跳转到指定url;
sessionId:当前session的id;
status:请求执行的状态码,非0表示未正确执行,这里是0,表示一切ok不许担心;
value:请求的返回值,这里返回值为空,如果client调用title接口,则该值应该是当前页面的title;
如果client发送的请求是定位某个特定的页面元素,则response的返回值可能是这样的:
{"name":"findElement","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":{"ELEMENT":"{2192893e-f260-44c4-bdf6-7aad3c919739}"}}
name,sessionId,status跟上面的例子是差不多的,区别是该请求的返回值是ELEMENT:{2192893e-f260-44c4-bdf6-7aad3c919739},表示定位到元素的id,通过该id,client可以发送如click之类的请求与 server端进行交互。
8. 客户端远程驱动浏览器
通过上面的实例和以前介绍的内容,我们知道了,客户端按照webdriver协议编写一段字符串,这段字符串再通过json的方式发送到浏览器端,浏览器端则使用chromedriver驱动程序启动浏览器并跟据发送过来的字符串操作浏览器进行相应的工作。
所以,我们想远程驱动浏览器,我们需要有程序去启动chromedriver程序。然后我们再在客户端json一段字符串过去。
如果不考虑remote模式,开启一个Selenium浏览器实例应该是类似这样。
driver = selenium.webdriver.Firefox()
但是Remote模式下,就要稍微复杂一点儿:
具体步骤:
1.下载合适的selenium-server,并启动
目的:启动chromedriver和设置监听端口
在win中:
下载地址:https://selenium-release.storage.googleapis.com/index.html?path=3.3/
下载建议:根据您当前的selenium版本下载对应的selenium server版本
在cmd中执行命令:
>> java -Dwebdriver.chrome.driver = “/opt/chromium-browser/chromedriver.exe ” -jar selenium-server-standalone-3.3.1.jar -port 9999
在linux中:
npm install -g selenium-standalone
selenium-standalone install
selenium-standalone start
2.编写客户端代码,如下:
from selenium.webdriver.remote import webdriver #这儿直接导入的remote里面的webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities #导入浏览器的基本参数
f=webdriver.WebDriver(command_executor="http://192.168.1.103:9999/wd/hub",desired_capabilities=DesiredCapabilities.CHROME)
f.get("http://www.baidu.com")
看出这段代码和上面《一、客户端和浏览器在同一台机器上》里面的代码有什么不同了吗?
上面实例化的是chrome 里面的webdriver 。而该实例里实例化的是remote里面的webdriver。这是为什么呢?
因:上面需要启动chromedriver和设置监听端口。而该实例里面启动chromedriver和设置监听端口是在远程服务器端通过selenium-server启动的。
9. selenium多机运行配置
多机版(真正的remote模式),需要定义两个角色:hub(即Master)、node(即Slave)。
要在其它node主机执行,必须满足以下几个条件:
- 本地hub 主机与远程node 主机之间可以相互ping 通。
- 远程主机必须安装运行脚本的浏览器及驱动(如,chrome 浏览器及chromedriver.exe 驱动)
- 远程主机必须安装java 环境
- 远程主机必须安装selenium server
具体怎么用,可以看帮助。
看所有的一级选项:
java -jar selenium-server-standalone-3.3.1.jar --help
看hub特性:
java -jar selenium-server-standalone-3.3.1.jar -role hub -help
看node特性:
java -jar selenium-server-standalone-3.3.1.jar -role node --help
启动hub很简单:
java -jar selenium-server-standalone-3.3.1.jar -role hub
启动node稍微复杂点儿,必须指定hub地址、端口:
java -jar selenium-server-standalone-3.3.1.jar -role node -port 5555 -hub http://10.120.9.222:4444/grid/register
最新版的selenium Grid允许同时并行的,在不同的环境运行多个测试任务。
【环境准备】
1.需要两台PC机,两台机器必须安装好jdk的环境变量(我的一个win10,一个win8)
2.需要一个selenium-server-standalone-3.11.0.jar包。
3.安装了python2.7(64位),selenium3.5(这两个版本不是很重要,都可以)
【配置开始】
1.集线器主服务(A ip:192.168.1.108)
Selenium Grid需要一台机子作为集线器,即启动主服务器,找到jar包所在的目录,按住shift键然后点击右键,选择‘在此处打开窗口’打开命令窗口,通过这个命令启动主服务,即hub服务:
java -jar selenium-server-standalone-3.11.0.jar -role hub
命令执行完后输出,有一个注册中心地址:http://192.168.1.108:4444/grid/register/:
如果不设置端口号,默认的端口号是4444,如果要改这个端口,可以再上面的命令后面加上 -port XXXX。启动完后,你可以用浏览器打开 http://localhost:4444/grid/console 这个网址查看主节点的状态。
2.节点服务(B ip:192.168.1.110)
启动完主服务hub之后,接下来做的是启动子节点,以另一台机器作为子节点的机器:
java -jar selenium-server-standalone-3.11.0.jar -role node -hub http://192.168.1.108:4444/grid/register/
服务启动后输出:
启动完成连接到主节点后,可以在主节A机子上 ,http://localhost:4444/grid/console网址查看到这个子节点状态。使用同样的方法,可以链接其它的子节点B、C…
【代码测试】
在主机A上运行测试的代码,会把命令发送给B:
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
d=DesiredCapabilities.CHROME.copy()
d['browserName']='chrome'
d['version']='65'
desired_cap={'browserName':'chrome',
'version':'65',
'javascriptEnabled':True}
driver=webdriver.Remote(command_executor='http://192.168.1.110:5555/wd/hub',desired_capabilities=d)
driver.get("http://192.168.1.108:8080/lab/stusign")
printdriver.title
driver.quit()
总结:
1.整体来说这个多机并行的环境搭建还是很容易的,参考官方文档就可以很轻松的搞定。
2.问题在于我们实际的工作工程中如何使用良好的策略来规则化我们的多机并行执行呢?还有就是执行的内容是用例脚本,而这些脚本是以何种规则分配到对应的机器上呢?这种问题,只能说具体情况具体分析。
3.就是我们在启动节点时,启动命令华友一些附加的参数如:-D、-config、-browser等等,你可以用java -jar selenium-server-standalone-3.11.0.jar -h去看具体的解释。
示例(好好研究下参数的意义):
java -jar selenium-server-standalone-3.11.0.jar -role node -hub http://192.168.1.108:4444/grid/register/
-browser “browserName=chrome,maxinstance=1,platform=WINDOWS” -Dwebdriver.chrome.driver=D://chromedriver.exe
有兴趣可以关注我的微信公众号“自动化测试全栈”,微信号:QAlife,学习更多自动化测试技术。
也可加入我们的自动化测试技术交流群,QQ群号码:301079813
主要探讨loadrunner/JMeter测试、Selenium/RobotFramework/Appium自动化测试、接口自动化测试,测试工具等测试技术,让我们来这里分享经验、交流技术、结交朋友、拓展视野、一起奋斗!
参考文章:
1.关于对Selenium的理解_Ant Ren的专栏-CSDN博客
2.Selenium源码分析之WebDriver_Ant Ren的专栏-CSDN博客_selenium源码、
3.透过源码看本质-关于Selenium Webdriver 实现原理的一点思考和分享