一文讲透彻selenium
发展历史
selenium core
2004年在 ThoughtWorks 公司,一个叫做 Jason Huggins 为了减少手工测试的工作,自己写了一套 JavaScript
的库,这套库可以进行页面交互,并且可以重复的在不同浏览器上进行重复的测试操作。
这套库后来变为了 Selenium Core,为Selenium Remote Control (RC) 和 Selenium IDE提供了坚实的核心基础能力。[1]
selenium RC
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]:
- The Selenium Server which launches and kills browsers, interprets and runs the Selenese commands passed from the test program, and acts as an HTTP proxy, intercepting and verifying HTTP messages passed between the browser and the AUT.
- Client libraries which provide the interface between each programming language and the Selenium RC Server.
早期的webdriver
就在Selenium处于开发阶段的同时,另一款浏览器自动化框架WebDriver也正在ThoughtWorks公司的酝酿之中。WebDriver的最初代码在2007年初发布。WebDriver项目的初衷是把端对端测试与底层测试工具隔离开。通常情况下,这种隔离手段通过适配器(Adapter)模式完成。WebDriver正是来源于该方法在许多项目上的不断实践应用,最初是HtmlUnit的封装,工具发布后很快开始支持Internet Explorer和Firefox。[4]
Selenium 2.0 = Selenium 1.0 + WebDriver
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]。
Selenium 3.0 = Selenium 2.0 −Selenium RC
- Selenium 3.0在Selenium2.0的基础上有了更多的改进,支持的原生驱动方面更为丰富[6]
- 去掉了对SeleniumRC的支持。
- JDK的最低版本要求为JDK8(只有在测试分布式的时候,才需要Java环境)。
w3c WebDriver
W3C组织制定了一套浏览器自动化的规范叫做WebDriver,这套规范规定了所有的浏览器生产商都必须遵守这个规范[7]。
其实定义了好多的遵循的接口和WebDriver的概念。对于Chrome、Firefox、Opera、Safari他们都需要遵守这个规范并且实现规范里面的接口,这些实现一般都是伴随浏览器的开发进行的。
Selenium不管是WebDriver还是Remote WebDriver都是W3C WebDriver的一种实现而已。真正的核心浏览器的交互在对应的浏览器的WebDriver上,其实你有了对应的浏览器的WebDriver,参考W3C的标准接口文档HTTP-based wire protocol你就可以单独实现浏览器的操作
webdriver 基于的协议:JSON Wire protocol
JSON Wire protocol是在http协议基础上,对http请求及响应的body部分的数据的进一步规范。
我们知道在HTTP请求及响应中常常包括以下几个部分:http请求方法、http请求及响应内容body、http响应状态码等[8]。
http请求方法:
- GET:用来从服务器获取信息。比如获取网页的标题信息
- POST:向服务器发送操作请求。比如
findElement
、Click
等
http响应状态码:
在WebDriver中为了给用户以更明确的反馈信息,提供了更细化的http响应状态码,比如:
- 7: NoSuchElement
- 11:ElementNotVisible
- 200:Everything OK
http请求及响应的body部分:
- body部分主要传送具体的数据,在WebDriver中这些数据都是以JSON的形式存在并进行传送的,这就是JSON Wire protocol。
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语言的脚本了。
webDriver使用案例
抛开selenium,我们通过 w3c webDriver协议,也可以调用浏览器进行自动化进行操作。以通过调用ChromeDriver.exe
为例。
ChromeDriver.exe
是一个可以独立运行的服务器程序,适用于Chrome浏览器。它实现了 WebDriver 协议。(下载地址:http://chromedriver.storage.googleapis.com/index.html)
C:\Users\yangbo>chromeDriver.exe -hUsage: chromeDriver.exe [OPTIONS]Options --port=PORTport 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=LISTcomma-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 requestsimport timedriver_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 requestsimport timedriver_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相关。
selenium 的工作原理
当看懂上述webDriver的原理后,就不难理解selenium webdriver了。selenium webdriver不需要直接操作浏览器,而是通过HTTP接口向驱动发出符合WebDriver规范的指令,就可以完成对浏览器的控制。
python
+ selenium
+chrome浏览器
环境部署 为例
selenium依赖安装
C:\Users\name>pip install seleniumCollecting 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版本一一对应,否则可能出现执行报错。需要将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/
quick start
环境配置完毕,就可以创建一个测试脚本:
#test.pyfrom selenium import webdriverdriver = 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. 创建对象driverdriver = webdriver.Chrome()2. 浏览器中加载urldriver.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) # seconds10. 获得当前页面的urldriver.current_url11. 获得当前页面的标题driver.title12. 退出driver.quit() 用于结束进程,关闭所有的窗口,最后测试结束的时候,建议大家用quit13.获取元素的文本driver.find_element_by_id("kw).text14.设置该元素是否用户可见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