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 :

python 复制代码
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"}

即:

python 复制代码
 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"}

即:

python 复制代码
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指令好用,有点脱裤子放屁的感觉。

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

python 复制代码
  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自动化,可以考虑这种方式。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

相关推荐
点云SLAM11 分钟前
PyTorch 中contiguous函数使用详解和代码演示
人工智能·pytorch·python·3d深度学习·contiguous函数·张量内存布局优化·张量操作
尘浮72824 分钟前
60天python训练计划----day45
开发语言·python
哆啦A梦的口袋呀35 分钟前
基于Python学习《Head First设计模式》第六章 命令模式
python·学习·设计模式
努力搬砖的咸鱼37 分钟前
从零开始搭建 Pytest 测试框架(Python 3.8 + PyCharm 版)
python·pycharm·pytest
Calvex40 分钟前
PyCharm集成Conda环境
python·pycharm·conda
一千柯橘1 小时前
python 项目搭建(类比 node 来学习)
python
sduwcgg1 小时前
python的numpy的MKL加速
开发语言·python·numpy
大模型真好玩1 小时前
可视化神器WandB,大模型训练的必备工具!
人工智能·python·mcp
东方佑1 小时前
使用 Python 自动化 Word 文档样式复制与内容生成
python·自动化·word
钢铁男儿1 小时前
Python 接口:从协议到抽象基 类(定义并使用一个抽象基类)
开发语言·python