跨平台设备管理方案seleniumgrid
跨平台设备管理方案 Selenium Grid
简介
Selenium Grid 是 Selenium 的三大组件之一,它可以在多台机器上并行运行测试,集中管理不同的浏览器版本和浏览器配置。通过将客户端命令发送到远程浏览器的实例, Selenium Grid 允许在远程计算机 (虚拟或真实) 上执行 WebDriver 脚本. 它旨在提供一种在多台计算机上并行运行测试的简便方法。
官方文档:https://www.selenium.dev/
Grid4 新特性
Selenium Grid4 在更新之后增加了 3 个特性:
- Hub 和 Node 使用同一个 jar 服务。
- 架构优化,在 Selenium Grid 4 版本的全新架构中划分成了组件:Router、Distributor、Node、Session Map、Session Queue、Event Bus。
- 支持不同的运行模式,Selenium 4 支持三种网格类型,包括 Standalone Mode 独立模式、Classical Grid 经典网格模式、Fully Distributed 完全分布式
下面这张图是官方提供的 Selenium Grid4 的架构图:
从图中可以看到 Selenium Grid 包括六大组件,分别是:
- Router 路由器
- Distributor 分发服务器
- Session Map 会话映射
- Node 测试结点
- New Session Queue 新的会话队列
- Event Bus 事件总线
SeleniumGrid 实现原理
SeleniumGrid 的实现原理为通过 SeleniumGridHub 作为一个中控中心。将客户端的请求分发到各个 Node 执行对应的脚本。只要保证 Node 具备相应的环境即可。如此一来,可以实现:
- 无需在本地安装 Appium Server 环境也可以进行 App 自动化测试。
- 无需在本地安装多个浏览器环境也可以进行 Web 的兼容性测试。
应用场景
- 实现分布式执行测试:提高执行效率。同样数量的测试用例,分布式运行的运行效率是单机运行的数倍。
- 浏览器/手机设备兼容性问题:在测试过程中,同样的测试用例需要在不同的设备/浏览器上面运行,以保证软件能够在不同的设备或浏览器中正常运行。
如何使用
Selenium Grid 针对于不同的应用场景有多种运行方式。
- 单机运行:独立模式。
- 单机运行:经典网格模式。
- 多系统运行:分发模式。
接下来将以最常用的网格模式进行演示,SeleniuimGrid 的使用也并不复杂,在使用过程中,主要分为以下 2 个步骤:
- 安装与部署,将 Hub 与 Node 部署好,确定可以被正常调用。
- 代码调用,直接通过调用 SeleniuimGrid 服务实现自动化测试。
安装与部署
由以上的架构图也可以看出,SeleniumGrid 的安装与部署主要分为 Hub 与 Node:
- Hub: 中控中心,只需要部署一个。
- Node: 子节点,可以部署多个。子节点可以为浏览器,也可以为 AppiumServer。
关于安装与部署的方式,常用的主要就是两种: Jar 包以及 Docker 部署。以下为官方文档的部署资料:
-
Selenium 官方文档地址(Jar 包部署):https://www.selenium.dev/documentation/grid/getting_started/
-
SeleniumDocker 官方文档地址(Docker 包部署):https://github.com/SeleniumHQ/docker-selenium?tab=readme-ov-file#hub-and-nodes
Jar 包安装与部署(推荐)
-
安装 Hub:
java -jar selenium-server-<version>.jar hub
。安装成功之后,可以访问http://127.0.0.1:4444/
,即可进入 Grid 页面。 -
添加 Node,如果需要添加端口则使用
--port
:java -jar selenium-server-<version>.jar node --port 5555
- 添加成功之后,即可在 hub 页面看到对应的设备。
Docker 安装与部署
注意:如果不了解 Docker 的同学,建议使用 Jar 安装。排错较为麻烦。
- 安装 Hub。
# 创建 grid 网络
docker network create grid
# --net 将Hub容器连接到grid网络
docker run -d -p 4442-4444:4442-4444 --net grid --name selenium-hub selenium/hub:4.16.1-20231219
- 添加浏览器 Node。
# 创建
docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub \
--shm-size="2g" \
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
selenium/node-chrome
# 创建 firefox 浏览器节点
docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub \
--shm-size="2g" \
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
selenium/node-firefox
添加 Appium Node
AppiumServer 也可以作为一个 Node 注册到 Hub 中,以此实现移动端的多设备自动化测试。
- 准备 Appium 配置文件(如下所示), 并通过命令启动 appium 服务:
appium --config appium1.yml
。
server:
# Appium 服务启动端口
port: 4724
# 使用的Driver
use-drivers:
- uiautomator2
- 创建节点配置文件。
# 指定节点端口
[server]
port = 5580
[node]
detect-drivers = false
[relay]
# 不能配置本地网络,比如 localhost 或者 127.0.0.1 因为网络设置为grid 没有和宿主机共享网络,所以访问不到
url = "http://172.16.4.187:4723"
status-endpoint = "/status"
# 配置信息,会展示在hub界面
configs = [
"2", "{\"platformName\": \"Android\", \"appium:udid\": \"emulator-5556\", \"appium:automationName\": \"uiautomator2\"}"
]
- 启动服务,添加节点:
# 使用jar包的方式添加节点
java -jar /path/to/selenium.jar node --config node1.toml
# 使用Docker包的方式添加节点
docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub \
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
-v 配置文件地址/node1.toml:/opt/bin/config.toml \
-v /var/run/docker.sock:/var/run/docker.sock \
selenium/node-docker
SeleniumGrid 实战案例
通过 Grid 实现调用不同的 App 设备
在代码中,通过添加配置信息,以及在初始化 Driver 的时候输入 Hub 的地址,即可实现通过 Grid 调用不同的设备。
- Python 版本:
class TestGrid:
@pytest.mark.parametrize(
"udid",
["emulator-5554", "emulator-5556"]
)
def test_grid(self, udid):
"""
grid 调用 appium 实现自动化测试
:return:
"""
caps = {}
# 设置 app 安装平台
caps["appium:platformName"] = "Android"
# 设置driver
caps["appium:automationName"] = "UiAutomator2"
# 设置 app 的包名
caps["appium:appPackage"] = "com.android.settings"
# 设置 app 启动页
caps["appium:appActivity"] = ".Settings"
# 设置 udid 可以指定hub分配到指定设备
caps["appium:udid"] = udid
# 不清空缓存
caps["appium:noReset"] = True
# 设置以下两个参数来控制启动app和关闭掉app
caps["appium:forceAppLaunch"] = True
caps["appium:shouldTerminateApp"] = True
options = AppiumOptions().load_capabilities(caps)
driver = webdriver.Remote(
"http://localhost:4444",
options=options
)
time.sleep(5)
driver.quit()
- Java 版本:
public class TestGrid {
@Test
public void grid() throws InterruptedException {
UiAutomator2Options uiAutomator2Options = new UiAutomator2Options()
.setPlatformName("Android")
.setAutomationName("uiautomator2")
// 使用不同的udid连接不同的设备
.setUdid("emulator-5556")
.setNoReset(true)
.amend("appium:appPackage", "com.android.settings")
.amend("appium:appActivity", ".Settings")
.amend("appium:forceAppLaunch", true)
.amend("appium:shouldTerminateApp", true);
try {
// 连接Selenium Hub
// 连接Selenium Hub
RemoteWebDriver driver = new RemoteWebDriver(new URL("http://127.0.0.1:4444"), uiAutomator2Options);
driver.findElement(AppiumBy.xpath("//*[@text='Battery']")).click();
Thread.sleep(2000);
// 必须要进行quit操作,否则grid 运行的时候会占用session
driver.quit();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
多浏览器运行代码
除了可以调用不同的移动端设备外,还可以通过此方式实现多浏览器的 web 端兼容性测试。
- Python 版本:
创建测试文件 test_multi_node.py
示例代码如下:
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestMultiNode:
def setup_method(self):
options = webdriver.ChromeOptions()
self.driver = webdriver.Remote(
command_executor='http://10.1.1.104:4444',
options=options
)
def test_multinode1(self):
# 打开 baidu 页
self.driver.get("http://www.baidu.com")
# 向输入框中输入
self.driver.find_element(By.ID, 'kw').send_keys("selenium")
# 点击搜索框
self.driver.find_element(By.ID, 'su').click()
# 等待一秒
sleep(1)
# 断言输入内容在页面中
assert "selenium" in self.driver.page_source
def test_multinode2(self):
# 打开 baidu 页
self.driver.get("http://www.baidu.com")
# 向输入框中输入
self.driver.find_element(By.ID, 'kw').send_keys("appium")
# 点击搜索框
self.driver.find_element(By.ID, 'su').click()
# 等待一秒
sleep(1)
# 断言输入内容在页面中
assert "appium" in self.driver.page_source
def test_multinode3(self):
# 打开 baidu 页
self.driver.get("http://www.baidu.com")
# 向输入框中输入
self.driver.find_element(By.ID, 'kw').send_keys("pytest")
# 点击搜索框
self.driver.find_element(By.ID, 'su').click()
# 等待一秒
sleep(1)
# 断言输入内容在页面中
assert "pytest" in self.driver.page_source
def test_multinode4(self):
# 打开 baidu 页
self.driver.get("http://www.baidu.com")
# 向输入框中输入
self.driver.find_element(By.ID, 'kw').send_keys("requests")
# 点击搜索框
self.driver.find_element(By.ID, 'su').click()
# 等待一秒
sleep(1)
# 断言输入内容在页面中
assert "requests" in self.driver.page_source
def test_multinode5(self):
# 打开 baidu 页
self.driver.get("http://www.baidu.com")
# 向输入框中输入
self.driver.find_element(By.ID, 'kw').send_keys("java")
# 点击搜索框
self.driver.find_element(By.ID, 'su').click()
# 等待一秒
sleep(1)
# 断言输入内容在页面中
assert "java" in self.driver.page_source
def teardown_method(self):
self.driver.quit()
为了模拟多浏览器并发运行,使用 pytest 的插件 pytest-xdist
实现分布式并发执行方式,提前安装 pytest-xdist
插件,然后使用命令执行用例。打开命令提示行或者终端,使用cd
命令进入到文件所在路径,然后执行 pytest test_multi_node.py -n 3 --alluredir ./results
命令。执行完用例之后,会把测试报告结果统一汇总到results
目录中。
- Java 版本:
/*
* @Author: 霍格沃兹测试开发学社-盖盖
* @Desc: '更多测试开发技术探讨,请访问:https://ceshiren.com/t/topic/15860'
*/
package com.ceshiren.nodes;
public class EdgeTest{
private WebDriver webDriver;
static final Logger logger = getLogger(lookup().lookupClass());
static DesiredCapabilities caps = new DesiredCapabilities();
@Test
public void edge(TestInfo testInfo) throws MalformedURLException, InterruptedException {
WebDriver webDriver = initDriver(testInfo, Platform.ANY);
webDriver.get("https://ceshiren.com/");
String title = webDriver.getTitle();
logger.info("MicrosoftEdge浏览器打开 {}", title);
sleep(4000);
webDriver.quit();
assertThat(title,containsString("测试人社区"));
}
public WebDriver initDriver(TestInfo testInfo, Platform platform) throws MalformedURLException {
String browser = testInfo.getTestMethod().map(Method::getName).get();
logger.info("浏览器为:{}",browser);
// caps.setPlatform(platform);
caps.setCapability(CapabilityType.PLATFORM_NAME,platform);
if("chrome".equals(browser)){
caps.setBrowserName("chrome");
// caps.setCapability(CapabilityType.BROWSER_NAME,"chrome");
ChromeOptions options = new ChromeOptions();
options.merge(caps);
}else if ("firefox".equals(browser)){
caps.setBrowserName("firefox");
FirefoxOptions options = new FirefoxOptions();
options.merge(caps);
}else if ("edge".equals(browser)){
caps.setBrowserName("MicrosoftEdge");
EdgeOptions options = new EdgeOptions();
options.merge(caps);
}else if ("safari".equals(browser)){
caps.setBrowserName("safari");
SafariOptions options = new SafariOptions();
options.merge(caps);
}
webDriver = new RemoteWebDriver(new URL("http://127.0.0.1:4444"),caps);
webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
return webDriver;
}
}
- suite 套件运行代码:
@Suite
@SelectPackages("com.ceshiren.nodes")
public class SuiteTest {
}
总结
- SeleniumGrid 安装与部署
- SeleniumGrid 调用不同 App 设备
- SeleniumGrid 调用多浏览器