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自动化,可以考虑这种方式。
【整整200集】超超超详细的Python接口自动化测试进阶教程,真实模拟企业项目实战!!
相关文章:
Selenium/webdriver原理解析
最近在看一些底层的东西。driver翻译过来是驱动,司机的意思。如果将webdriver比做成司机,竟然非常恰当。 我们可以把WebDriver驱动浏览器类比成出租车司机开出租车。在开出租车时有三个角色: 乘客:他/她告诉出租车司机去哪里&…...
多用户跨境B2B2C商城后台管理系统快速搭建
搭建一个多用户跨境B2B2C商城后台管理系统需要考虑多个方面,包括系统架构设计、用户权限管理、商品管理、订单管理、支付管理、物流管理等。搭建步骤如下: 1. 系统架构设计 首先,需要设计一个稳定可靠的系统架构。选择一个适合B2B2C商城的商…...
MySQL 优化
问题描述 MySQL 的性能优化分为四个部分: 硬件和操作系统层面的优化架构设计层面的优化MySQL 程序配置优SQL 优化 一、硬件及操作系统层面优化 从硬件层面来说,影响 Mysql 性能的因素有,CPU、可用内存大小、磁盘读写速度、 网络带宽。 从操作…...
VMware Workstation及CentOS-7虚机安装
创建新的虚机: 选择安装软件(这里选的是桌面版,也可以根据实际情况进行选择) 等待检查软件依赖关系 选择安装位置,自主配置分区 创建一个普通用户 安装完成后重启 点击完成配置,进入登陆界面…...
双向带头循环链表+OJ题讲解
💓博主个人主页:不是笨小孩👀 ⏩专栏分类:数据结构与算法👀 刷题专栏👀 C语言👀 🚚代码仓库:笨小孩的代码库👀 ⏩社区:不是笨小孩👀 🌹欢迎大家三连关注&…...
电脑开不了机如何解锁BitLocker硬盘锁
事情从这里说起,不想看直接跳过 早上闲着无聊,闲着没事干,将win11的用户名称改成了含有中文字符的用户名,然后恐怖的事情发生了,蓝屏了… 然后就是蓝屏收集错误信息,重启,蓝屏收集错误信息&…...
Python Web开发 Jinja2模板引擎
在之前的文章中,简单介绍了Python Web开发框架Flask,知道了如何写个Hello World,但是距离用Flask开发真正的项目,还有段距离,现在我们目标更靠近一些 —— 学习下Jinja2模板。 模板的作用 模板是用来做什么的呢&…...
ubuntu上安装mosquitto服务
1、mosquitto是什么 Mosquitto 项目最初由 IBM 和 Eurotech 于 2013 年开发,后来于 2016 年捐赠给 Eclipse 基金会。Eclipse Mosquitto 基于 Eclipse 公共许可证(EPL/EDL license)发布,用户可以免费使用。作为全球使用最广的 MQTT 协议实现之一 &#x…...
嵌入式开发学习(STC51-9-led点阵)
内容 点亮一个点; 显示数字; 显示图像; LED点阵简介 LED 点阵是由发光二极管排列组成的显示器件 通常应用较多的是8 * 8点阵,然后使用多个8 * 8点阵可组成不同分辨率的LED点阵显示屏,比如16 * 16点阵可以使用4个8 *…...
RedisTemplate.opsForZSet()用法简介并举例
RedisTemplate.opsForZSet()是RedisTemplate类提供的用于操作ZSet类型(有序集合)的方法。它可以用于对Redis中的ZSet数据结构进行各种操作,如添加成员、获取成员、删除成员等。 下面是一些常用的RedisTemplate.opsForZSet()方法及其用法示例…...
Java个人博客系统--基于Springboot的设计与实现
目录 一、项目概述 应用技术 接口实现: 数据库定义: 数据库建表: 博客表数据库相关操作: 添加项⽬公共模块 加密MD5 页面展示:http://121.41.168.121:8080/blog_login.html 项目源码:https://gitee…...
在jupyter中下载数据集失败及解决方法(以IMDB为例)
在IMDB数据集下载时,由于网络原因下载失败,报错如下: Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz ConnectionResetError Traceback (most recent call last) … Exception: URL fetch f…...
【设计模式】-工厂方法模式
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它通过定义一个用于创建对象的接口,但是将具体对象的创建推迟到子类中。这样,子类可以决定要实例化的对象类型。工厂方法模式提供了一种方式,通…...
H7-TOOL的高速DAPLINK用于新版STM32CubeIDE V1.13及其以上版本的超简单实现方法(2023-08-08)
之前分享了一个方法,太繁琐了,H7-TOOL群的群友提供了一个方法,实现非常简单。1、使用STM32CubeMX或者自己创建一个STM32CubeIDE工程后,设置这两个地方即可: 配置调试器,设置完毕记得点击右下角的Apply 2、然…...
成功解决ubuntu-22.04的sudo apt-get update一直卡在【0% [Waiting for headers]】
成功解决ubuntu-22.04的sudo apt-get update一直卡在【0% [Waiting for headers]】 问题描述解决方案 问题描述 在下载安装包的时候一直卡在0% [Waiting for headers],报错信息如下: Get:1 file:/var/cudnn-local-repo-ubuntu1804-8.5.0.96 InRelease […...
openLayers实战(一):vue项目中的离线地图引入
最近的项目涉及到离线地图的操作,查阅社区文章,决定使用openLayersvue离线地图的方式进行开发,前期基础引入操作完全参考掘金文章,非常优秀全面的文章。 openlayers 实战离线地图 - 掘金 此外,开发过程的地图操作可参考…...
如何构造一个安全的单例?
为什么要问这个问题? 我们知道,单例是一种很常用的设计模式,主要作用就是节省系统资源,让对象在服务器中只有一份。但是实际开发中可能有很多人压根没有写过单例这种模式,只是看过或者为了面试去写写demo熟悉一下。那…...
单片机开发 esp8266
一、固件界面 二、项目介绍 固件名称:esp8266-universalboard v1.0 提供商: 半条虫(466814195) 下载:esp8266-universalboard.bin 源码地址:Gitlab...
Linux 查看版本和用户权限提升实践心得
文章目录 linux (Ubuntu内核)查看版本版本信息解释内置yum工具?用户权限提升操作步骤 查看deepin系统的版本和其debian的版本遇到的问题:deepin-release文件不存在 linux (Ubuntu内核)查看版本 使用lsb_release命令: lsb_release -a该命令将…...
多线程编程5:线程同步和进程通信(C++11和linux)
常见的线程同步 linux: 互斥锁:实现共享资源的串行访问,有三个版本普通锁(默认属性),检错锁(可以防止相同线程重复加锁)和递归锁(相同线程可以重复加锁)条件变量:配合互斥锁使用,实现线程之间的通信&#…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
