手机版

selenium工作原理(selenium原理及运行过程解析)

100次浏览     发布时间:2024-09-12 09:01:34     编辑: hero要无畏

在web端自动化测试中,selenium基本上是初学者的第一个使用工具,由于其开源,支持多种语言进行自动化的实现,因此使用率比较高。

本文分两部分:

第一部分,debug运行代码,结合源码对selenium的原理和运行过程进行简单的解析。

第二部分,原理过程的代码实现,使用python 调用API方式实现第一部分测试操作,增加初学者对工具的理解,也对工具的使用更能有心得。

一、什么是Selenium WebDriver

Selenium 是支持 web 浏览器自动化的一系列工具和库的集合,其核心是WebDriver

  1. 是一种用于Web应用的自动化测试工具,一种代码实现
  2. 是一套简洁、友好的面向对象的接口(webdriver类库封装很多方法供调用)

它的工作原理简单概括为:WebDriver通过http协议与浏览器driver程序(如chromedriver)建立通信,向chromedriver传递相关指令,chromedriver接受指令并控制浏览器启动、运行、测试和退出,这就像真正的用户正在操作浏览器一样。如下图所示:

测试框架端:主要负责测试工具、测试数据、以及测试案例的组织和运行,其中WebDriver结合测试代码在框架的调度下与Browser Driver进行通信;并根据Driver返回结果进行assert断言操作是成功或失败,标记案例状态。

selenium Server or grid: 是在远程执行测试脚本用于进行通信的节点,如本地执行测试可以忽略。

Browser Driver: 用来接受web driver端发来的请求指令,用于启动和关闭http server 及指令转换传递给浏览器;同时将Brower端返回的操作结果给到webdriver(测试框架端)

Browser:接受driver端指令并根据指令进行相关浏览器操作,并将操作结果返给Driver。

二、WebDriver背后隐藏的操作

测试脚本中几行代码便可以启动浏览器并指挥浏览器进行相关动作,那么在webdriver层都进行了怎样的动作呢

#!/usr/bin env python
# -*- coding:utf-8 -*-

from selenium import webdriver

if __name__ == '__main__':
    driver = webdriver.Chrome()
    driver.get("https://www.baidu.com")

以上8行代码便可以打开浏览器,然后打开百度首页。在第7行打断点,通过debug进行一步步跟踪去了解下。

2.1启动服务和建立链接

2.1.1 首先跳转到chrome\webdriver.py

实例的创建:79行船初始创建Service实例,81行调用父类__init__函数生成chrome driver实例。

同时进行了相关变量的判断赋值如executable_path(chromedriver路径, 测试代码中我们没有赋值,程序给了默认值'chromedriver'),chrome_options(浏览器选项)等。

2.1.2 Service实例创建(chrome\service.py)

第一步的79行创建Service实例,跟踪跳转到chrome\service.py,最终通过调用基类common\service.py的__init__()方法初始并生成了Service实例对象,包括service.url 及 port。如下图:

2.1.3 Service启动、driver实例创建(chromium\webdriver.py)

  • Service启动

a. 首先进行了浏览器options,desired_capbilities中的创建和参数赋值

b. 其次在103行进行了启动服务操作,service.start()

继续跟踪,是在common\service.py调用start()方法,如下图:

由于我们当初测试脚本没有给出chromedriver路径,因此源码中调用seleniumManager进行了自动匹配并下载了对应chrome浏览器的chromedriver。

然后通过self._start_process()方法启动service,如图:

由上图看出_start_process()方法通过subprocess.Popen()执行了['chromedriver','--port=53233']命令,即在53233端口启动chromedriver,生成一项服务,53323端口是在创建Service实例时生成的,此后webdriver通过localhost:53323与chromedriver生成的服务进行通信。

并且代码还与启动的服务建立socket链接,已确认在该端口上启动的服务可用,确认后再次断开连接。

  • driver实例创建

a. 依然是在该文件中,106行的调用父类的__init__()方法,如下图:

b. 跳转到remote\webdriver.py 发起建立session会话,如图:

# 在remote_connection.py查看执行的操作如下, 即一个url为/session的post请求
Command.NEW_SESSION: ("POST", "/session"),

c. 跳转到remote_connection.py发送相关请求,建立session会话

成功建立session后,便启动浏览器窗口,同时并返回响应数据,其中包括后续步骤用到的一项sessionId,后续的API请求操作都要带上sessionId确定是那个driver实例,去操作那个浏览器窗口。如下图:

2.2 调用API发送指令

driver.get('https://www.baidu.com')执行在浏览器上打开百度首页

  1. 首先调用remote\webdriver.py的get()方法

Command.GET定义在remote_connection.py, 对应API地址为

  1. 执行execute()方法, 在请求参数params上加上sessionId(driver实例启动浏览器时返回的sessionId)

  1. 进入remote_connection.py 执行execute()方法, 并且生成请求的具体url ,然后传递url,请求方法,body给到_request方法去发送请求,最终通过urlopen发送请求,至此浏览器展示百度首页

  1. 后续其它定位,点击等操作只需发送相关API请求即可,不同的是请求所带参数的不同。相关API在remote_connection.py查看。
  2. driver.quit(),执行DELETE请求,删除session,如图:

三、过程的代码实现

