Selenium/webdriver介绍以及工作原理

2023-12-04

最近在看一些底层的东西。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自动化,可以考虑这种方式。

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Selenium/webdriver介绍以及工作原理 的相关文章

随机推荐

  • 「JMeter」数据参数化大集合:让你的测试数据更加强大!

    大家好 我是小马哥 每天进步一点点 今天分享的内容是 Jmeter之数据参数化方法汇总 一 什么是参数化 Jmeter参数化是指将脚本中的某些需要输入数据使用参数来代替 在脚本运行时指定参数的取值范围和规则 脚本在运行时就可以根据需要选取不
  • 题解 | #筛选某店铺最有价值用户中消费最多前5名#

    背景 双非本211硕 编程语言 c cuda python方向是算法部署 AI框架 算子开发目标行业 互联网 半导体已oc 深势科技 高性能计算浙 思路 首先join两张表获取所有员工对应的薪水信息 题目为获取每个部门中当前员工薪水最高 拆
  • 集成测试和系统测试的区别是什么?

    前面的文章聊过测试过程效率提升和演变 也分享了我对于单元测试的一些实践和思考 这篇文章接着上篇单元测试的内容 聊聊集成测试的特点 要解决什么问题 以及实践的注意事项 下图是 从需求出现到最后的线上发布 大致要经历的几个阶段 狭义上的测试活动
  • 引爆AI音乐热潮,拓世AI助你创造属于自己的音乐

    使用人工智能创作音乐早已不是什么新鲜事 如果说仅仅几年前 互联网上涌现的那些由AI创作的Kanye West歌曲 听起来仿佛来自科幻小说的奇妙世界 那么如今Al音乐的发展速度已远超人们想象 以至于每当你浏览各大网络平台时 都能看到相关内容
  • 基于java的俄罗斯方块游戏系统设计与实现

    基于java的俄罗斯方块游戏系统设计与实现 I 引言 A 研究背景和动机 基于Java的俄罗斯方块游戏系统设计与实现的研究背景和动机 俄罗斯方块是一种经典的益智游戏 游戏规则简单 但难度较大 需要玩家有良好的计算能力和手眼协调能力 近年来
  • 题解 | #试卷完成数同比2020年的增长率及排名变化#

    import mathn int input count 0 for i in range n 1 s pow i 2 include
  • Airtest进阶使用篇!提高脚本稳定性 + 批量运行脚本!

    一 背景 今天彭于晏为大家分享Airtest进阶使用篇 主要包含两块的内容 提高脚本稳定性 批量运行脚本生成测试报告 二 提高脚本稳定性 1 添加全局配置 全局设置 ST FIND TIMEOUT 10 设置隐式等待时长 默认识别图片时间是
  • 基于java的宠物领养系统设计与实现

    基于java的宠物领养系统设计与实现 I 引言 A 研究背景和动机 基于Java的宠物领养系统设计与实现的研究背景和动机是构建一个方便 安全 可靠 易用的宠物领养平台 以满足领养者 领养机构 宠物领养信息服务平台等多方需求 该平台需要具备以
  • 提升Jmeter测试效率的9种参数化方法!

    jmeter工具无论做接口测试还是性能测试 参数化都是一个必须掌握且非常有用的知识点 参数化的使用场景 1 多个请求都是同一个ip地址 若服务器地址更换了 则脚本需要更改每个请求的ip 2 注册账号 不允许账号重复 想批量注册用户时 3 模
  • Jmeter线程组和同步定时器!

    1 线程组 1 1线程组的定义 进程 正在运作中的程序 QQ 微信 迅雷 线程 进程中的执行单元 一个进程包含多个线程 下载 播放 线程组 按照线程性质对线程进行分组 单个下载 批量下载 接口分组 线程组 就是一个线程组 里面有若干个请求
  • 题解 | #糖糖别胡说,我真的不是签到题目#

    可以提前把施法后的b算出来 因为前面的结果会影响后面的判断 include
  • 需求不明确的情况下,测试该如何处理?

    当需求不明确的情况下 测试团队可以采取以下措施来处理 1 与项目团队进行沟通 测试团队应与项目团队密切合作 与业务分析师 产品经理等相关人员进行沟通 以获取更多的需求细节和背景信息 通过与相关方的交流 可以更好地理解需求的意图和预期 从而更
  • 商城免费搭建之java商城 鸿鹄云商 B2B2C产品概述

    B2B2C平台 以传统电商行业为基石 鸿鹄云商支持 商家入驻 平台自营 多运营模式 积极打造 全新市场 全新 模式 企业级B2B2C电商平台 致力干助力各行 互联网创业腾飞并获取更多的收益 从消费者出发 助力企业构建完整 电商交易生态 整合
  • Leetcode 剑指 Offer II 055. 二叉搜索树迭代器

    题目难度 中等原题链接今天继续更新 Leetcode 的剑指 Offer 专项突击版 系列 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列 include
  • 最全最详细ChatGPT角色预设词教程,Prompt分享

    使用指南 1 可直复制使用 2 可以前往已经添加好Prompt预设的AI系统测试使用 可自定义添加使用 雅思写作考官 我希望你假定自己是雅思写作考官 根据雅思评判标准 按我给你的雅思考题和对应答案给我评分 并且按照雅思写作评分细则给出打分依
  • 题解 | #实现二叉树先序,中序和后序遍历#

    include
  • 乘数而启,向数而行|2023数字金融创新发展论坛成功举办

    订阅制 C端消费者早已耳熟能详 如今也凭借灵活 服务更新稳定的特点 逐渐成为B端企业服务的新热点 比如对中小企业而言 办公IT设备等配套支出都必不可少 但收入 栗栗在线招人啦 哇 各位 招人好难啊 你们赶紧来找栗栗啊 不限经验 不限地域 不
  • AI知识库:智能化的知识管理

    随着人工智能技术的不断发展 越来越多的企业开始关注如何利用AI技术提升业务运营效率 其中 AI知识库作为一种智能化的知识管理工具 已经在各行各业得到了广泛的应用 接下来就探讨一下AI知识库是如何帮助企业实现智能化知识管理的 一 AI知识库的
  • Leetcode-二叉树oj题

    1 二叉树的前序遍历 144 二叉树的前序遍历 https leetcode cn problems binary tree preorder traversal 这个题目在遍历的基础上还要求返回数组 数组里面按前序存放二叉树节点的值 既然
  • Selenium/webdriver介绍以及工作原理

    最近在看一些底层的东西 driver翻译过来是驱动 司机的意思 如果将webdriver比做成司机 竟然非常恰当 我们可以把WebDriver驱动浏览器类比成出租车司机开出租车 在开出租车时有三个角色 乘客 他 她告诉出租车司机去哪里 大概