1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
| 最近在看一些底层的东西。
driver翻译过来是驱动,司机的意思。如果将webdriver比做成司机,竟然非常恰当。
我们可以把WebDriver驱动浏览器类比成出租车司机开出租车。
在开出租车时有三个角色: 乘客:他/她告诉出租车司机去哪里,大概怎么走 出租车司机:他按照乘客的要求来操控出租车 出租车:出租车按照司机的操控完成真正的行驶,把乘客送到目的地
在WebDriver中也有类似的三个角色:
自动化测试代码:自动化测试代码发送请求给浏览器的驱动(比如火狐驱动、谷歌驱动) 浏览器的驱动:它来解析这些自动化测试的代码,解析后把它们发送给浏览器 浏览器:执行浏览器驱动发来的指令,并最终完成工程师想要的操作。
所以在这个类比中:
工程师写的自动化测试代码就相当于是乘客
浏览器的驱动就相当于是出租车司机
浏览器就相当于是出租车
面再从技术上解释下WebDriver的工作原理: 从技术上讲,也同样是上面的三个角色:
WebDriver API(基于Java、Python、C#等语言)
对于java语言来说,就是下载下来的selenium的Jar包,比如selenium-java-3.8.1.zip包,代表Selenium3.8.1的版本
浏览器的驱动(browser driver)
每个浏览器都有自己的驱动,均以exe文件形式存在
比如谷歌的chromedriver.exe、火狐的geckodriver.exe、IE的IEDriverServer.exe
浏览器
浏览器当然就是我们很熟悉的常用的各种浏览器。
那在WebDriver脚本运行的时候,它们之间是如何通信的呢?为什么同一个browser driver即可以处理java语言的脚本,也可以处理python语言的脚本呢?让我们来看一下,一条Selenium脚本执行时后端都发生了哪些事情:
对于每一条Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动 浏览器驱动中包含了一个HTTP Server,用来接收这些http请求 HTTP Server接收到请求后根据请求来具体操控对应的浏览器 浏览器执行具体的测试步骤 浏览器将步骤执行结果返回给HTTP Server HTTP Server又将结果返回给Selenium的脚本,如果是错误的http代码我们就会在控制台看到对应的报错信息。 为什么使用HTTP协议呢?
因为HTTP协议是一个浏览器和Web服务器之间通信的标准协议,而几乎每一种编程语言都提供了丰富的http libraries,这样就可以方便的处理客户端Client和服务器Server之间的请求request及响应response,WebDriver的结构中就是典型的C/S结构,WebDriver API相当于是客户端,而小小的浏览器驱动才是服务器端。
WebDriver基于的协议:JSON Wire protocol。
JSON Wire protocol是在http协议基础上,对http请求及响应的body部分的数据的进一步规范。
我们知道在HTTP请求及响应中常常包括以下几个部分:http请求方法、http请求及响应内容body、http响应状态码等。
常见的http请求方法:
GET:用来从服务器获取信息。比如获取网页的标题信息
POST:向服务器发送操作请求。比如findElement,Click等
http响应状态码:
在WebDriver中为了给用户以更明确的反馈信息,提供了更细化的http响应状态码,比如:
7:NoSuchElement
11:ElementNotVisible
200:Everything OK
现在到了最关键的http请求及响应的body部分了:
body部分主要传送具体的数据,在WebDriver中这些数据都是以JSON的形式存在并进行传送的,这就是JSON Wire protocol。
Selenium 是将各个浏览器的API封装成" Selenium自己设计定义的协议,名字叫做The WebDriver Wire Protocol " 的webdriver API
操作层面:
1、测试人员编写UI自动化测试脚本(java,python等等),运行脚本后,程序会打开指定的webdriver浏览器
webdriver浏览器作为一个remote-server 接受脚本的命令,同时webservice会打开一个端口:http://localhost:9515 浏览器则会监听这个端口
2、webservice会将脚本语言翻译成json格式传递给浏览器执行操作命令
逻辑层面:
1、测试人员执行测试脚本后,就创建了一个session, 通过http 请求向webservice发送了restfull的请求。
2、webservice翻译restfull的请求为浏览器能懂的脚本,然后接受脚本执行结果。
3、webservice将结果进行封装--json 给到客户端client/测试脚本 ,然后client就知道操作是否成功,同时测试也可以进行校验了。
我们可以验证一下: 下载好chromedriver,放到环境变量里,注意要和chrome浏览器版本对上,然后执行chromedriver 可以看到,会启动一个server, 并开启端口9515:
andersons-iMac:~ anderson$ chromedriver Starting ChromeDriver 2.39.562713 (dd642283e958a93ebf6891600db055f1f1b4f3b2) on port 9515 Only local connections are allowed. GVA info: Successfully connected to the Intel plugin, offline Gen9 强调了只允许本地连接。
前面已经提过了,乘客向司机发一个请求, 行为是构造一个http请求 构造的请求是这样子的:
请求方式 :POST 请求地址 :http://localhost:9515/session 请求body :
capabilities = { "capabilities": { "alwaysMatch": { "browserName": "chrome" }, "firstMatch": [ {} ] }, "desiredCapabilities": { "platform": "ANY", "browserName": "chrome", "version": "", "chromeOptions": { "args": [], "extensions": [] } } } 我们可以尝试使用python requests 向 ChromeDriver发送请求
import requests import json session_url = 'http://localhost:9515/session' session_pars = {"capabilities": {"firstMatch": [{}], \ "alwaysMatch": {"browserName": "chrome",\ "platformName": "any", \ "goog:chromeOptions": {"extensions": [], "args": []}}}, \ "desiredCapabilities": {"browserName": "chrome", \ "version": "", "platform": "ANY", "goog:chromeOptions": {"extensions": [], "args": []}}} r_session = requests.post(session_url,json=session_pars) print(json.dumps(r_session.json(),indent=2)) 结果:
{ "sessionId": "44fdb7b1b048a76c0f625545b0d2567b", "status": 0, "value": { "acceptInsecureCerts": false, "acceptSslCerts": false, "applicationCacheEnabled": false, "browserConnectionEnabled": false, "browserName": "chrome", "chrome": { "chromedriverVersion": "2.40.565386 (45a059dc425e08165f9a10324bd1380cc13ca363)", "userDataDir": "/var/folders/yd/dmwmz84x5rj354qkz9rwwzbc0000gn/T/.org.chromium.Chromium.RzlABs" }, "cssSelectorsEnabled": true, "databaseEnabled": false, "handlesAlerts": true, "hasTouchScreen": false, "javascriptEnabled": true, "locationContextEnabled": true, "mobileEmulationEnabled": false, "nativeEvents": true, "networkConnectionEnabled": false, "pageLoadStrategy": "normal", "platform": "Mac OS X", "rotatable": false, "setWindowRect": true, "takesHeapSnapshot": true, "takesScreenshot": true, "unexpectedAlertBehaviour": "", "version": "71.0.3578.80", "webStorageEnabled": true } } 如何打开一个网页,类似driver.get(url) 那么构造的请求是:
请求方式 :POST 请求地址 :http://localhost:9515/session/:sessionId/url
注意:上述地址中的 ":sessionId" 要用启动浏览器的请求返回结果中的sessionId的值 例如:我刚刚发送请求,启动浏览器,返回结果中"sessionId": "44fdb7b1b048a76c0f625545b0d2567b" 然后请求的URL地址 请求地址:http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/url
请求body :{"url": "https://www.baidu.com", "sessionId": "44fdb7b1b048a76c0f625545b0d2567b"} 即:
import requests url = 'http://localhost:9515/session/44fdb7b1b048a76c0f625545b0d2567b/url' pars = {"url": "https://www.baidu.com", "sessionId": "44fdb7b1b048a76c0f625545b0d2567b"} r = requests.post(url,json=pars) print(r.json()) 如何定位元素,类似driver.finde_element_by_xx:
请求方式 :POST 请求地址 :http://localhost:9515/session/:sessionId/element
注意:上述地址中的 ":sessionId" 要用启动浏览器的请求返回结果中的sessionId的值 例如:我刚刚发送请求,启动浏览器,返回结果中"sessionId": "b2801b5dc58b15e76d0d3295b04d295c" 然后我构造 查找页面元素的请求地址 请求地址:http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element
请求body :{"using": "css selector", "value": ".postTitle a", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"} 即:
import requests url = 'http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element' pars = {"using": "css selector", "value": ".postTitle a", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"} r = requests.post(url,json=pars) print(r.json()) 如何操作元素:类似click()
请求方式 :POST 请求地址 :http://localhost:9515/session/:sessionId/element/:id/click
注意:上述地址中的 ":sessionId" 要用启动浏览器的请求返回结果中的sessionId的值 :id 要用元素定位请求后返回ELEMENT的值
例如:我刚刚发送请求,启动浏览器,返回结果中"sessionId": "b2801b5dc58b15e76d0d3295b04d295c" 元素定位,返回ELEMENT的值"0.11402119390850629-1"
然后我构造 点击页面元素的请求地址 请求地址:http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element/0.11402119390850629-1/click
请求body :{"id": "0.11402119390850629-1", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"} 即:
import requests url = 'http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element/0.11402119390850629-1/click' pars ={"id": "0.5930642995574296-1", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"} r = requests.post(url,json=pars) print(r.json()) 从上面可以看出来,UI自动化,其实也可以写成API自动化。 只是,只是 好繁琐,没有封装好的wedriver指令好用,有点脱裤子放屁的感觉。 我们来写段代码感觉一下:
import requests import time
capabilities = { "capabilities": { "alwaysMatch": { "browserName": "chrome" }, "firstMatch": [ {} ] }, "desiredCapabilities": { "platform": "ANY", "browserName": "chrome", "version": "", "chromeOptions": { "args": [], "extensions": [] } } }
# 打开浏览器 http://127.0.0.1:9515/session res = requests.post('http://127.0.0.1:9515/session', json=capabilities).json() session_id = res['sessionId']
# 打开百度 requests.post('http://127.0.0.1:9515/session/%s/url' % session_id, json={"url": "http://www.baidu.com", "sessionId": session_id})
time.sleep(3)
# 关闭浏览器,删除session requests.delete('http://127.0.0.1:9515/session/%s' % session_id, json={"sessionId": session_id}) 其实搞懂真正的原理,也就是为了方便解决问题,在debug的时候,更方便的查看和解决问题。
当然,如果在接口自动化里面也需要调用少量的UI自动化,可以考虑这种方式。
|