selenium底层原理详解
目录
1、selenium版本的演变
1.1、Selenium 1.x(Selenium RC时代)
1.2、Selenium 2.x(WebDriver整合时代)
1.3、Selenium 3.x +
2、selenium原理说明
3、源码说明
3.1、启动webdriver服务建立连接
3.2、发送操作
1、selenium版本的演变
1.1、Selenium 1.x(Selenium RC时代)
-
核心原理:
- Selenium RC(Remote Control):Selenium 1.x主要通过Selenium RC来实现自动化测试。Selenium RC启动一个Server,该Server负责控制浏览器行为。
- JavaScript注入技术:Selenium RC将操作Web元素的API调用转化为JavaScript代码,然后通过Selenium Core(一堆JavaScript函数的集合)注入到浏览器中执行。这种方式依赖于浏览器对JavaScript的支持,但速度较慢且稳定性依赖于Selenium内核对API的JavaScript翻译质量。
1.2、Selenium 2.x(WebDriver整合时代)
核心原理:
- WebDriver的引入:Selenium 2.x整合了WebDriver项目,使得Selenium更加强大。WebDriver利用浏览器原生的API,封装成一套面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(如截屏、窗口大小调整、启动/关闭浏览器等)。
- 浏览器原生API:由于使用浏览器原生API,WebDriver的速度大大提升,且调用的稳定性交给了浏览器厂商本身。然而,不同浏览器厂商对Web元素的操作和呈现存在差异,因此WebDriver需要为不同浏览器提供不同的实现(如ChromeDriver、FirefoxDriver等)。
- WebDriver Wire协议:WebDriver启动后会在特定端口上启动基于WebDriver Wire协议的Web Service,所有对WebDriver的API调用都会通过HTTP请求发送给这个Web Service。
1.3、Selenium 3.x +
核心原理:
- 继承2.x的特性:Selenium 3.x在底层原理上与Selenium 2.x保持一致,继续利用WebDriver和浏览器原生API进行操作。
- 新增特性:Selenium 3.x加入了对更多浏览器原生驱动的支持,如Edge和Safari的原生驱动,以及更新了对Firefox的支持(通过geckodriver)。
- 移除Selenium RC:与Selenium 2.x相比,Selenium 3.x去除了Selenium RC组件,更加专注于WebDriver的使用。
- 新增功能和API:为了满足用户不断变化的需求,Selenium会引入新的功能和API,以支持更复杂的测试场景和用例。
2、selenium原理说明
说明:这里说的原理都是整合了WebDriver之后的selenium版本。
思考:selenium是如何驱动浏览器做各种操作的呢?
- 分析:
- 首先我们想想,我们可以直接和浏览器交互吗,显然是不能,这时候就需要借助一个代理人帮我们做这件事,这个代理人就是WebDriver,我们不知道浏览器内核的各种API,难道浏览器厂商还不知道吗,所以他们就提供这样一个代理人给我们使用。
- 也就是我们现在知道WebDriver提供一个服务,我们去请求这个服务把对浏览器的操作通过HTTP请求发送给WebDriver这个服务,再由它把操作解析后去调用浏览器的API,最终结果原路返回。
- 这个时候我们还需要把这些操作统一起来才行,不然不太可能我们自己总是去调用接口发送请求吧,这时候selenium client就出现了,它在内部帮我们处理好了底层通信的一切,还把对浏览器的操作统一封装成一个个函数供给我们操作,我们只需要关心操作和操作返回的结果就行。
- 综上就是整个selenium做的事情了。
把上面的过程在提炼一下,流程如下:
- 1.对于每一条Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动,最开始建立连接时服务端返回一个sessionid给客户端,后续的交互都是通过sessionid进行交互
- 2.浏览器驱动中包含了一个HTTP Server,用来接收这些http请求
- 3.HTTP Server接收到请求后根据请求来具体操控对应的浏览器
- 4.浏览器执行具体的测试步骤
- 5.浏览器将步骤执行结果返回给HTTP Server
- 6.HTTP Server又将结果返回给Selenium的脚本,如果是错误的http代码我们就会在控制台看到对应的报错信息。
3、源码说明
说明:我们从源码的角度看看,底层是如何进行交互的
3.1、启动webdriver服务建立连接
代码如下:
from selenium import webdriverdriver_path = 'E:\PycharmProjects\webUiTest\env\Scripts\chromedriver'
driver = webdriver.Chrome(executable_path=driver_path)
1、我们看看代码webdriver.Chrome(executable_path=driver_path)做了什么事情,按住ctrl键点击Chrome进入源码查看:
def __init__(self, executable_path="chromedriver", port=0,options=None, service_args=None,desired_capabilities=None, service_log_path=None,chrome_options=None, keep_alive=True):if chrome_options:warnings.warn('use options instead of chrome_options',DeprecationWarning, stacklevel=2)options = chrome_optionsif options is None:# desired_capabilities stays as passed inif desired_capabilities is None:desired_capabilities = self.create_options().to_capabilities()else:if desired_capabilities is None:desired_capabilities = options.to_capabilities()else:desired_capabilities.update(options.to_capabilities())self.service = Service(executable_path,port=port,service_args=service_args,log_path=service_log_path)self.service.start()try:RemoteWebDriver.__init__(self,command_executor=ChromeRemoteConnection(remote_server_addr=self.service.service_url,keep_alive=keep_alive),desired_capabilities=desired_capabilities)except Exception:self.quit()raiseself._is_remote = False
2、我们知道webdriver.Chrome()就是建立服务连接的过程,所以我们看到建立服务相关的代码就是:
我们在进入到self.service.start()源码看看它做了什么,源码如下:
def start(self):"""Starts the Service.:Exceptions:- WebDriverException : Raised either when it can't start the serviceor when it can't connect to the service"""try:cmd = [self.path]cmd.extend(self.command_line_args())self.process = subprocess.Popen(cmd, env=self.env,close_fds=platform.system() != 'Windows',stdout=self.log_file,stderr=self.log_file,stdin=PIPE)except TypeError:pass
3、原来是通过subprocess.Popen()函数根据我们传过来的chromedriver路径,开启一个子进程来执行打开chromedriver服务的命令。
4、但是别急,到这里只是把webdriver服务开启了,还没有初始化driver对象,继续回到源码,初始化driver对象肯定是在开启服务之后,也就是下面的源码:
5、我们继续进入看看它做了什么事情:
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',desired_capabilities=None, browser_profile=None, proxy=None,keep_alive=False, file_detector=None, options=None):"""Create a new driver that will issue commands using the wire protocol."""capabilities = {}if options is not None:capabilities = options.to_capabilities()if desired_capabilities is not None:if not isinstance(desired_capabilities, dict):raise WebDriverException("Desired Capabilities must be a dictionary")else:capabilities.update(desired_capabilities)if proxy is not None:warnings.warn("Please use FirefoxOptions to set proxy",DeprecationWarning, stacklevel=2)proxy.add_to_capabilities(capabilities)self.command_executor = command_executorif type(self.command_executor) is bytes or isinstance(self.command_executor, str):self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)self._is_remote = Trueself.session_id = Noneself.capabilities = {}self.error_handler = ErrorHandler()self.start_client()if browser_profile is not None:warnings.warn("Please use FirefoxOptions to set browser profile",DeprecationWarning, stacklevel=2)self.start_session(capabilities, browser_profile)self._switch_to = SwitchTo(self)self._mobile = Mobile(self)self.file_detector = file_detector or LocalFileDetector()
- 从注释可以看出这里主要是:创建一个新的WebDriver实例,它将使用WebDriver协议来发送命令给浏览器。
- 使用对应变量保存相关初始化需要的参数,然后开启一个会话(session)与webdriver建立通信,我们看看最重要的部分,也就是开启会话调用的函数:
def start_session(self, capabilities, browser_profile=None):"""Creates a new session with the desired capabilities."""if not isinstance(capabilities, dict):raise InvalidArgumentException("Capabilities must be a dictionary")if browser_profile:if "moz:firefoxOptions" in capabilities:capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encodedelse:capabilities.update({'firefox_profile': browser_profile.encoded})w3c_caps = _make_w3c_caps(capabilities)parameters = {"capabilities": w3c_caps,"desiredCapabilities": capabilities}response = self.execute(Command.NEW_SESSION, parameters)
可以看到start_session()函数里面发送请求是:self.execute()函数,我们继续进入看看:
def execute(self, driver_command, params=None):"""Sends a command to be executed by a command.CommandExecutor."""if self.session_id is not None:if not params:params = {'sessionId': self.session_id}elif 'sessionId' not in params:params['sessionId'] = self.session_idparams = self._wrap_value(params)response = self.command_executor.execute(driver_command, params)if response:print("打印响应参数", json.dumps(response, indent=4))self.error_handler.check_response(response)response['value'] = self._unwrap_value(response.get('value', None))return response# If the server doesn't send a response, assume the command was# a successreturn {'success': 0, 'value': None, 'sessionId': self.session_id}
通过源码的值它主要是通过CommandExecutor发送一个请求,这里我们把响应的结果打印到控制台看看,这里的响应返回了什么,新增一行输出代码如下:
我们在进入到self.command_executor.execute(driver_command, params)函数看看是怎么把请求发送出去的:
def execute(self, command, params):"""Send a command to the remote server.Any path subtitutions required for the URL mapped to the command should beincluded in the command parameters."""command_info = self._commands[command]assert command_info is not None, 'Unrecognised command %s' % commandpath = string.Template(command_info[1]).substitute(params)if hasattr(self, 'w3c') and self.w3c and isinstance(params, dict) and 'sessionId' in params:del params['sessionId']data = utils.dump_json(params)url = '%s%s' % (self._url, path)return self._request(command_info[0], url, body=data)
这里还是没有看到它到底是怎么把请求发送出去的,继续进入到self._request(command_info[0], url, body=data)函数:
def _request(self, method, url, body=None):"""Send an HTTP request to the remote server."""LOGGER.debug('%s %s %s' % (method, url, body))parsed_url = parse.urlparse(url)headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)resp = Noneif body and method != 'POST' and method != 'PUT':body = Noneprint(f"请求参数:url: {url} \n body: {body} \n headers: {json.dumps(headers, indent=4)}")if self.keep_alive:resp = self._conn.request(method, url, body=body, headers=headers)statuscode = resp.statuselse:http = urllib3.PoolManager(timeout=self._timeout)resp = http.request(method, url, body=body, headers=headers)
终于到这里看到了它底层是通过urllib3库来发送http请求的,这里我们把请求的参数打印出来:
我们再次运行下面的代码,看看请求参数和响应结果是什么:
from selenium import webdriverdriver_path = 'E:\PycharmProjects\webUiTest\env\Scripts\chromedriver'
driver = webdriver.Chrome(executable_path=driver_path)
输出结果如下:
请求参数:url: http://127.0.0.1:59146/session body: {"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "chrome", "platformName": "any", "goog:chromeOptions": {"extensions": [], "args": []}}}, "desiredCapabilities": {"browserName": "chrome", "version": "", "platform": "ANY", "goog:chromeOptions": {"extensions": [], "args": []}}} headers: {"Accept": "application/json","Content-Type": "application/json;charset=UTF-8","User-Agent": "selenium/3.141.0 (python windows)","Connection": "keep-alive"
}
打印响应参数 {"value": {"capabilities": {"acceptInsecureCerts": false,"browserName": "chrome","browserVersion": "127.0.6533.100","chrome": {"chromedriverVersion": "127.0.6533.119 (bdef6783a05f0b3f885591e7d2c7b2aec1a89dea-refs/branch-heads/6533@{#1999})","userDataDir": "C:\\Users\\\u5218\u519b\\AppData\\Local\\Temp\\scoped_dir13212_999079333"},"fedcm:accounts": true,"goog:chromeOptions": {"debuggerAddress": "localhost:59154"},"networkConnectionEnabled": false,"pageLoadStrategy": "normal","platformName": "windows","proxy": {},"setWindowRect": true,"strictFileInteractability": false,"timeouts": {"implicit": 0,"pageLoad": 300000,"script": 30000},"unhandledPromptBehavior": "dismiss and notify","webauthn:extension:credBlob": true,"webauthn:extension:largeBlob": true,"webauthn:extension:minPinLength": true,"webauthn:extension:prf": true,"webauthn:virtualAuthenticators": true},"sessionId": "34680a6d180d4c8f0a7225d00f92111f"}
}
终于我们可以看到初始化建立会话是通过请求url: http://127.0.0.1:59146/session 然后返回sessionId,后续操作都会携带该sessionId请求,这样对应webdriver它才知道来自那个请求,从而实现会话保持,至此终于把会话建立了,后续就可以通过这个会话发送操作了。
3.2、发送操作
前言:通过上面的终于把会话建立了,现在我们就需要通过会话发送操作命令了,我们执行下面的代码看看这个过程是怎么的:
driver.get("https://www.baidu.com")
运行结果如下:
driver.get():做的事就是把get操作转换为对应的url地址,然后通过携带sessionId发送请求到webdriver服务端,也就是说driver.xxx()的每一个操作都对应了一个url地址,这里肯定有个映射关系来维持,进入源码查看不难找到在RemoteConnection这个类中维护了这样的关系:
相关文章:

