2004年在 ThoughtWorks 公司,一个叫做 Jason Huggins 为了减少手工测试的工作,自己写了一套 JavaScript
的库,这套库可以进行页面交互,并且可以重复的在不同浏览器上进行重复的测试操作。这套库后来变为了 Selenium Core,为Selenium Remote Control (RC) 和 Selenium IDE提供了坚实的核心基础能力。[1]
Selenium Core是第一个工具。但是,由于同源政策,Selenium Core在跨域测试方面遇到了障碍。同源策略禁止JavaScript代码访问托管在与JavaScript启动位置不同的域上的Web元素。为了克服同源策略问题,测试人员需要安装Selenium Core(一个JavaScript程序)和包含被测试Web应用程序的Web服务器的本地副本,以便它们属于相同的域。这导致了Selenium RC的诞生,它被认为是当时的ThoughtWork的工程师保罗·哈曼特(Paul Hammant)。[2] _补充:同源策略问题,测试用例部署在与被测应用相同的服务器上(只要被测应用和测试脚本同源就可以)。这也意味着,你无法测试别人的网站,比如 _https://www.baidu.com。 基于上述问题,selenium RC 做了两点[3]:
Selenium RC
就在Selenium处于开发阶段的同时,另一款浏览器自动化框架WebDriver也正在ThoughtWorks公司的酝酿之中。WebDriver的最初代码在2007年初发布。WebDriver项目的初衷是把端对端测试与底层测试工具隔离开。通常情况下,这种隔离手段通过适配器(Adapter)模式完成。WebDriver正是来源于该方法在许多项目上的不断实践应用,最初是HtmlUnit的封装,工具发布后很快开始支持Internet Explorer和Firefox。[4]
2009年8月由 Selenium 1.0 和 WebDriver 项目合并而成,需要注意的是,在 Selenium 2.0 中主推的是 WebDriver,可以将其看作 Selenium RC 的替代品。为了保持向下的兼容性,Selenium 2.0 并没有彻底抛弃 Selenium RC。
Selenium RC 是在浏览器中运行 JavaScript 应用,使用浏览器内置的 JavaScript 翻译器来翻译和执行 selenese 的(selenese 是 Selenium 命令集合)。WebDriver 通过原生浏览器支持或者浏览器扩展来直接控制浏览器。WebDriver 是针对各个浏览器而开发的,取代了嵌入被测 Web 应用中的 JavaScript。WebDriver 与浏览器紧密集成,支持创建更高级的测试,避免了 JavaScript 安全模型导致的限制。除来自浏览器厂商的支持外,WebDriver 还可利用操作系统级的调用,模拟用户输入。Selenium 与 WebDriver 原本属于两个不同的项目,WebDriver 的创建者 Simon Stewart早在 2009 年 8 月的一封邮件中解释了项目合并的原因[5]。
W3C组织制定了一套浏览器自动化的规范叫做WebDriver,这套规范规定了所有的浏览器生产商都必须遵守这个规范[7]。其实定义了好多的遵循的接口和WebDriver的概念。对于Chrome、Firefox、Opera、Safari他们都需要遵守这个规范并且实现规范里面的接口,这些实现一般都是伴随浏览器的开发进行的。Selenium不管是WebDriver还是Remote WebDriver都是W3C WebDriver的一种实现而已。真正的核心浏览器的交互在对应的浏览器的WebDriver上,其实你有了对应的浏览器的WebDriver,参考W3C的标准接口文档HTTP-based wire protocol你就可以单独实现浏览器的操作
JSON Wire protocol是在http协议基础上,对http请求及响应的body部分的数据的进一步规范。我们知道在HTTP请求及响应中常常包括以下几个部分:http请求方法、http请求及响应内容body、http响应状态码等[8]。
findElement
、Click
等在WebDriver中为了给用户以更明确的反馈信息,提供了更细化的http响应状态码,比如:
JSON是一种数据交换的格式,是对XML的升级与替代,下面是一个JSON文件的例子:
{
"firstname": "Alex",
"lastname": "Smith",
"moble": "13300000000"
}
下面的例子是WebDriver中在成功找到一个元素后JSON Wire Protocol的返回:
{"status" : 0, "value" : {"element" : "123422"}}
所以在Client
和Server
之间,只要是基于JSON Wire Protocol
来传递数据,就与具体的脚本语言无关了,这样同一个浏览器的驱动就可以处理Java语言的脚本,也可以处理Python语言的脚本了。
举个查找百度搜索输入框的例子
POST请求
http://xxx.xxx.xxx.xxx:4444/wd/hub/session/277e823a-8be2-48eb-9001-971cc1bff9c4/elements
{using:'css selector',value:'#kw'}
返回:
{
"status": 0,
"sessionId": "b6d34570-8300-4507-b60c-ab7cd1fe6536",
"state": "success",
"value": [
{
"ELEMENT": "0"
}
],
"class": "org.openqa.selenium.remote.Response",
"hCode": 30641031
}
要对上面的输入框 填写文字操作,
POST请求
http://xxx.xxx.xxx.xxx:4444/wd/hub/session/277e823a-8be2-48eb-9001-971cc1bff9c4/element/0/value
{value:[hello,world]}
抛开selenium,我们通过 w3c webDriver协议,也可以调用浏览器进行自动化进行操作。以通过调用ChromeDriver.exe
为例。
ChromeDriver.exe
是一个可以独立运行的服务器程序,适用于Chrome浏览器。它实现了 WebDriver 协议。(下载地址:http://chromedriver.storage.googleapis.com/index.html)
C:\Users\yangbo>chromeDriver.exe -h
Usage: chromeDriver.exe [OPTIONS]
Options
--port=PORT port to listen on
--adb-port=PORT adb server port
--log-path=FILE write server log to file instead of stderr, increases log level to INFO
--log-level=LEVEL set log level: ALL, DEBUG, INFO, WARNING, SEVERE, OFF
--verbose log verbosely (equivalent to --log-level=ALL)
--silent log nothing (equivalent to --log-level=OFF)
--append-log append log file instead of rewriting
--replayable (experimental) log verbosely and don't truncate long strings so that the log can be replayed.
--version print the version number and exit
--url-base base URL path prefix for commands, e.g. wd/url
--readable-timestamp add readable timestamps to log
--enable-chrome-logs show logs from the browser (overrides other logging options)
--allowed-ips=LIST comma-separated allowlist of remote IP addresses which are allowed to connect to ChromeDriver
--allowed-origins=LIST comma-separated allowlist of request origins which are allowed to connect to ChromeDriver. Using `*` to allow any host origin is dangerous!
首先启动chromedriver,默认端口9515,更改端口-port=xxxx参数,请求本地driverSerice创建session,并新打开一个浏览器界面:
import requests
import time
driver_url = 'http://localhost:9515/session'
driver_value = {"capabilities":
{"firstMatch": [{}],
"alwaysMatch":
{"browserName":
"chrome",
"platformName": "any",
"goog:chromeOptions":
{"extensions": [], "args": []}}},
"desiredCapabilities":
{"browserName":
"chrome",
"version": "",
"platform": "ANY",
"goog:chromeOptions": {"extensions": [],
"args": []}}}
response_session = requests.post(driver_url, json = driver_value)
print(response_session.json())
获取sessionId,添加到请求参数中,执行一个百度一下的完整案例:
import requests
import time
driver_url = 'http://localhost:9515/session'
driver_value = {"capabilities":
{"firstMatch": [{}],
"alwaysMatch":
{"browserName":
"chrome",
"platformName": "any",
"goog:chromeOptions":
{"extensions": [], "args": []}}},
"desiredCapabilities":
{"browserName":
"chrome",
"version": "",
"platform": "ANY",
"goog:chromeOptions": {"extensions": [],
"args": []}}}
response_session = requests.post(driver_url, json = driver_value)
sessionId = response_session.json()["value"]["sessionId"]
url = driver_url+'/'+sessionId+'/url'
value = {"url": "https://www.baidu.com/", "sessionId": sessionId}
res = requests.post(url = url,json = value)
time.sleep(1)
url = driver_url+'/'+sessionId+'/element'
pars = {"using": "css selector", "value": "#kw", "sessionId": sessionId}
r = requests.post(url,json=pars)
elementIdInput = list(r.json()['value'].values())[0]
time.sleep(1)
url = driver_url+'/'+sessionId+'/element/'+elementIdInput+'/value'
pars = {"text": "test"}
r = requests.post(url,json=pars)
time.sleep(1)
url = driver_url+'/'+sessionId+'/element'
pars = {"using":"css selector","value":"#su", "sessionId": sessionId}
r = requests.post(url,json=pars)
print(r.json())
elementIdSearch = list(r.json()['value'].values())[0]
url = driver_url+'/'+sessionId+'/element/'+elementIdSearch+'/click'
r = requests.post(url,json=pars)
通过上述案例发现,chromeDriver.exe
,启动一个client端
,client 端通过携带session
对remote server端
发送请求,浏览器实现了webdriver的统一接口,所以就进行相应的动作。各大浏览器都遵循w3c webdriver
的规范,于是有了对应的IEDriver
、FireFoxDriver
、chromeDriver
等等。以上内容仅仅是webdriver本身API提供的能力,没有用到任何selenium相关。
当看懂上述webDriver的原理后,就不难理解selenium webdriver了。selenium webdriver不需要直接操作浏览器,而是通过HTTP接口向驱动发出符合WebDriver规范的指令,就可以完成对浏览器的控制。
python
+ selenium
+chrome浏览器
环境部署 为例
C:\Users\name>pip install selenium
Collecting selenium
Downloading selenium-3.4.3-py2.py3-none-any.whl (931kB)
26% |████████ | 245kB 576kB/s eta 0:00:02
27% |█████████ | 256kB 570kB/s eta 0:00:02
28% |██████████ | 266kB 536kB/s eta 0:00:0
29% |███████████ | 276kB 530kB/s eta 0:00:0
30% |████████████ | 286kB 586kB/s eta 0:00:0
……
*浏览器版本要和webDriver版本一一对应,否则可能出现执行报错。需要将webDriver加入到环境变量,浏览器.exe添加到环境变量。
IE浏览器:http://docs.seleniumhq.org/download/ Firfox浏览器:https://github.com/mozilla/geckodriver/releases Chrome浏览器:http://chromedriver.storage.googleapis.com/index.html Edge浏览器:https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
环境配置完毕,就可以创建一个测试脚本:
#test.py
from selenium import webdriver
driver = webdriver.Chrome() # Chrome浏览器
driver.get('https://www.baidu.com')
print(driver.title)
driver.quit()
#remote_connection.py
...
self._commands = {
Command.STATUS: ('GET', '/status'),
Command.NEW_SESSION: ('POST', '/session'),
Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
Command.QUIT: ('DELETE', '/session/$sessionId'),
Command.GET_CURRENT_WINDOW_HANDLE:
('GET', '/session/$sessionId/window_handle'),
Command.W3C_GET_CURRENT_WINDOW_HANDLE:
('GET', '/session/$sessionId/window'),
Command.GET_WINDOW_HANDLES:
('GET', '/session/$sessionId/window_handles'),
Command.W3C_GET_WINDOW_HANDLES:
('GET', '/session/$sessionId/window/handles'),
Command.GET: ('POST', '/session/$sessionId/url'),
Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'),
Command.GO_BACK: ('POST', '/session/$sessionId/back'),
Command.REFRESH: ('POST', '/session/$sessionId/refresh'),
Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'),
Command.W3C_EXECUTE_SCRIPT:
('POST', '/session/$sessionId/execute/sync'),
Command.W3C_EXECUTE_SCRIPT_ASYNC:
('POST', '/session/$sessionId/execute/async'),
Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
Command.ELEMENT_SCREENSHOT: ('GET', '/session/$sessionId/element/$id/screenshot'),
Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'),
Command.W3C_GET_ACTIVE_ELEMENT: ('GET', '/session/$sessionId/element/active'),
Command.GET_ACTIVE_ELEMENT:
('POST', '/session/$sessionId/element/active'),
Command.FIND_CHILD_ELEMENT:
('POST', '/session/$sessionId/eleme
... ...
1. 创建对象driver
driver = webdriver.Chrome()
2. 浏览器中加载url
driver.get(url)
3. 浏览器窗口最大化
driver.maximize_window()
4. 浏览器窗口固定大小
driver.set_window_size(x, y)
5. 向前
driver.forward()
6. 后退
driver.back()
7. 刷新
driver.refresh()
8. 截屏
driver.get_screenshot_as_file(filename)
9. 设置等待时间:时间单位为s,有时候页面元素加载不全的时候,我们需要去用等待时间,等待页面加载完全。显示等待,隐式等待
time.sleep(n)
driver.implicitly_wait(10) # seconds
10. 获得当前页面的url
driver.current_url
11. 获得当前页面的标题
driver.title
12. 退出
driver.quit() 用于结束进程,关闭所有的窗口,最后测试结束的时候,建议大家用quit
13.获取元素的文本
driver.find_element_by_id("kw).text
14.设置该元素是否用户可见
driver.find_element_by_id("kw").is_displayed() #布尔值
[1]http://www.aosabook.org/en/selenium.html
[2]https://zhuanlan.zhihu.com/p/377124962
[3]https://www.selenium.dev/documentation/legacy/selenium_1/
[4]http://aosabook.org/en/selenium.html
[5]https://zhuanlan.zhihu.com/p/445477977
[6]https://www.bbsmax.com/A/GBJrQW7Zz0/
[7]https://www.w3.org/TR/webdriver/
[8]https://www.cnblogs.com/rowlingtech8/articles/15983394.html