selenium底层原理详解

news2024/12/27 13:46:03

目录

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 webdriver

driver_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_options

        if options is None:
            # desired_capabilities stays as passed in
            if 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()
            raise
        self._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 service
           or 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_executor
        if 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 = True
        self.session_id = None
        self.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.encoded
            else:
                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_id

        params = 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 success
        return {'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 be
        included in the command parameters.
        """
        command_info = self._commands[command]
        assert command_info is not None, 'Unrecognised command %s' % command
        path = 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 = None
        if body and method != 'POST' and method != 'PUT':
            body = None
        print(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.status
        else:
            http = urllib3.PoolManager(timeout=self._timeout)
            resp = http.request(method, url, body=body, headers=headers)

终于到这里看到了它底层是通过urllib3库来发送http请求的,这里我们把请求的参数打印出来:

我们再次运行下面的代码,看看请求参数和响应结果是什么:

from selenium import webdriver

driver_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这个类中维护了这样的关系:

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

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

相关文章

flink车联网项目:维表离线同步(第69天)

系列文章目录 3.3 维表离线同步 3.3.1 思路 3.3.2 示例 3.3.3 其他表开发 3.3.4 部署 3.3.1.1 将表提交到生成环境 3.3.1.2 添加虚拟节点 3.3.1.3 配置计算节点 3.3.1.4 添加虚拟结束节点 3.3.1.5 提交到生产环境 3.3.1.6 发布 3.3.1.7 运维中心 3.3.1.8 补数据 3.3.1.9 补数据…

c++进阶------多态

作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 ​🎂 作者介绍: 🎂🎂 🎂 🎉🎉&#x1f389…

机器学习/数据分析--通俗语言带你入门线性回归(结合案例)

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 前言 机器学习是深度学习和数据分析的基础,接下来将更新常见的机器学习算法注意:在打数学建模比赛中,机器学习用的也很多&a…

探索GitLab:从搭建到高效使用的实用指南

企业里为什么喜欢使用GitLab 一、GitLab简介二、搭建GitLab三、GitLab的权限管理3.1、用户注册3.2、创建用户组3.3、为用户组添加用户3.4、为工程添加访问权限 四、GitLab的code review五、团队知识管理六、总结 一、GitLab简介 GitLab是利用 Ruby on Rails 一个开源的版本管理…

Adobe Media Encoder ME 2023-23.6.6.2 解锁版下载安装教程 (专业的视频和音频编码渲染工具)

前言 Adobe Media Encoder(简称Me)是一款专业的音视频格式转码软件,文件格式转换软件。主要用来对音频和视频文件进行编码转换,支持格式非常多,使用系统预设设置,能更好的导出与相关设备兼容的文件。 一、…

网站怎么做敏感词过滤,敏感词过滤的思路和实践

敏感词过滤是一种在网站、应用程序或平台中实现内容审查的技术,用于阻止用户发布包含不适当、非法或不符合政策的内容。我们在实际的网站运营过程中,往往需要担心某些用户发布的内容中包含敏感词汇,这些词汇往往会导致我们的网站被用户举报&a…

JVM的组成

JVM 运行在操作系统之上 java二进制字节码文件的运行环境 JVM的组成部分 java代码在编写完成后编译成字节码文件通过类加载器 来到运行数据区,主要作用是加载字节码到内存 包含 方法区/元空间 堆 程序计数器,虚拟机栈,本地方法栈等等 随后来到执行引擎,主要作用是翻译字…

系统工程与信息系统(上)

系统工程 概念 【系统工程】是一种组织管理技术。 【系统工程】是为了最好的实现系统的目的,对系统的组成要素、组织结构、信息流、控制机构进行分析研究的科学方法。 【系统工程】从整体出发、从系统观念出发,以求【整体最优】 【系统工程】利用计算机…

信息搜集--敏感文件Banner

免责声明:本文仅做分享参考... git安装: Windows10下安装Git_win10安装git好慢-CSDN博客 git目录结构: Git 仓库目录 .git 详解-CSDN博客 敏感目录泄露 1-git泄露 Git是一个开源的分布式版本控制系统,我们简单的理解为Git 是一个*内容寻址文件系统*,也就是说Gi…

二十四、解释器模式

文章目录 1 基本介绍2 案例2.1 Instruction 接口2.2 StartInstruction 类2.3 PrimitiveInstruction 类2.4 RepeatInstruction 类2.5 InstructionList 类2.6 Context 类2.7 Client 类2.8 Client 类的运行结果2.9 总结 3 各角色之间的关系3.1 角色3.1.1 AbstractExpression ( 抽象…

Nexpose漏扫

免责声明:本文仅做分享参考... nexpose官网: Nexpose On-Premise Vulnerability Scanner - Rapid7 Rapid7的Nexpose是一款非常专业的漏洞扫描软件。有community版本和enterprise版本。 其中community版是免费的,但是功能简单;enterprise版本功能强大.…

适用于 Windows 10 的最佳免费数据恢复软件是什么?

有没有适用于 Windows 10 的真正免费的数据恢复软件? 丢失重要数据,无论是由于硬件问题、软件问题、意外删除、格式化还是病毒和恶意软件,确实很麻烦。当你面临数据丢失时,你可能真心希望找到一款免费的数据恢复软件,…

【C++指南】深入剖析:C++中的引用

💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 引言: 一、引用的基本概念 1. 定义与特性 2. 语法与声明 二、引用的进阶用法 1. 函…

Python高手参考手册:迭代器协议详解

在Python中,迭代器协议提供了一种灵活的方式来遍历容器中的元素。本文将详细介绍迭代器协议的基础知识、其实现机制以及一些实用的应用案例。 迭代器协议概述 1.1 迭代器与迭代协议 迭代器协议定义了如何遍历容器中的元素。在Python中,任何实现了迭代…

Android:使用Gson常见问题(包含解决将Long型转化为科学计数法的问题)

一、解决将Long型转化为科学计数法的问题 1.1 场景 将一个对象转为Map类型时,调用Gson.fromJson发现,原来对象中的long类型的personId字段,被解析成了科学计数法,导致请求接口失败,报参数错误。 解决结果图 1.2、Exa…

轻松掌握域名系统(DNS):基础小白的入门指南

文章目录 域名系统概述DNS 的发展和结构DNS 的服务和功能互联网的域名结构域名服务器的类型和功能域名解析的过程DNS资源记录小结 域名系统概述 域名系统(Domain Name System,DNS) 是一种核心服务,它使得网络应用能够在应用层使用…

腾讯地图SDK Android版开发 6 显示覆盖物

腾讯地图SDK Android版开发 6 显示覆盖物 前言地图类中覆盖物的接口覆盖物类Marker示例Polyline示例Polygon示例Arc示例Circle示例移除示例效果图 Marker的更多属性常用属性交互碰撞动画其它属性 折线的更多属性常用属性交互其它属性 多边形的更多属性常用属性交互其它属性 Arc…

【手抖拜拜!特发性震颤患者的专属锻炼秘籍,轻松改善生活品质】

Hey小伙伴们~👋 今天咱们来聊聊一个可能不那么常被提及,但却实实在在影响着很多人生活质量的话题——特发性震颤。如果你或你身边的人正为此困扰,别怕,这篇笔记就是你们的“稳手宝典”📚! 🌈 了…

【论文复现】 | 改善抑郁估计从面部对齐,训练优化和调度

文章目录 1、Introduction2、Related work3、 Proposed methodology3.1. Preprocessing and face alignment3.2 Architecture3.3 Data Augmentation3.4 Training 4、Experimental analysis and results4.1 Datasets4.2 Experimental setup4.3 Protocol and Performance metrice…

JS面试题3

1、使用递归完成1~100的累加 // 1~100累加function addRes(x,y){if(x y){return x}else{return y addRes(x,y-1) // 第一次:3 addRes(1,2) > 往后依次递减,到达目标值位置后依次计算累加值}}console.log(addRes(1,3))// 斐波那契数列(兔子数列) &…