selenium底层原理详解
目录 1、selenium版本的演变 1.1、Selenium 1.x(Selenium RC时代) 1.2、Selenium 2.x(WebDriver整合时代) 1.3、Selenium 3.x 2、selenium原理说明 3、源码说明 3.1、启动webdriver服务建立连接 3.2、发送操作 1、seleni…...
【Solidity】继承
继承 Solidity 中使用 is 关键字实现继承: contract Father {function getNumber() public pure returns (uint) {return 10;}function getNumber2() public pure virtual returns (uint) {return 20;} }contract Son is Father {}现在 Son 就可以调用 Father 的 …...

docker 安装mino服务,启动报错: Fatal glibc error: CPU does not support x86-64-v2
背景 docker 安装mino服务,启动报错: Fatal glibc error: CPU does not support x86-64-v2 原因 Docker 镜像中的 glibc 版本要求 CPU 支持 x86-64-v2 指令集,而你的硬件不支持。 解决办法 降低minio对应的镜像版本 经过验证:qu…...

地图相册系统的设计与实现
摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&a…...
使用vh和rem实现元素响应式布局
示例代码 height: calc(100vh 30rem) vh(Viewport Height):vh是一个相对单位,代表浏览器窗口高度的百分比,例如20vh就是浏览器窗口高度的20%。 rem(root em):rem是通过html根元素…...
螺旋矩阵 II(LeetCode)
题目 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 解题 def generateMatrix(n):matrix [[0] * n for _ in range(n)]top, bottom 0, n - 1left, right 0, n - 1num 1while top <…...
如何快速掌握一款MCU
了解MCU特点 rom ,ramgpiotimerpower 明确哪些资源是项目开发需要的 认真理解相关资料模块 开始编程 编写特别的验证程序(项目不紧)按照自己的理解编写(老司机,时间紧张) 掌握MCU基本功能 定时器 固…...

