1. 项目概述为什么我们需要一个Appium Device Farm如果你是一名移动端自动化测试工程师或者正在向这个方向发展那么“测试设备管理”这个问题迟早会成为你效率提升路上的拦路虎。想象一下这个场景你写了一套非常棒的Appium自动化脚本在你自己那台用了两年的安卓手机上跑得飞快。然后产品经理跑过来说“我们得覆盖一下iOS 15和16还有安卓10到13的几个主流机型对了还有那个新出的折叠屏手机也得测一下兼容性。”你看着手边仅有的两三台设备瞬间头大。这就是Appium Device Farm要解决的问题。它不是一个单一的工具而是一个设备集群管理方案。简单来说它允许你将多台物理或虚拟的移动设备Android/iOS接入到一个中心化的服务中然后你的Appium测试脚本可以像调用远程API一样按需申请、使用并释放这些设备实现真正的并行、多设备、多版本的自动化测试。对于追求测试效率、覆盖度和持续集成CI的团队来说这是从“手工作坊”迈向“自动化工厂”的关键一步。网上有很多零散的教程教你怎么装Appium、配环境但关于如何把这些分散的设备组织成一个高效、稳定、可管理的“农场”Farm系统性的中文指南并不多见。今天我就结合自己搭建和维护多套Device Farm的经验从零开始带你走通整个安装、配置和核心调优的流程。无论你是想在本机搭建一个小型测试集群还是在服务器上部署一个供团队使用的设备池这篇文章都能给你提供可直接复现的详细步骤和避坑指南。2. 核心架构与方案选型在动手之前我们必须搞清楚我们要搭建的到底是什么以及有哪些主流方案可选。这决定了后续所有技术栈和配置的走向。2.1 Device Farm的核心组件一个完整的Appium Device Farm通常包含以下几个核心部分设备节点即实际的移动设备可以是USB连接的真机、局域网Wi-Fi连接的设备、或者Android模拟器/iOS模拟器。每个设备都需要运行一个appium-server实例作为其“代理”。Appium服务器运行在设备节点上的服务负责接收来自客户端的WebDriver协议请求并将其转化为对设备通过UIAutomator2/XCUITest的操作。设备管理中枢这是Farm的大脑。它负责设备注册与发现设备节点启动后向中枢注册自己的信息如UDID、系统版本、状态等。任务调度接收测试任务的请求根据策略如轮询、基于标签将任务分配给空闲且符合条件的设备。状态监控实时监控设备状态空闲、使用中、离线、异常。提供API对外提供一套RESTful API供测试脚本或CI系统调用来申请设备、执行测试。客户端/测试脚本你的自动化测试代码。它不再直接连接某个具体设备的IP和端口而是调用设备管理中枢的API由中枢分配设备并返回连接信息。2.2 主流方案对比与选型目前社区主要有以下几种实现方式我将它们分为“集成式”和“自建式”方案一使用开源集成方案 - Appium Device Farm (formerly OpenSTF) 的衍生思路STFSmartphone Test Farm曾经是业界标杆但其原作者已停止维护。不过其思想被许多项目继承。例如你可以使用appium-device-farm这个Node.js包或者基于Docker Selenium Grid 4 模式进行改造。这类方案集成度较高但可能需要一定的定制化开发。优点架构清晰社区有相关实践可参考。缺点需要自己整合Appium配置和调试过程相对复杂对运维能力要求高。方案二基于Selenium Grid 4进行扩展Selenium Grid 4 引入了对Appium移动端的原生支持。你可以将每个“设备节点”配置为Grid中的一个Node而Grid的Hub就自然成为了设备管理中枢。这是目前最推荐、最标准的自建方案。优点标准协议完全遵循W3C WebDriver协议和Selenium Grid架构生态兼容性好。功能强大自带负载均衡、会话管理、可视化UI、丰富的路由策略。社区活跃Selenium社区庞大问题容易找到解决方案。与CI/CD无缝集成Jenkins、GitLab CI等工具都有成熟的Grid插件。缺点需要理解Grid的架构初始配置有一定学习成本。方案三云服务商方案如AWS Device Farm, BrowserStack直接使用商业云服务。这不在我们今天“安装与配置”的讨论范围内但它是解决设备问题最快捷的方式适合预算充足、不想投入运维的团队。我们的选择 为了最具普适性和学习价值本文将重点详解方案二基于Selenium Grid 4搭建Appium Device Farm。这是目前平衡了可控性、功能性和社区支持的最佳实践。我们将搭建一个由1个Hub和多个Node每个Node对应一台设备或一个模拟器组成的测试集群。3. 基础环境准备与全局配置工欲善其事必先利其器。在开始搭建Grid和Appium之前我们需要一个干净、一致的基础环境。我强烈建议使用一台独立的Linux服务器如Ubuntu 20.04/22.04 LTS作为承载环境这有利于后续的维护和扩展。如果你只是在本地学习macOS或Windows也可以但步骤可能略有差异。3.1 服务器环境初始化首先通过SSH连接到你的服务器进行基础配置。# 1. 更新系统包列表并升级现有软件 sudo apt-get update sudo apt-get upgrade -y # 2. 安装一些必要的工具 sudo apt-get install -y curl wget unzip zip net-tools openjdk-11-jdk # 3. 配置Java环境变量通常安装后自动设置验证一下 java -version # 应输出类似 openjdk version 11.0.xx 的信息注意Appium 2.x 对Node.js版本有要求。Grid 4本身是Java应用但Appium Node需要Node.js环境。我们选择使用Node Version Manager (nvm)来管理Node.js这样可以灵活切换版本。3.2 安装与配置Node.js (使用nvm)# 1. 下载并安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # 2. 重新加载shell配置使nvm命令生效 # 对于bash: source ~/.bashrc # 对于zsh: source ~/.zshrc # 如果不确定可以退出SSH重新登录。 # 3. 验证nvm安装 nvm --version # 4. 安装Node.js的长期支持版(LTS)Appium 2.x推荐使用v18或v20 nvm install 18 nvm use 18 nvm alias default 18 # 设置为默认版本 # 5. 验证Node.js和npm node --version npm --version3.3 安装Appium 2.x 及相关驱动Appium 2.x采用了全新的架构将不同平台的驱动如UIAutomator2, XCUITest作为独立插件安装。这更清晰也更容易管理。# 1. 使用npm全局安装Appium 2.x npm install -g appiumnext # 2. 安装Appium的驱动管理工具如果未自动安装 npm install -g appium-driver # 3. 安装常用的驱动程序 # 安装Android UIAutomator2驱动用于安卓真机和模拟器 appium driver install uiautomator2 # 如果你需要测试iOS还需要安装XCUITest驱动此步骤通常在macOS上进行 # appium driver install xcuitest # 4. 安装Appium Doctor用于检查环境 npm install -g appium-doctor # 5. 运行Appium Doctor检查安卓环境在Linux服务器上 appium-doctor --android # 根据输出提示安装缺失的依赖如Android SDK。3.4 配置Android SDK (用于安卓设备节点)这是配置安卓设备节点的关键一步。我们不需要完整的Android Studio只需要命令行工具。# 1. 创建SDK安装目录 mkdir -p ~/android-sdk cd ~/android-sdk # 2. 下载最新的命令行工具包 # 请访问 https://developer.android.com/studio#command-tools 获取最新链接 wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip # 注意版本号会更新请替换为实际最新版本号。 # 3. 解压 unzip commandlinetools-linux-*.zip # 4. 设置环境变量。编辑 ~/.bashrc (或 ~/.zshrc) echo export ANDROID_HOME$HOME/android-sdk ~/.bashrc echo export PATH$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools ~/.bashrc source ~/.bashrc # 5. 使用sdkmanager安装必要的组件 # 接受所有许可 yes | sdkmanager --licenses # 安装平台工具、构建工具和一个系统镜像用于创建模拟器 sdkmanager platform-tools platforms;android-33 build-tools;33.0.0 system-images;android-33;google_apis;x86_64 # 6. 验证adb adb version至此我们的基础软件环境就准备好了。接下来是重头戏搭建Selenium Grid 4。4. Selenium Grid 4 Hub与Node部署详解我们将按照“先Hub后Node”的顺序进行部署。Hub是中心调度器Node是执行节点承载设备。4.1 部署Grid HubGrid Hub是一个标准的Java Jar包部署非常简单。# 1. 下载最新版本的Selenium Server (Grid) # 访问 https://www.selenium.dev/downloads/ 获取最新稳定版链接 cd ~ wget https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.15.0/selenium-server-4.15.0.jar # 请将版本号替换为最新。 # 2. 创建一个简单的启动脚本方便管理 cat start_grid_hub.sh EOF #!/bin/bash JAVA_OPTS-Xmx512m SELENIUM_JARselenium-server-4.15.0.jar LOG_FILEgrid_hub.log echo 启动Selenium Grid Hub... nohup java $JAVA_OPTS -jar $SELENIUM_JAR hub --port 4444 $LOG_FILE 21 HUB_PID$! echo Hub已启动进程PID: $HUB_PID echo 日志输出到: $LOG_FILE echo 控制台地址: http://$(hostname -I | awk {print $1}):4444 EOF # 3. 赋予脚本执行权限并启动Hub chmod x start_grid_hub.sh ./start_grid_hub.sh启动后打开浏览器访问http://你的服务器IP:4444。你应该能看到Selenium Grid的图形化控制台上面显示当前注册的节点数为0。这说明Hub已经成功运行。4.2 配置并启动Appium Grid Node关键步骤这是最核心的配置环节。每个设备或模拟器都需要一个独立的Node进程。Node的配置通过一个config.toml文件完成它定义了节点的能力Capabilities。第一步创建Node配置文件假设我们第一台Node连接的是一个安卓真机。在服务器上创建一个目录存放Node配置。mkdir -p ~/grid-nodes/android-node-1 cd ~/grid-nodes/android-node-1创建配置文件config.toml# config.toml [node] # 会话超时时间防止会话挂起 session-timeout 300 # 覆盖会话重试次数 override-max-sessions false max-sessions 1 # 每个Node同时只运行一个会话对应一台设备 [[node.driver-config]] # 显示名称在Grid UI中展示 display-name Android Phone - OnePlus 9 (Android 13) # 使用的驱动实现对于Appium我们使用“外部”服务 stereotype {browserName: chrome, platformName: Android, appium:platformVersion: 13, appium:deviceName: OnePlus9} # 最大并发会话数同样设置为1 max-sessions 1 # 最关键的部分定义如何启动Appium服务 [node] # 指定这是一个“完全分布式”的节点配置 detect-drivers false [[node.driver-config]] # 指定Appium服务器的启动命令和参数 implementation appium # Appium服务器的可执行文件路径假设已全局安装 executable /root/.nvm/versions/node/v18.20.0/bin/appium # 传递给Appium的参数 args [ --relaxed-security, --allow-insecureadb_shell, --base-path/wd/hub, --port4723 # 这个端口是Appium服务本身监听的端口每个Node必须不同 ] # Appium服务的健康检查端点 healthcheck http://localhost:4723/wd/hub/status # 启动后等待服务就绪的时间毫秒 startup-timeout 60000重要参数解析stereotype: 这里定义的是该节点的“能力模板”。当测试脚本请求的设备能力与此匹配时Grid就会将任务路由到此节点。browserName在移动端测试中通常没用但必须设置我们设为chrome。核心是platformName,appium:platformVersion,appium:deviceName。注意这里的deviceName可以是一个通用标识如OnePlus9不一定必须是adb devices查出的精确名称但后续Appium启动时需要能关联到具体设备。executable: 你的Appium二进制文件路径。使用which appium命令可以找到。args: Appium启动参数。--relaxed-security: 允许一些不安全的操作简化配置。--allow-insecureadb_shell: 允许使用adb shell命令很多操作需要。--base-path/wd/hub: Appium 2.x的默认基础路径。--port4723:每个Node的Appium服务端口必须唯一如果启动第二个Node需要用另一个端口如4724。healthcheck: Grid会定期ping这个URL来检查Node是否健康。第二步编写Node启动脚本创建启动脚本start_node.shcat start_node.sh EOF #!/bin/bash CONFIG_FILEconfig.toml SELENIUM_JAR/root/selenium-server-4.15.0.jar # 指向Hub下载的jar包路径 LOG_FILEnode.log HUB_URLhttp://localhost:4444 # Hub的地址如果Node和Hub在同一机器就是localhost echo 启动Appium Grid Node... # 关键通过环境变量指定Appium要连接的设备UDID # 假设我们通过adb连接了一台设备其UDID为“abc123” export APPIUM_UDIDabc123 nohup java -jar $SELENIUM_JAR node --config $CONFIG_FILE --hub $HUB_URL $LOG_FILE 21 NODE_PID$! echo Node已启动进程PID: $NODE_PID echo 日志输出到: $LOG_FILE EOF chmod x start_node.sh第三步连接设备并启动Node连接安卓真机通过USB将手机连接到服务器并开启手机的USB调试模式。在服务器上执行adb devices确认设备已被识别记下其UDID一串字母数字。修改脚本将start_node.sh中的export APPIUM_UDIDabc123替换为你的设备真实UDID。启动Node./start_node.sh。第四步验证Node注册再次刷新Grid Hub的控制台 (http://服务器IP:4444)。你应该能在“Nodes”部分看到新注册的节点其Stereotype显示为我们配置的Android Phone - OnePlus 9。实操心得config.toml中的stereotype是过滤和路由的关键。你的测试脚本在创建DesiredCapabilities时必须包含与之匹配的键值对Grid才会把任务分配过来。例如脚本中platformVersion设为13deviceName设为OnePlus9就能匹配到这个节点。你可以为不同系统版本、不同型号的设备创建不同的config.toml文件启动多个Node进程。4.3 配置多设备与模拟器节点如果你有多个真机或者需要启动安卓模拟器流程是类似的关键在于区分端口和设备标识。场景一第二台安卓真机创建新目录~/grid-nodes/android-node-2。复制config.toml修改display-namestereotype中的appium:platformVersion和appium:deviceName。args中的--port改为4724。创建新的start_node.sh修改CONFIG_FILE路径。export APPIUM_UDID为第二台设备的UDID。LOG_FILE名称。启动第二个Node。场景二安卓模拟器节点首先用Android SDK创建一个模拟器cd $ANDROID_HOME/tools/bin ./avdmanager create avd -n Pixel_5_API_33 -k system-images;android-33;google_apis;x86_64 -d pixel_5启动模拟器可以无头模式启动节省资源emulator -avd Pixel_5_API_33 -no-window -no-audio 等待模拟器启动完成用adb devices确认其UDID。像配置真机Node一样创建模拟器节点的配置目录和config.tomlstereotype中deviceName可以设为Android Emulator。启动NodeAPPIUM_UDID设为模拟器的UDID。现在你的Grid Hub控制台上应该能看到多个不同能力的节点了。5. 编写测试脚本并连接Grid Farm设备农场搭建好了我们如何用它呢测试脚本需要从直接连接Appium改为通过Grid Hub来申请设备。以下是一个使用Pythonappium-python-client的示例脚本# test_on_grid.py from appium import webdriver from appium.options.common import AppiumOptions from selenium.webdriver.common.by import By import time # 1. 定义Desired Capabilities # 这些能力必须与某个Node的 stereotype 匹配 capabilities { platformName: Android, appium:platformVersion: 13, # 匹配我们Node配置的13 appium:deviceName: OnePlus9, # 匹配我们Node配置的OnePlus9 appium:automationName: uiautomator2, appium:appPackage: com.android.calculator2, # 测试系统计算器 appium:appActivity: .Calculator, # 关键指定使用Grid Hub appium:newCommandTimeout: 300, } # 2. Grid Hub的地址注意路径是 /wd/hub grid_url http://你的服务器IP:4444/wd/hub # 3. 创建驱动实例 # AppiumOptions用于管理能力字典 options AppiumOptions().load_capabilities(capabilities) try: # 这里连接的是Grid Hub而不是具体的Appium服务器 driver webdriver.Remote(command_executorgrid_url, optionsoptions) print(成功通过Grid连接到设备) print(f会话ID: {driver.session_id}) print(f设备信息: {driver.capabilities}) # 4. 执行一些简单的测试操作 # 例如点击计算器按钮 driver.find_element(By.ID, com.android.calculator2:id/digit_7).click() driver.find_element(By.ID, com.android.calculator2:id/op_add).click() driver.find_element(By.ID, com.android.calculator2:id/digit_8).click() driver.find_element(By.ID, com.android.calculator2:id/eq).click() result driver.find_element(By.ID, com.android.calculator2:id/result).text print(f计算结果: {result}) time.sleep(2) except Exception as e: print(f测试执行出错: {e}) finally: # 5. 退出会话释放设备资源 if driver in locals(): driver.quit() print(测试结束设备已释放回设备池。)脚本关键点解析command_executor这是最大的变化。以前你直接填http://localhost:4723/wd/hub现在填的是Grid Hub的地址http://grid_hub_ip:4444/wd/hub。Grid会充当代理。能力匹配脚本中的platformVersion和deviceName必须与目标Node的stereotype匹配。Grid的路由器会根据这些信息将会话请求转发到符合条件的空闲Node上。资源释放driver.quit()至关重要。这会通知Grid会话结束Grid随后会通知对应的Node释放设备使其状态变回“空闲”可供其他测试使用。运行这个脚本你会在Grid控制台的“Sessions”标签页看到正在运行的会话并在对应的Node上看到设备被占用。6. 高级配置、优化与故障排查基础功能跑通后我们需要让这个Farm更稳定、更易用。6.1 Grid与Node的常用配置参数Hub 配置(java -jar selenium-server.jar hub --help)--session-request-timeout: 会话请求超时时间秒。如果所有匹配的Node都繁忙请求会在这个时间后失败。默认300。--session-retry-interval: 重试分配会话的间隔秒。默认5。--max-sessions: Hub允许的最大并发会话总数。根据你的Node总数设置。Node 配置(config.toml或java -jar selenium-server.jar node --help)max-sessions:强烈建议设为1。一个Appium Node实例对应一台物理设备是最清晰、最稳定的模型。虽然技术上可以一个Node服务多设备通过不同的Appium端口但管理复杂容易冲突。session-timeout: 节点上会话的空闲超时时间。防止脚本异常退出导致设备一直被占用。down-polling-interval: 节点检查Hub是否存活的时间间隔毫秒。unregister-if-still-down-after: 节点认为Hub宕机后等待多久才注销自己毫秒。6.2 使用Docker容器化部署推荐用于生产手动管理多个Node进程很麻烦。使用Docker可以极大简化部署和扩展。思路是为每个设备Node构建一个Docker镜像镜像内包含Java、Node.js、Appium、Android SDK以及我们的config.toml。然后通过Docker Compose或K8s来管理。示例Dockerfile (对于安卓Node):FROM openjdk:11-jre-slim # 安装Node.js RUN apt-get update apt-get install -y curl \ curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ apt-get install -y nodejs \ npm install -g appiumnext appium-driver uiautomator2 # 安装Android SDK (简化版可优化为多阶段构建) RUN apt-get install -y wget unzip \ mkdir -p /opt/android-sdk cd /opt/android-sdk \ wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip \ unzip *.zip rm *.zip \ mv cmdline-tools latest \ yes | /opt/android-sdk/latest/bin/sdkmanager --sdk_root/opt/android-sdk platform-tools ENV ANDROID_HOME/opt/android-sdk ENV PATH$PATH:$ANDROID_HOME/platform-tools # 复制Grid Server Jar和配置文件 COPY selenium-server-4.15.0.jar /opt/selenium/selenium-server.jar COPY config.toml /opt/selenium/config.toml # 暴露Appium端口在config.toml中定义 EXPOSE 4723 # 启动脚本 COPY entrypoint.sh /opt/selenium/entrypoint.sh RUN chmod x /opt/selenium/entrypoint.sh ENTRYPOINT [/opt/selenium/entrypoint.sh]entrypoint.sh:#!/bin/bash # 启动adb server adb start-server # 等待设备连接在实际中可能需要通过--device参数传递UDID # 这里假设设备已经通过USB连接到宿主机并被映射到容器内 # 启动Appium Grid Node java -jar /opt/selenium/selenium-server.jar node --config /opt/selenium/config.toml --hub http://grid-hub:4444然后使用docker run启动容器并通过--device参数将宿主机的USB设备映射到容器内。通过Docker Compose可以轻松定义多个这样的服务。6.3 常见问题与排查技巧实录搭建过程中你肯定会遇到各种问题。这里记录几个最常见的坑和解决办法。问题1Node成功启动但在Grid控制台显示为“down”或“unreachable”。排查检查Node日志 (node.log)。最常见的原因是healthcheck失败。在Node机器上手动执行appium --relaxed-security --allow-insecureadb_shell --base-path/wd/hub --port4723看Appium服务是否能独立启动。检查config.toml中的executable路径是否正确。使用which appium确认。检查Node与Hub之间的网络连通性。telnet hub_ip 4444。解决确保Appium能独立运行并且healthcheck的URL (http://localhost:4723/wd/hub/status) 能返回成功的JSON响应。问题2测试脚本提交到Grid后一直处于“等待中”最后超时失败。排查在Grid控制台查看是否有符合条件的空闲Node。检查Node的stereotype是否与脚本的DesiredCapabilities精确匹配。注意大小写和键的名称如appium:platformVersionvsplatformVersion。Grid 4匹配非常严格。检查Node的max-sessions是否为0或者所有会话槽位是否已满。解决仔细核对能力配置。在脚本中尽量使用AppiumOptions来规范能力字典的格式。在Grid UI中可以点击Node查看其详细的能力声明。问题3测试脚本报错提示无法创建新会话错误信息涉及无法连接到设备。排查首先确认设备是否通过adb devices在线且状态为device。查看对应Node的日志以及Appium服务器的日志通常Appium会输出到Node日志中。错误信息通常会指出是adb命令执行失败还是无法安装/启动测试应用。检查设备上是否开启了USB调试以及是否授权了电脑的RSA密钥指纹第一次连接时设备屏幕会有提示。解决重新插拔USB线在设备上撤销USB调试授权再重新授权。对于模拟器确保模拟器已完全启动。问题4如何动态管理大量设备节点建议不要手动管理。可以采用以下策略脚本化编写Shell或Python脚本根据设备列表自动生成config.toml和启动命令。容器编排如上文所述使用Docker Compose或Kubernetes。每个Pod对应一个设备Node通过hostPath将设备挂载进容器。结合K8s的DaemonSet和节点标签可以实现设备资源的动态调度。使用专门的服务考虑更专业的设备管理平台如开源项目OpenSTF的后继者或者基于appium-device-farm包进行二次开发。问题5iOS设备如何接入核心差异iOS真机测试必须在macOS系统上进行因为需要Xcode和相关的开发者工具。步骤在一台macOS机器上配置好Appium、Xcode、WebDriverAgent等iOS测试环境。将该macOS机器作为一个Grid Node。其config.toml中的executable路径指向mac上的Appiumstereotype中platformName设为iOS。启动Node注册到中心的Grid HubHub可以部署在Linux服务器上。测试脚本中指定platformName: iOS等能力即可通过Grid分配到这台mac上的iOS设备。注意网络需要互通且mac上的Appium需要允许来自Hub的远程连接。最后再分享一个维护上的小技巧给设备贴标签。在config.toml的stereotype中可以添加自定义的appium:tags如[smoke, regression, high-perf]。在测试脚本中也可以通过能力指定需要的标签。这样Grid可以实现更精细化的测试任务路由比如将冒烟测试只分配给那些标记为smoke的稳定设备。
基于Selenium Grid 4搭建Appium设备农场:实现多设备自动化测试集群
发布时间:2026/6/26 22:32:06
1. 项目概述为什么我们需要一个Appium Device Farm如果你是一名移动端自动化测试工程师或者正在向这个方向发展那么“测试设备管理”这个问题迟早会成为你效率提升路上的拦路虎。想象一下这个场景你写了一套非常棒的Appium自动化脚本在你自己那台用了两年的安卓手机上跑得飞快。然后产品经理跑过来说“我们得覆盖一下iOS 15和16还有安卓10到13的几个主流机型对了还有那个新出的折叠屏手机也得测一下兼容性。”你看着手边仅有的两三台设备瞬间头大。这就是Appium Device Farm要解决的问题。它不是一个单一的工具而是一个设备集群管理方案。简单来说它允许你将多台物理或虚拟的移动设备Android/iOS接入到一个中心化的服务中然后你的Appium测试脚本可以像调用远程API一样按需申请、使用并释放这些设备实现真正的并行、多设备、多版本的自动化测试。对于追求测试效率、覆盖度和持续集成CI的团队来说这是从“手工作坊”迈向“自动化工厂”的关键一步。网上有很多零散的教程教你怎么装Appium、配环境但关于如何把这些分散的设备组织成一个高效、稳定、可管理的“农场”Farm系统性的中文指南并不多见。今天我就结合自己搭建和维护多套Device Farm的经验从零开始带你走通整个安装、配置和核心调优的流程。无论你是想在本机搭建一个小型测试集群还是在服务器上部署一个供团队使用的设备池这篇文章都能给你提供可直接复现的详细步骤和避坑指南。2. 核心架构与方案选型在动手之前我们必须搞清楚我们要搭建的到底是什么以及有哪些主流方案可选。这决定了后续所有技术栈和配置的走向。2.1 Device Farm的核心组件一个完整的Appium Device Farm通常包含以下几个核心部分设备节点即实际的移动设备可以是USB连接的真机、局域网Wi-Fi连接的设备、或者Android模拟器/iOS模拟器。每个设备都需要运行一个appium-server实例作为其“代理”。Appium服务器运行在设备节点上的服务负责接收来自客户端的WebDriver协议请求并将其转化为对设备通过UIAutomator2/XCUITest的操作。设备管理中枢这是Farm的大脑。它负责设备注册与发现设备节点启动后向中枢注册自己的信息如UDID、系统版本、状态等。任务调度接收测试任务的请求根据策略如轮询、基于标签将任务分配给空闲且符合条件的设备。状态监控实时监控设备状态空闲、使用中、离线、异常。提供API对外提供一套RESTful API供测试脚本或CI系统调用来申请设备、执行测试。客户端/测试脚本你的自动化测试代码。它不再直接连接某个具体设备的IP和端口而是调用设备管理中枢的API由中枢分配设备并返回连接信息。2.2 主流方案对比与选型目前社区主要有以下几种实现方式我将它们分为“集成式”和“自建式”方案一使用开源集成方案 - Appium Device Farm (formerly OpenSTF) 的衍生思路STFSmartphone Test Farm曾经是业界标杆但其原作者已停止维护。不过其思想被许多项目继承。例如你可以使用appium-device-farm这个Node.js包或者基于Docker Selenium Grid 4 模式进行改造。这类方案集成度较高但可能需要一定的定制化开发。优点架构清晰社区有相关实践可参考。缺点需要自己整合Appium配置和调试过程相对复杂对运维能力要求高。方案二基于Selenium Grid 4进行扩展Selenium Grid 4 引入了对Appium移动端的原生支持。你可以将每个“设备节点”配置为Grid中的一个Node而Grid的Hub就自然成为了设备管理中枢。这是目前最推荐、最标准的自建方案。优点标准协议完全遵循W3C WebDriver协议和Selenium Grid架构生态兼容性好。功能强大自带负载均衡、会话管理、可视化UI、丰富的路由策略。社区活跃Selenium社区庞大问题容易找到解决方案。与CI/CD无缝集成Jenkins、GitLab CI等工具都有成熟的Grid插件。缺点需要理解Grid的架构初始配置有一定学习成本。方案三云服务商方案如AWS Device Farm, BrowserStack直接使用商业云服务。这不在我们今天“安装与配置”的讨论范围内但它是解决设备问题最快捷的方式适合预算充足、不想投入运维的团队。我们的选择 为了最具普适性和学习价值本文将重点详解方案二基于Selenium Grid 4搭建Appium Device Farm。这是目前平衡了可控性、功能性和社区支持的最佳实践。我们将搭建一个由1个Hub和多个Node每个Node对应一台设备或一个模拟器组成的测试集群。3. 基础环境准备与全局配置工欲善其事必先利其器。在开始搭建Grid和Appium之前我们需要一个干净、一致的基础环境。我强烈建议使用一台独立的Linux服务器如Ubuntu 20.04/22.04 LTS作为承载环境这有利于后续的维护和扩展。如果你只是在本地学习macOS或Windows也可以但步骤可能略有差异。3.1 服务器环境初始化首先通过SSH连接到你的服务器进行基础配置。# 1. 更新系统包列表并升级现有软件 sudo apt-get update sudo apt-get upgrade -y # 2. 安装一些必要的工具 sudo apt-get install -y curl wget unzip zip net-tools openjdk-11-jdk # 3. 配置Java环境变量通常安装后自动设置验证一下 java -version # 应输出类似 openjdk version 11.0.xx 的信息注意Appium 2.x 对Node.js版本有要求。Grid 4本身是Java应用但Appium Node需要Node.js环境。我们选择使用Node Version Manager (nvm)来管理Node.js这样可以灵活切换版本。3.2 安装与配置Node.js (使用nvm)# 1. 下载并安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # 2. 重新加载shell配置使nvm命令生效 # 对于bash: source ~/.bashrc # 对于zsh: source ~/.zshrc # 如果不确定可以退出SSH重新登录。 # 3. 验证nvm安装 nvm --version # 4. 安装Node.js的长期支持版(LTS)Appium 2.x推荐使用v18或v20 nvm install 18 nvm use 18 nvm alias default 18 # 设置为默认版本 # 5. 验证Node.js和npm node --version npm --version3.3 安装Appium 2.x 及相关驱动Appium 2.x采用了全新的架构将不同平台的驱动如UIAutomator2, XCUITest作为独立插件安装。这更清晰也更容易管理。# 1. 使用npm全局安装Appium 2.x npm install -g appiumnext # 2. 安装Appium的驱动管理工具如果未自动安装 npm install -g appium-driver # 3. 安装常用的驱动程序 # 安装Android UIAutomator2驱动用于安卓真机和模拟器 appium driver install uiautomator2 # 如果你需要测试iOS还需要安装XCUITest驱动此步骤通常在macOS上进行 # appium driver install xcuitest # 4. 安装Appium Doctor用于检查环境 npm install -g appium-doctor # 5. 运行Appium Doctor检查安卓环境在Linux服务器上 appium-doctor --android # 根据输出提示安装缺失的依赖如Android SDK。3.4 配置Android SDK (用于安卓设备节点)这是配置安卓设备节点的关键一步。我们不需要完整的Android Studio只需要命令行工具。# 1. 创建SDK安装目录 mkdir -p ~/android-sdk cd ~/android-sdk # 2. 下载最新的命令行工具包 # 请访问 https://developer.android.com/studio#command-tools 获取最新链接 wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip # 注意版本号会更新请替换为实际最新版本号。 # 3. 解压 unzip commandlinetools-linux-*.zip # 4. 设置环境变量。编辑 ~/.bashrc (或 ~/.zshrc) echo export ANDROID_HOME$HOME/android-sdk ~/.bashrc echo export PATH$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools ~/.bashrc source ~/.bashrc # 5. 使用sdkmanager安装必要的组件 # 接受所有许可 yes | sdkmanager --licenses # 安装平台工具、构建工具和一个系统镜像用于创建模拟器 sdkmanager platform-tools platforms;android-33 build-tools;33.0.0 system-images;android-33;google_apis;x86_64 # 6. 验证adb adb version至此我们的基础软件环境就准备好了。接下来是重头戏搭建Selenium Grid 4。4. Selenium Grid 4 Hub与Node部署详解我们将按照“先Hub后Node”的顺序进行部署。Hub是中心调度器Node是执行节点承载设备。4.1 部署Grid HubGrid Hub是一个标准的Java Jar包部署非常简单。# 1. 下载最新版本的Selenium Server (Grid) # 访问 https://www.selenium.dev/downloads/ 获取最新稳定版链接 cd ~ wget https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.15.0/selenium-server-4.15.0.jar # 请将版本号替换为最新。 # 2. 创建一个简单的启动脚本方便管理 cat start_grid_hub.sh EOF #!/bin/bash JAVA_OPTS-Xmx512m SELENIUM_JARselenium-server-4.15.0.jar LOG_FILEgrid_hub.log echo 启动Selenium Grid Hub... nohup java $JAVA_OPTS -jar $SELENIUM_JAR hub --port 4444 $LOG_FILE 21 HUB_PID$! echo Hub已启动进程PID: $HUB_PID echo 日志输出到: $LOG_FILE echo 控制台地址: http://$(hostname -I | awk {print $1}):4444 EOF # 3. 赋予脚本执行权限并启动Hub chmod x start_grid_hub.sh ./start_grid_hub.sh启动后打开浏览器访问http://你的服务器IP:4444。你应该能看到Selenium Grid的图形化控制台上面显示当前注册的节点数为0。这说明Hub已经成功运行。4.2 配置并启动Appium Grid Node关键步骤这是最核心的配置环节。每个设备或模拟器都需要一个独立的Node进程。Node的配置通过一个config.toml文件完成它定义了节点的能力Capabilities。第一步创建Node配置文件假设我们第一台Node连接的是一个安卓真机。在服务器上创建一个目录存放Node配置。mkdir -p ~/grid-nodes/android-node-1 cd ~/grid-nodes/android-node-1创建配置文件config.toml# config.toml [node] # 会话超时时间防止会话挂起 session-timeout 300 # 覆盖会话重试次数 override-max-sessions false max-sessions 1 # 每个Node同时只运行一个会话对应一台设备 [[node.driver-config]] # 显示名称在Grid UI中展示 display-name Android Phone - OnePlus 9 (Android 13) # 使用的驱动实现对于Appium我们使用“外部”服务 stereotype {browserName: chrome, platformName: Android, appium:platformVersion: 13, appium:deviceName: OnePlus9} # 最大并发会话数同样设置为1 max-sessions 1 # 最关键的部分定义如何启动Appium服务 [node] # 指定这是一个“完全分布式”的节点配置 detect-drivers false [[node.driver-config]] # 指定Appium服务器的启动命令和参数 implementation appium # Appium服务器的可执行文件路径假设已全局安装 executable /root/.nvm/versions/node/v18.20.0/bin/appium # 传递给Appium的参数 args [ --relaxed-security, --allow-insecureadb_shell, --base-path/wd/hub, --port4723 # 这个端口是Appium服务本身监听的端口每个Node必须不同 ] # Appium服务的健康检查端点 healthcheck http://localhost:4723/wd/hub/status # 启动后等待服务就绪的时间毫秒 startup-timeout 60000重要参数解析stereotype: 这里定义的是该节点的“能力模板”。当测试脚本请求的设备能力与此匹配时Grid就会将任务路由到此节点。browserName在移动端测试中通常没用但必须设置我们设为chrome。核心是platformName,appium:platformVersion,appium:deviceName。注意这里的deviceName可以是一个通用标识如OnePlus9不一定必须是adb devices查出的精确名称但后续Appium启动时需要能关联到具体设备。executable: 你的Appium二进制文件路径。使用which appium命令可以找到。args: Appium启动参数。--relaxed-security: 允许一些不安全的操作简化配置。--allow-insecureadb_shell: 允许使用adb shell命令很多操作需要。--base-path/wd/hub: Appium 2.x的默认基础路径。--port4723:每个Node的Appium服务端口必须唯一如果启动第二个Node需要用另一个端口如4724。healthcheck: Grid会定期ping这个URL来检查Node是否健康。第二步编写Node启动脚本创建启动脚本start_node.shcat start_node.sh EOF #!/bin/bash CONFIG_FILEconfig.toml SELENIUM_JAR/root/selenium-server-4.15.0.jar # 指向Hub下载的jar包路径 LOG_FILEnode.log HUB_URLhttp://localhost:4444 # Hub的地址如果Node和Hub在同一机器就是localhost echo 启动Appium Grid Node... # 关键通过环境变量指定Appium要连接的设备UDID # 假设我们通过adb连接了一台设备其UDID为“abc123” export APPIUM_UDIDabc123 nohup java -jar $SELENIUM_JAR node --config $CONFIG_FILE --hub $HUB_URL $LOG_FILE 21 NODE_PID$! echo Node已启动进程PID: $NODE_PID echo 日志输出到: $LOG_FILE EOF chmod x start_node.sh第三步连接设备并启动Node连接安卓真机通过USB将手机连接到服务器并开启手机的USB调试模式。在服务器上执行adb devices确认设备已被识别记下其UDID一串字母数字。修改脚本将start_node.sh中的export APPIUM_UDIDabc123替换为你的设备真实UDID。启动Node./start_node.sh。第四步验证Node注册再次刷新Grid Hub的控制台 (http://服务器IP:4444)。你应该能在“Nodes”部分看到新注册的节点其Stereotype显示为我们配置的Android Phone - OnePlus 9。实操心得config.toml中的stereotype是过滤和路由的关键。你的测试脚本在创建DesiredCapabilities时必须包含与之匹配的键值对Grid才会把任务分配过来。例如脚本中platformVersion设为13deviceName设为OnePlus9就能匹配到这个节点。你可以为不同系统版本、不同型号的设备创建不同的config.toml文件启动多个Node进程。4.3 配置多设备与模拟器节点如果你有多个真机或者需要启动安卓模拟器流程是类似的关键在于区分端口和设备标识。场景一第二台安卓真机创建新目录~/grid-nodes/android-node-2。复制config.toml修改display-namestereotype中的appium:platformVersion和appium:deviceName。args中的--port改为4724。创建新的start_node.sh修改CONFIG_FILE路径。export APPIUM_UDID为第二台设备的UDID。LOG_FILE名称。启动第二个Node。场景二安卓模拟器节点首先用Android SDK创建一个模拟器cd $ANDROID_HOME/tools/bin ./avdmanager create avd -n Pixel_5_API_33 -k system-images;android-33;google_apis;x86_64 -d pixel_5启动模拟器可以无头模式启动节省资源emulator -avd Pixel_5_API_33 -no-window -no-audio 等待模拟器启动完成用adb devices确认其UDID。像配置真机Node一样创建模拟器节点的配置目录和config.tomlstereotype中deviceName可以设为Android Emulator。启动NodeAPPIUM_UDID设为模拟器的UDID。现在你的Grid Hub控制台上应该能看到多个不同能力的节点了。5. 编写测试脚本并连接Grid Farm设备农场搭建好了我们如何用它呢测试脚本需要从直接连接Appium改为通过Grid Hub来申请设备。以下是一个使用Pythonappium-python-client的示例脚本# test_on_grid.py from appium import webdriver from appium.options.common import AppiumOptions from selenium.webdriver.common.by import By import time # 1. 定义Desired Capabilities # 这些能力必须与某个Node的 stereotype 匹配 capabilities { platformName: Android, appium:platformVersion: 13, # 匹配我们Node配置的13 appium:deviceName: OnePlus9, # 匹配我们Node配置的OnePlus9 appium:automationName: uiautomator2, appium:appPackage: com.android.calculator2, # 测试系统计算器 appium:appActivity: .Calculator, # 关键指定使用Grid Hub appium:newCommandTimeout: 300, } # 2. Grid Hub的地址注意路径是 /wd/hub grid_url http://你的服务器IP:4444/wd/hub # 3. 创建驱动实例 # AppiumOptions用于管理能力字典 options AppiumOptions().load_capabilities(capabilities) try: # 这里连接的是Grid Hub而不是具体的Appium服务器 driver webdriver.Remote(command_executorgrid_url, optionsoptions) print(成功通过Grid连接到设备) print(f会话ID: {driver.session_id}) print(f设备信息: {driver.capabilities}) # 4. 执行一些简单的测试操作 # 例如点击计算器按钮 driver.find_element(By.ID, com.android.calculator2:id/digit_7).click() driver.find_element(By.ID, com.android.calculator2:id/op_add).click() driver.find_element(By.ID, com.android.calculator2:id/digit_8).click() driver.find_element(By.ID, com.android.calculator2:id/eq).click() result driver.find_element(By.ID, com.android.calculator2:id/result).text print(f计算结果: {result}) time.sleep(2) except Exception as e: print(f测试执行出错: {e}) finally: # 5. 退出会话释放设备资源 if driver in locals(): driver.quit() print(测试结束设备已释放回设备池。)脚本关键点解析command_executor这是最大的变化。以前你直接填http://localhost:4723/wd/hub现在填的是Grid Hub的地址http://grid_hub_ip:4444/wd/hub。Grid会充当代理。能力匹配脚本中的platformVersion和deviceName必须与目标Node的stereotype匹配。Grid的路由器会根据这些信息将会话请求转发到符合条件的空闲Node上。资源释放driver.quit()至关重要。这会通知Grid会话结束Grid随后会通知对应的Node释放设备使其状态变回“空闲”可供其他测试使用。运行这个脚本你会在Grid控制台的“Sessions”标签页看到正在运行的会话并在对应的Node上看到设备被占用。6. 高级配置、优化与故障排查基础功能跑通后我们需要让这个Farm更稳定、更易用。6.1 Grid与Node的常用配置参数Hub 配置(java -jar selenium-server.jar hub --help)--session-request-timeout: 会话请求超时时间秒。如果所有匹配的Node都繁忙请求会在这个时间后失败。默认300。--session-retry-interval: 重试分配会话的间隔秒。默认5。--max-sessions: Hub允许的最大并发会话总数。根据你的Node总数设置。Node 配置(config.toml或java -jar selenium-server.jar node --help)max-sessions:强烈建议设为1。一个Appium Node实例对应一台物理设备是最清晰、最稳定的模型。虽然技术上可以一个Node服务多设备通过不同的Appium端口但管理复杂容易冲突。session-timeout: 节点上会话的空闲超时时间。防止脚本异常退出导致设备一直被占用。down-polling-interval: 节点检查Hub是否存活的时间间隔毫秒。unregister-if-still-down-after: 节点认为Hub宕机后等待多久才注销自己毫秒。6.2 使用Docker容器化部署推荐用于生产手动管理多个Node进程很麻烦。使用Docker可以极大简化部署和扩展。思路是为每个设备Node构建一个Docker镜像镜像内包含Java、Node.js、Appium、Android SDK以及我们的config.toml。然后通过Docker Compose或K8s来管理。示例Dockerfile (对于安卓Node):FROM openjdk:11-jre-slim # 安装Node.js RUN apt-get update apt-get install -y curl \ curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ apt-get install -y nodejs \ npm install -g appiumnext appium-driver uiautomator2 # 安装Android SDK (简化版可优化为多阶段构建) RUN apt-get install -y wget unzip \ mkdir -p /opt/android-sdk cd /opt/android-sdk \ wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip \ unzip *.zip rm *.zip \ mv cmdline-tools latest \ yes | /opt/android-sdk/latest/bin/sdkmanager --sdk_root/opt/android-sdk platform-tools ENV ANDROID_HOME/opt/android-sdk ENV PATH$PATH:$ANDROID_HOME/platform-tools # 复制Grid Server Jar和配置文件 COPY selenium-server-4.15.0.jar /opt/selenium/selenium-server.jar COPY config.toml /opt/selenium/config.toml # 暴露Appium端口在config.toml中定义 EXPOSE 4723 # 启动脚本 COPY entrypoint.sh /opt/selenium/entrypoint.sh RUN chmod x /opt/selenium/entrypoint.sh ENTRYPOINT [/opt/selenium/entrypoint.sh]entrypoint.sh:#!/bin/bash # 启动adb server adb start-server # 等待设备连接在实际中可能需要通过--device参数传递UDID # 这里假设设备已经通过USB连接到宿主机并被映射到容器内 # 启动Appium Grid Node java -jar /opt/selenium/selenium-server.jar node --config /opt/selenium/config.toml --hub http://grid-hub:4444然后使用docker run启动容器并通过--device参数将宿主机的USB设备映射到容器内。通过Docker Compose可以轻松定义多个这样的服务。6.3 常见问题与排查技巧实录搭建过程中你肯定会遇到各种问题。这里记录几个最常见的坑和解决办法。问题1Node成功启动但在Grid控制台显示为“down”或“unreachable”。排查检查Node日志 (node.log)。最常见的原因是healthcheck失败。在Node机器上手动执行appium --relaxed-security --allow-insecureadb_shell --base-path/wd/hub --port4723看Appium服务是否能独立启动。检查config.toml中的executable路径是否正确。使用which appium确认。检查Node与Hub之间的网络连通性。telnet hub_ip 4444。解决确保Appium能独立运行并且healthcheck的URL (http://localhost:4723/wd/hub/status) 能返回成功的JSON响应。问题2测试脚本提交到Grid后一直处于“等待中”最后超时失败。排查在Grid控制台查看是否有符合条件的空闲Node。检查Node的stereotype是否与脚本的DesiredCapabilities精确匹配。注意大小写和键的名称如appium:platformVersionvsplatformVersion。Grid 4匹配非常严格。检查Node的max-sessions是否为0或者所有会话槽位是否已满。解决仔细核对能力配置。在脚本中尽量使用AppiumOptions来规范能力字典的格式。在Grid UI中可以点击Node查看其详细的能力声明。问题3测试脚本报错提示无法创建新会话错误信息涉及无法连接到设备。排查首先确认设备是否通过adb devices在线且状态为device。查看对应Node的日志以及Appium服务器的日志通常Appium会输出到Node日志中。错误信息通常会指出是adb命令执行失败还是无法安装/启动测试应用。检查设备上是否开启了USB调试以及是否授权了电脑的RSA密钥指纹第一次连接时设备屏幕会有提示。解决重新插拔USB线在设备上撤销USB调试授权再重新授权。对于模拟器确保模拟器已完全启动。问题4如何动态管理大量设备节点建议不要手动管理。可以采用以下策略脚本化编写Shell或Python脚本根据设备列表自动生成config.toml和启动命令。容器编排如上文所述使用Docker Compose或Kubernetes。每个Pod对应一个设备Node通过hostPath将设备挂载进容器。结合K8s的DaemonSet和节点标签可以实现设备资源的动态调度。使用专门的服务考虑更专业的设备管理平台如开源项目OpenSTF的后继者或者基于appium-device-farm包进行二次开发。问题5iOS设备如何接入核心差异iOS真机测试必须在macOS系统上进行因为需要Xcode和相关的开发者工具。步骤在一台macOS机器上配置好Appium、Xcode、WebDriverAgent等iOS测试环境。将该macOS机器作为一个Grid Node。其config.toml中的executable路径指向mac上的Appiumstereotype中platformName设为iOS。启动Node注册到中心的Grid HubHub可以部署在Linux服务器上。测试脚本中指定platformName: iOS等能力即可通过Grid分配到这台mac上的iOS设备。注意网络需要互通且mac上的Appium需要允许来自Hub的远程连接。最后再分享一个维护上的小技巧给设备贴标签。在config.toml的stereotype中可以添加自定义的appium:tags如[smoke, regression, high-perf]。在测试脚本中也可以通过能力指定需要的标签。这样Grid可以实现更精细化的测试任务路由比如将冒烟测试只分配给那些标记为smoke的稳定设备。