100次浏览 发布时间:2024-09-12 09:01:34
在web端自动化测试中,selenium基本上是初学者的第一个使用工具,由于其开源,支持多种语言进行自动化的实现,因此使用率比较高。
本文分两部分:
第一部分,debug运行代码,结合源码对selenium的原理和运行过程进行简单的解析。
第二部分,原理过程的代码实现,使用python 调用API方式实现第一部分测试操作,增加初学者对工具的理解,也对工具的使用更能有心得。
一、什么是Selenium WebDriver
Selenium 是支持 web 浏览器自动化的一系列工具和库的集合,其核心是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)
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链接,已确认在该端口上启动的服务可用,确认后再次断开连接。
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')执行在浏览器上打开百度首页
Command.GET定义在remote_connection.py, 对应API地址为
三、过程的代码实现
代码在最下方,执行过程与上方的跟踪过程相同。
为了不用来回跳着看,以上步骤全直接写在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)