XSS-DOM
文章目录 源码SVG标签Dom-Clobbringtostring 源码 <script>const data decodeURIComponent(location.hash.substr(1));;const root document.createElement(div);root.innerHTML data;// 这里模拟了XSS过滤的过程,方法是移除所有属性,sanitize…...

uniapp去掉页面导航条
在pages.json文件中,globalStyle中添加 ”app-plus“:{"titleNView":false }...

MySQL数据库专栏(三)数据库服务维护操作
1、界面维护,打开服务窗口找到MySQL服务,右键单击可对服务进行启动、停止、重启等操作。 选择属性,还可以设置启动类型为自动、手动、禁用。 2、指令维护 卸载服务:sc delete [服务名称] 例如:sc delete MySQL 启动服…...

【QT】基于UDP/TCP/串口 的Ymodom通讯协议客户端
【QT】基于UDP/TCP/串口的Ymodom通讯协议客户端 前言Ymodom实现QT实现开源库的二次开发-1开源库的二次开发-2 串口方式实现TCP方式实现UDP方式实现补充:文件读取补充:QT 封装成EXE 前言 Qt 运行环境 Desktop_Qt_5_11_2_MSVC2015_64bit ,基于…...

超详细!!!electron-vite-vue开发桌面应用之引入UI组件库element-plus(四)
云风网 云风笔记 云风知识库 一、安装element-plus以及图标库依赖 npm install element-plus --save npm install element-plus/icons-vue npm i -D unplugin-icons二、vite按需引入插件 npm install -D unplugin-vue-components unplugin-auto-importunplugin-vue-componen…...