代码在最下方,执行过程与上方的跟踪过程相同。

  1. 创建Service实例对象
  2. 启动Service,executable_path:本地chromedriver路径
  3. 设置浏览器选项Options,在get_capbilities方法中设置
  4. webdriver与第2步启动的服务建立链接(即创建driver实例,并打开浏览器)
  5. 获取上一步sessionId,通过requests库发送post请求,打开百度首页
  6. requests库post请求定位搜索框,响应获取搜索框id
  7. 根据搜索框id,发送post请求,输入搜索的文本“selenium”
  8. 同第6步,定位 “百度一下” 按钮,响应获取按钮id
  9. 根据案例id,发送post请求,点击“百度一下”按钮
  10. 发送method=delete的请求,退出会话连接

为了不用来回跳着看,以上步骤全直接写在main中,没有再写方法。同时也在postman中调试运行成功,不过需手动启动chromedirver,并在postman全局变量{{remoteHost}}修改端口,postman的collection.json文件会放入github,有兴趣的话去获取下载,或者关注微信公众号:ATester私信获取。

#!/usr/bin env python
# -*- coding:utf-8 -*-
import errno
import os
import logging
import subprocess
import requests
from selenium.common import WebDriverException
from selenium.webdriver.chrome.service import Service


logger = logging.getLogger(__name__)


# 获取chromedriver路径
def get_exe_path():
    pro_path = os.path.dirname(os.path.dirname(__file__))
    exe_path = pro_path + '/common/chromedriver.exe'
    return exe_path


def start_service(path, port):
    str_port = '--port=' + str(port)
    ls = [str_port]
    cmd = [path]
    cmd.extend(ls)
    try:
        process = subprocess.Popen(
            cmd,
            env=os.environ,
            # close_fds=system() != "Windows",
            # stdout=log_file,
            # stderr=log_file,
            # stdin=PIPE,
            # creationflags=creation_flags,
        )
        logger.debug(f"Started executable: `{path}` in a child process with pid: {process.pid}")
    except TypeError:
        raise
    except OSError as err:
        if err.errno == errno.ENOENT:
            raise WebDriverException(
                f"'{os.path.basename(path)}' executable needs to be in PATH."
            )
        elif err.errno == errno.EACCES:
            raise WebDriverException(
                f"'{os.path.basename(path)}' executable may have wrong permissions."
            )
        else:
            raise
    except Exception as e:
        raise WebDriverException(
            f"The executable {os.path.basename(path)} needs to be available in the path.\n{str(e)}"
        )


def get_capabilities():
    json_data = {
        "capabilities": {
            "firstMatch": [{}],
            "alwaysMatch": {
                "browserName": "chrome",
                "pageLoadStrategy": "normal",
                "goog:chromeOptions": {
                    "extensions": [],
                    "args": ["--windows-size=1920,1080", "--no-sandbox", "--start-maximized"]
                }
            }
        }
    }
    return json_data


if __name__ == '__main__':
    # driver = webdriver.Chrome()
    executable_path = get_exe_path()


    # 创建Service实例对象
    service = Service(executable_path=executable_path)
    # 启动service
    start_service(executable_path, service.port)


    """
        # 设置浏览器选项,更多浏览器选项请自行添加
        # 如果设置浏览器选项,请使用105行创建driver实例,并启动浏览器;
        # 如果使用requests.post去建立链接去开启浏览器,请在get_capabilites()中设置设置浏览器选项
        # opt = webdriver.ChromeOptions()
        # opt.add_argument('--window-size=1920,1080')
        # opt.add_argument('--no-sandbox')
        # opt.add_argument('--log-level=3')
        # opt.add_argument("-lang=zh-cn")
        # 无头模式运行
        # opt.add_argument('--headless')
        # 无痕模式时使用
        # opt.add_argument("–incognito")
    """
    data = get_capabilities()


    # webdriver与service建立链接(打开google浏览器)
    service_url = service.service_url
    create_session_url = f"{service_url}/session"
    # driver = webdriver.Remote(service.service_url, options=opt)
    res = requests.post(create_session_url, json=data)
    # 获取此次链接的sessionId
    sessionId = res.json()["value"]["sessionId"]


    # 打开百度首页
    open_site_url = f"{service_url}/session/{sessionId}/url"
    data = {
        "url": "https://www.baidu.com"
    }
    res = requests.post(url=open_site_url, json=data)


    # 定位百度搜索框
    find_ele_url = f"{service_url}/session/{sessionId}/element"
    data = {
        "using": "css selector",
        "value": "#kw"
    }
    res = requests.post(url=find_ele_url, json=data)
    # 获取元素id
    ele_id = res.json()["value"]["element-6066-11e4-a52e-4f735466cecf"]
    print("ele_id:", ele_id)


    # 搜索框输入字符串“selenium”
    ele_send_key_url = f"{service_url}/session/{sessionId}/element/{ele_id}/value"
    data = {
        "text": "selenium"
    }
    res = requests.post(url=ele_send_key_url, json=data)


    # 定位 百度一下 button
    data = {
        "using": "css selector",
        "value": "#su"
    }
    res = requests.post(url=find_ele_url, json=data)
    # 获取元素id
    ele_id = res.json()["value"]["element-6066-11e4-a52e-4f735466cecf"]


    # 点击 百度一下 button
    ele_click_url = f"{service_url}/session/{sessionId}/element/{ele_id}/click"
    data = {
        "button": 0
    }
    res = requests.post(url=ele_click_url, json=data)
    
    # 退出,断开会话链接
    quit_url = f"{service_url}/session/{sessionId}"
    res = requests.delete(url=quit_url)