selenium/webdriver运行原理与机制

最近在看一些底层的东西。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 :


  
  1. capabilities = {

  2.    "capabilities": {

  3.    "alwaysMatch": {

  4.    "browserName": "chrome"

  5.    },

  6.    "firstMatch": [

  7.    {}

  8.    ]

  9.    },

  10.    "desiredCapabilities": {

  11.    "platform": "ANY",

  12.    "browserName": "chrome",

  13.    "version": "",

  14.    "chromeOptions": {

  15.    "args": [],

  16.    "extensions": []

  17.    }

  18.    }

  19.   }

  20.   我们可以尝试使用python requests 向 ChromeDriver发送请求

  21.   import requests

  22.   import json

  23.   session_url = 'http://localhost:9515/session'

  24.   session_pars = {"capabilities": {"firstMatch": [{}], \

  25.    "alwaysMatch": {"browserName": "chrome",\

  26.    "platformName": "any", \

  27.    "goog:chromeOptions": {"extensions": [], "args": []}}}, \

  28.    "desiredCapabilities": {"browserName": "chrome", \

  29.    "version": "", "platform": "ANY", "goog:chromeOptions": {"extensions": [], "args": []}}}

  30.   r_session = requests.post(session_url,json=session_pars)

  31.   print(json.dumps(r_session.json(),indent=2))

  32.   结果:

  33.   {

  34.    "sessionId": "44fdb7b1b048a76c0f625545b0d2567b",

  35.    "status": 0,

  36.    "value": {

  37.    "acceptInsecureCerts": false,

  38.    "acceptSslCerts": false,

  39.    "applicationCacheEnabled": false,

  40.    "browserConnectionEnabled": false,

  41.    "browserName": "chrome",

  42.    "chrome": {

  43.    "chromedriverVersion": "2.40.565386 (45a059dc425e08165f9a10324bd1380cc13ca363)",

  44.    "userDataDir": "/var/folders/yd/dmwmz84x5rj354qkz9rwwzbc0000gn/T/.org.chromium.Chromium.RzlABs"

  45.    },

  46.    "cssSelectorsEnabled": true,

  47.    "databaseEnabled": false,

  48.    "handlesAlerts": true,

  49.    "hasTouchScreen": false,

  50.    "javascriptEnabled": true,

  51.    "locationContextEnabled": true,

  52.    "mobileEmulationEnabled": false,

  53.    "nativeEvents": true,

  54.    "networkConnectionEnabled": false,

  55.    "pageLoadStrategy": "normal",

  56.    "platform": "Mac OS X",

  57.    "rotatable": false,

  58.    "setWindowRect": true,

  59.    "takesHeapSnapshot": true,

  60.    "takesScreenshot": true,

  61.    "unexpectedAlertBehaviour": "",

  62.    "version": "71.0.3578.80",

  63.    "webStorageEnabled": true

  64.    }

  65.   }

如何打开一个网页,类似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"}

即:


  
  1.  import requests

  2.   url = 'http://localhost:9515/session/44fdb7b1b048a76c0f625545b0d2567b/url'

  3.   pars = {"url": "https://www.baidu.com", "sessionId": "44fdb7b1b048a76c0f625545b0d2567b"}

  4.   r = requests.post(url,json=pars)

  5.   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"}

即:


  
  1. import requests

  2.   url = 'http://localhost:9515/session/b2801b5dc58b15e76d0d3295b04d295c/element/0.11402119390850629-1/click'

  3.   pars ={"id": "0.5930642995574296-1", "sessionId": "b2801b5dc58b15e76d0d3295b04d295c"}

  4.   r = requests.post(url,json=pars)

  5.   print(r.json())

 从上面可以看出来,UI自动化,其实也可以写成API自动化。

  只是,只是

  好繁琐,没有封装好的wedriver指令好用,有点脱裤子放屁的感觉。

  我们来写段代码感觉一下:


  
  1.   import requests

  2.   import time

  3.   capabilities = {

  4.    "capabilities": {

  5.    "alwaysMatch": {

  6.    "browserName": "chrome"

  7.    },

  8.    "firstMatch": [

  9.    {}

  10.    ]

  11.    },

  12.    "desiredCapabilities": {

  13.    "platform": "ANY",

  14.    "browserName": "chrome",

  15.    "version": "",

  16.    "chromeOptions": {

  17.    "args": [],

  18.    "extensions": []

  19.    }

  20.    }

  21.   }

# 打开浏览器 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自动化,可以考虑这种方式。

 

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

  1. 文档获取方式:

  2. 加入我的软件测试交流群:680748947免费获取~(同行大佬一起学术交流,每晚都有大佬直播分享技术知识点)

这份文档,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

以上均可以分享,只需要你搜索vx公众号:程序员雨果,即可免费领取

相关推荐

  1. 【大语言模型】Transformer原理以及运行机制

    2024-03-30 00:46:03       75 阅读
  2. 搜索引擎的定义运行原理

    2024-03-30 00:46:03       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-30 00:46:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-30 00:46:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-30 00:46:03       82 阅读
  4. Python语言-面向对象

    2024-03-30 00:46:03       91 阅读

热门阅读

  1. js移除子元素

    2024-03-30 00:46:03       37 阅读
  2. 应用Druid解析SQL获取查询表字段、参数信息

    2024-03-30 00:46:03       37 阅读
  3. 5.89 BCC工具之tcptop.py解读

    2024-03-30 00:46:03       34 阅读
  4. PTA 道路管制

    2024-03-30 00:46:03       38 阅读
  5. VUE3从i18n国际化组件动态获取字符串

    2024-03-30 00:46:03       39 阅读
  6. 星图金融价值跃迁:打造“一体两翼”正向循环

    2024-03-30 00:46:03       43 阅读
  7. 5、Cocos Creator 动作系统

    2024-03-30 00:46:03       40 阅读
  8. Composer常见错误以及常用解决办法指南

    2024-03-30 00:46:03       39 阅读
  9. Vue模板引用(ref),超详细

    2024-03-30 00:46:03       44 阅读