【排序篇】实现快速排序的三种方法
🌈个人主页:Yui_ 🌈Linux专栏:Linux 🌈C语言笔记专栏:C语言笔记 🌈数据结构专栏:数据结构 文章目录 1 交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本1.2.2 挖坑法1.2.3 前后指针…...
Java 标识符(详解)
文章目录 一、简介二、命名规则三、命名规范 一、简介 在 Java 中,用于给变量、类、方法等命名的符号组合,我们称之为Java标识符,它就像是给这些编程元素贴上的独特标签,以便在程序中能够准确地引用和操作它们。 二、命名规则 标…...

2024年,有哪些优质的计算机书籍推荐?
在2024年,计算机领域的新书层出不穷,涵盖了从基础理论到前沿技术的多个方面。以下是今年出版的几本备受关注的计算机新书。 1. AI与机器学习类 1、深度学习详解 1.李宏毅老师亲笔推荐,杨小康、周明、叶杰平、邱锡鹏鼎力推荐! 2.数百万次播…...
Python基础知识点--总结
1. 注释 注释用于提高代码的可读性,在代码中添加说明文字,使代码更容易理解。 单行注释:使用 # 符号开头,注释内容在符号之后的行内。多行注释:使用三引号( 或 """)包裹注释内…...

高效记录与笔记整理的策略:工具选择、结构设计与复习方法
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
Request重复读的问题
换了新工作都有时间写文章,每天也是加班到很晚,也不是工作内容多,主要是还是效率低,要考虑多干的很心累。 一、关于request重复读的问题,从源码的角度来分析 为什么他不能重复读 跳转 再看源码前可能需要一些基础的…...
Linux学习第60天:Linux驱动开发的一些总结
今天是Linux驱动开发的最后一个章节,题目中标明是60天完成的,其实在实际学习及笔记的整理中不止是60天。中间有过断更,有时断更的时间还是挺长的。这是在整个Linux驱动开发学习中最不满意的地方。 题目为Linux学习,其实这个题目有…...
OPP || 继承和抽象类 || 访问控制
OPP面向对象程序设计 数据抽象:类的接口声明和定义实现分离继承:类构成的(树型)层次关系动态绑定:忽略相似类型区别,用统一的方式使用 基类派生类: 继承:类名 冒号 访问说明符 …...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
OCR MLLM Evaluation
为什么需要评测体系?——背景与矛盾 能干的事: 看清楚发票、身份证上的字(准确率>90%),速度飞快(眨眼间完成)。干不了的事: 碰到复杂表格(合并单元…...

Spring AOP代理对象生成原理
代理对象生成的关键类是【AnnotationAwareAspectJAutoProxyCreator】,这个类继承了【BeanPostProcessor】是一个后置处理器 在bean对象生命周期中初始化时执行【org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization】方法时…...

ABAP设计模式之---“Tell, Don’t Ask原则”
“Tell, Don’t Ask”是一种重要的面向对象编程设计原则,它强调的是对象之间如何有效地交流和协作。 1. 什么是 Tell, Don’t Ask 原则? 这个原则的核心思想是: “告诉一个对象该做什么,而不是询问一个对象的状态再对它作出决策。…...