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 :  

capabilities = {"capabilities": {"alwaysMatch": {"browserName": "chrome"},"firstMatch": [{}]},"desiredCapabilities": {"platform": "ANY","browserName": "chrome","version": "","chromeOptions": {"args": [],"extensions": []}}}我们可以尝试使用python requests 向 ChromeDriver发送请求import requestsimport jsonsession_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 requestsurl = '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 requestsurl = '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 requestsimport timecapabilities = {"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自动化,可以考虑这种方式。


              【下面是我整理的2023年最全的软件测试工程师学习知识架构体系图】


一、Python编程入门到精通

二、接口自动化项目实战 

三、Web自动化项目实战

四、App自动化项目实战 

五、一线大厂简历

六、测试开发DevOps体系 

七、常用自动化测试工具

八、JMeter性能测试 

九、总结(尾部小惊喜)

生命不息,奋斗不止。每一份努力都不会被辜负,只要坚持不懈,终究会有回报。珍惜时间,追求梦想。不忘初心,砥砺前行。你的未来,由你掌握!

生命短暂,时间宝贵,我们无法预知未来会发生什么,但我们可以掌握当下。珍惜每一天,努力奋斗,让自己变得更加强大和优秀。坚定信念,执着追求,成功终将属于你!

只有不断地挑战自己,才能不断地超越自己。坚持追求梦想,勇敢前行,你就会发现奋斗的过程是如此美好而值得。相信自己,你一定可以做到! 

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/224152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot_tomcat开启access log日志,监控打印每个请求地址和响应时间

springboot_tomcat开启access log日志,监控打印每个请求地址和响应时间 一、前言二、环境三、配置对应的对象信息四、AccessLog配置默认值及说明五、配置实例六、pattern参数组成说明七、常用的pattern配置7.1 pattern默认值7.2 默认配置说明7.3 打印请求、响应中co…

解决因找不到qt5core.dll文件而导致无法执行代码问题

Qt5core.dll是Qt5框架的核心模块,用于提供基本的Qt功能。如果在代码执行过程中找不到qt5core.dll,可能导致相关功能无法正常使用。以下是五种详细解决方法、qt5core.dll文件详细介绍以及丢失原因。 一、qt5core.dll文件详细介绍 文件名称:q…

白杨SEO:从董宇辉事件聊聊个人IP这个事,企业为什么要重视个人IP?

董宇辉事件是指东方甄选因主播董宇辉的小作文到底由谁写的风波,具体详情可以网上搜搜,最近是热搜。下图1产生原因,下图2董宇辉回应截图。 白杨SEO说一下,这里不关注谁对谁错。我想说的是,一是现在个人IP非常重要&…

《微信小程序开发从入门到实战》学习五十一

4.5 实现投票小程序服务端功能 4.5.4 获取我的投票信息 最后实现“我的”投票页面。该页面pages/myVote/myVote.js还有一个todo,获取用参与的所有投票的列表。这个功能需要用到用户的openid,因此也需要使用云函数来实现。 新建myVoteList云函数。完成代码将其上传…

C语言结构体小项目之通讯录代码实现+代码分析

一、思路 1.文件 这里由于通讯录实现代码较长,因此分三个文件进行,contact.c用于实现通讯录主体代码,声明各项头文件用contact.h实现,测试用test.c 二.功能 增加联系人删除联系人修改联系人查找指定联系人排序显示通讯录的信息…

Mac 打不开github解决方案

序言 github 时有打不开的情况,为此很是烦恼,这里分享一下如何解决这种问题,其实问题的本质是在访问github网页时无法通过github.com的二级域名进行动态域名解析。 解决方案 手动配置静态文件hosts,将该域名和IP的映射关系添加…

vue文件下载请求blob文件流token失效的问题

页面停留很久token失效没有刷新页面,这时候点击下载依然可以导出文件,但是文件打不开且接口实际上返回的是401,这是因为文件下载的方式通过window创建a标签的形式打开的,并没有判断token失效问题 const res await this.$axios.…

10:00面试,10:08就出来了,问的问题超出我认知

本来在上家公司上班,加班是每天必不可少的,但是看在加班费给的比较多的份上,就没有太计较了。没想到9月份下一份通知,所有人不准加班,加班费不仅没有了,薪资还要降30%,这下搞的生活都生活不下去了。 还好有…

Dijkstra求最短路 I(Dijkstra算法)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。 输入格式 第一行包含整数 n 和 m。 接下来 m 行每行包含三个整…

vscode 常用 Emmet Abbreviation 快捷方式

vscode 常用 Emmet Abbreviation 快捷方式 输入快捷指令后&#xff0c; 按“tab”键或者回车键 即可 .box*5&#xff1a;生成 5 个 class 为 box 的 div 元素 <div class"box"></div> <div class"box"></div> <div class&quo…

python和pygame实现捉小兔游戏

python和pygame实现捉小兔游戏 python和pygame实现捉小兔游戏&#xff0c;需要安装使用第三方库pygame&#xff0c;关于Python中pygame游戏模块的安装使用可见 https://blog.csdn.net/cnds123/article/details/119514520 下面是使用Python和Pygame创建的游戏&#xff0c;其中有…

常用的Linux基本命令

这些是一些常用的Linux基本命令&#xff0c;涵盖了文件操作、系统管理、进程管理、磁盘管理等方面&#xff1a; ls&#xff1a;列出目录内容cd&#xff1a;切换当前工作目录pwd&#xff1a;显示当前工作目录的绝对路径mkdir&#xff1a;创建新目录rmdir&#xff1a;删除空目录…

Pytorch从零开始实战13

Pytorch从零开始实战——ResNet与DenseNet探索 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——ResNet与DenseNet探索环境准备数据集模型选择开始训练可视化总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#xff0c;P…

Java连接数据库实现用户登录和注册功能

目录 需求内容如下 示例代码 数据库studb Java代码 效果图 需求内容如下 1&#xff0c;创建数据库studb 2&#xff0c;库中添加用户表userinfo,包含如下字段 用户id ,用户名&#xff0c;用户密码&#xff0c;用户权限 &#xff08;数据类型和约束自己定义&#xff09…

web微服务规划

一、背景 通过微服务来搭建web系统&#xff0c;就要对微服务进行规划&#xff0c;包括服务的划分&#xff0c;每个服务和数据库的命名规则&#xff0c;服务用到的端口等。 二、微服务划分 1、根据业务进行拆分 如&#xff1a; 一个购物系统可以将微服务拆分为基础中心、会员…

SpringMVC异常处理机制

2.1 异常描述 在J2EE项目的开发中&#xff0c;不管是对底层的数据库操作过程&#xff0c;还是业务层的处理过程&#xff0c;还是控制层的处理过程&#xff0c;都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常&#xff0c;系统的代码耦合度高&a…

【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]

阅读导航 引言一、thread类的简单介绍二、线程函数详细介绍1. start() 函数&#xff08;1&#xff09;头文件&#xff08;2&#xff09;函数原型 2. join() 函数&#xff08;1&#xff09;头文件&#xff08;2&#xff09;函数原型 3. detach() 函数&#xff08;1&#xff09;头…

LeetCode Hot100 25.K个一组翻转链表

题目&#xff1a; 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯…

7+m6A+分型+实验,甲基化方向的生信思路,没有思路的同学可参考

今天给同学们分享一篇生信文章“Landscape analysis of m6A modification regulators related biological functions and immune characteristics in myasthenia gravis”&#xff0c;这篇文章发表在J Transl Med期刊上&#xff0c;影响因子为7.4。 结果解读&#xff1a; MG相…

快速文件搜索软件 Everything-voidtools

Everything 文件搜索软件 "Everything"是一款快速、轻巧且高效的桌面搜索引擎软件&#xff0c;专门用于在 Windows 操作系统上搜索文件和文件夹。它的主要特点是超快的搜索速度和精准的搜索结果&#xff0c;能够让用户快速找到他们需要的文件或文件夹。 1. 快速搜…