1. 项目概述当Selenium遇上Appium桌面Web自动化思维如何“降维打击”移动端如果你和我一样是从Web自动化测试比如用Selenium入行的第一次接触移动端App自动化时大概率会有点懵。设备怎么连元素怎么抓模拟器怎么搞一堆新名词扑面而来。但当我真正开始用Java Selenium Appium这套组合拳来做App自动化时我发现了一个有趣的现象很多在Web端玩得滚瓜烂熟的理念和技巧在移动端竟然能无缝“平移”甚至因为移动端场景更聚焦实现起来反而更简单。这个项目就是一次将成熟的Web自动化工程化思维系统性地应用到移动App测试中的实战记录。简单来说这个项目的核心目标就是利用Java语言的稳定生态、Selenium WebDriver的标准化协议思想以及Appium对移动设备的驱动能力构建一套能够模拟真实用户操作App的自动化测试框架。它解决的痛点非常明确手工重复测试App的回归用例耗时耗力、不同机型/系统版本兼容性测试成本高、以及UI交互逻辑复杂导致测试覆盖不全。无论是测试原生Android/iOS应用、混合应用Hybrid App还是内嵌的WebView页面这套组合都能提供统一的编程接口。适合谁来参考如果你是正在从Web测试转向移动端测试的工程师或者你的团队需要为App建立基础的自动化回归能力但又不想被某个商业工具绑定那么这篇从环境搭建、脚本编写到框架设计的全程实录应该能给你提供一条清晰的路径。我会重点分享如何用你熟悉的Java和Selenium WebDriver API去理解和驾驭Appium把“模拟App”这件事变得像写Web测试脚本一样顺手。2. 技术选型与架构设计为什么是Java Selenium Appium在开始敲代码之前我们先得把“为什么是它们三个”这个问题聊透。技术选型不是拍脑袋每一个选择背后都是对需求、团队技能和长期维护成本的权衡。2.1 核心组件角色解析首先我们把这三个技术拆开看理解它们各自扮演的角色Java 稳固的“地基”与“粘合剂”生态成熟在测试领域特别是大型项目和企业级应用中Java因其严格的类型检查、丰富的测试库JUnit, TestNG和强大的构建工具Maven, Gradle而备受青睐。这意味着你可以轻松管理依赖、组织测试套件、生成报告并与CI/CD工具如Jenkins无缝集成。团队技能复用如果你的开发团队和后端服务主要使用Java那么测试团队使用Java可以降低沟通成本甚至能更好地理解业务代码结构。长期可维护性Java代码的结构清晰面向对象特性便于构建如Page Object ModelPOM这样的设计模式这对于需要长期维护和多人协作的自动化项目至关重要。Selenium WebDriver 统一的“遥控器”协议这里有个关键点我们项目中提到的Selenium主要指的是其核心的WebDriver协议而不是Selenium IDE那个录制工具。WebDriver定义了一套与浏览器交互的标准化RESTful API如点击、输入、获取元素等。Appium的伟大之处在于它完全遵循并扩展了WebDriver协议即所谓的JSON Wire Protocol。这意味着如果你会用Selenium WebDriver的Java客户端库如selenium-java去操作浏览器那么你几乎可以用同一套API去操作手机App。你的WebDriver driver对象在Web测试中指向ChromeDriver在App测试中则指向AppiumDriver但调用的方法如driver.findElement()、driver.click()是完全一致的。这种协议层面的统一极大地降低了学习成本。Appium 跨平台的“设备驱动引擎”Appium在这里扮演的是“翻译官”和“执行者”的角色。它是一个用Node.js编写的HTTP服务器接收来自Java客户端遵循WebDriver协议的请求。然后Appium会根据你的配置调用不同平台底层的自动化框架来真正执行命令。例如在Android上它通常使用Google官方提供的UiAutomator2在iOS上则使用苹果的XCUITest。Appium本身不执行任何测试逻辑它只是提供了一个标准化的接口让你能用同一种语言WebDriver协议去指挥不同平台的原生测试工具。它的“跨平台”特性允许你用同一套测试脚本通过不同的Capability配置来测试Android和iOS应用这对于需要双端覆盖的产品来说价值巨大。2.2 架构设计思路从脚本到框架理解了组件我们来看如何把它们组装起来。一个可维护的自动化项目绝不能是一堆散乱的脚本。我采用的是一种分层架构这也是业界普遍认可的最佳实践。[测试脚本层] (Test Cases) | | 调用页面对象组织业务流程 | [页面对象层] (Page Objects) | | 封装元素定位与基础操作 | [驱动工具层] (Driver Utils) | | 初始化AppiumDriver管理会话 | [Appium Server] [真机/模拟器]驱动工具层这是最底层负责AppiumDriver的初始化和生命周期管理。它会读取配置文件如设备UDID、App包名、Activity名、Appium服务器地址等创建对应的AndroidDriver或IOSDriver实例并提供获取Driver、退出Driver等方法。这里会处理很多棘手的细节比如等待App启动、处理安装弹窗、设置隐式等待时间等。页面对象层这是核心体现了“模拟用户操作”的思想。每一个App的页面如登录页、首页、设置页都对应一个Java类。这个类中不包含具体的测试逻辑只做两件事1) 定义该页面上所有需要操作的元素如输入框、按钮的定位方式2) 提供操作这些元素的方法如inputUsername(String name),clickLoginButton()。这样做的好处是当App UI发生变化时你只需要修改对应的Page Object类所有用到该页面的测试脚本都不会受影响。测试脚本层这是顶层利用JUnit或TestNG等测试框架调用不同的Page Object方法组合成完整的测试流程如“登录-搜索-下单”。这里关注的是测试用例本身、断言验证以及测试数据的管理。实操心得在项目初期不要急于写复杂的测试用例。花时间把驱动工具层和第一个Page Object类搭建稳固后面新增页面和用例会像搭积木一样快。我建议先从App的一个核心流程比如登录开始走通整个链路验证架构的可行性。3. 环境搭建与核心配置实战理论说再多不如动手搭一遍。环境搭建是劝退新手的第一个门槛但只要按步骤来其实都是体力活。这里我以Windows/Mac Android真机为例给出最清晰的路径。3.1 基础环境准备Java开发环境确保安装JDK 8或以上版本推荐JDK 11或17这些LTS版本并配置好JAVA_HOME和PATH环境变量。用java -version验证。Android开发环境安装Android Studio不是为了写代码而是为了获取Android SDK和必不可少的命令行工具。配置环境变量设置ANDROID_HOME指向你的SDK安装路径如C:\Users\YourName\AppData\Local\Android\Sdk并将%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\tools添加到PATH中。安装必要组件通过Android Studio的SDK Manager确保安装了你要测试的Android版本所对应的“Platform Tools”和“Build Tools”。尤其要安装“Android SDK Platform-Tools”它包含了关键的adb命令。Node.js与Appium Server从官网安装Node.js建议LTS版本。Appium 2.x是一个Node.js应用。通过npm全局安装Appium打开终端运行npm install -g appium。安装完成后运行appium -v检查版本。Appium 2.x重要步骤安装驱动Appium 2.x采用了插件化架构核心服务器不包含任何平台驱动需要手动安装。对于Android运行appium driver install uiautomator2。对于iOS运行appium driver install xcuitest。可以通过appium driver list查看已安装的驱动。3.2 连接真机与必备工具连接Android手机开启手机的“开发者选项”通常是在关于手机中连续点击版本号。在开发者选项中开启“USB调试”。用USB线连接电脑和手机手机上可能会弹出“允许USB调试吗”的授权框选择“允许”。在电脑终端运行adb devices。如果看到设备列表中出现你的设备序列号且状态为device则表示连接成功。如果显示unauthorized检查手机上的授权提示。安装Appium Inspector这是Appium官方的元素定位工具相当于Web自动化中的“浏览器开发者工具”。你可以从Appium官网下载桌面版或者通过npm安装npm install -g appium-inspector。它是我们编写脚本时查看元素属性如resource-id, xpath, class的必备神器。3.3 创建Maven项目与依赖配置在IDE如IntelliJ IDEA中创建一个新的Maven项目。在pom.xml中添加关键依赖dependencies !-- Selenium Java Client (WebDriver协议客户端) -- dependency groupIdorg.seleniumhq.selenium/groupId artifactIdselenium-java/artifactId version4.15.0/version !-- 使用较新版本 -- /dependency !-- Appium Java Client (扩展了Selenium支持移动端特有功能) -- dependency groupIdio.appium/groupId artifactIdjava-client/artifactId version8.6.0/version /dependency !-- 测试框架这里以TestNG为例 -- dependency groupIdorg.testng/groupId artifactIdtestng/artifactId version7.8.0/version scopetest/scope /dependency !-- 日志框架便于排查问题 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version /dependency /dependencies注意事项selenium-java和java-client的版本需要匹配否则可能会出现奇怪的兼容性问题。建议去Maven仓库查看它们的最新稳定版搭配。我上面列出的版本是经过验证可稳定工作的组合。3.4 编写第一个启动脚本理解Desired Capabilities一切就绪我们来写一段最简单的代码目标是在真机上启动一个App这里以系统自带的“计算器”为例包名通常是com.android.calculator2。首先启动Appium Server。在终端直接运行appium看到[Appium] Welcome to Appium v2.x.x和[Appium] Appium REST http interface listener started on 0.0.0.0:4723就表示服务启动成功默认监听4723端口。然后创建你的第一个测试类import io.appium.java_client.android.AndroidDriver; import org.openqa.selenium.remote.DesiredCapabilities; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; public class FirstAppiumTest { private AndroidDriver driver; BeforeTest public void setUp() throws MalformedURLException { // 1. 设置Desired Capabilities这是告诉Appium你要测试什么、怎么测试的核心配置 DesiredCapabilities caps new DesiredCapabilities(); caps.setCapability(platformName, Android); // 平台 caps.setCapability(platformVersion, 13); // 手机系统版本根据你手机情况修改 caps.setCapability(deviceName, Your_Device_Name); // 设备名adb devices查到的名字 caps.setCapability(automationName, UiAutomator2); // Android默认引擎 caps.setCapability(appPackage, com.android.calculator2); // 要测试的App包名 caps.setCapability(appActivity, com.android.calculator2.Calculator); // 要启动的Activity名 // 2. 初始化AndroidDriver连接到本地的Appium Server URL appiumServerUrl new URL(http://127.0.0.1:4723); driver new AndroidDriver(appiumServerUrl, caps); // 3. 设置一个全局的隐式等待让脚本在查找元素时有一定耐心 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); } Test public void testLaunchApp() { // 这里可以写你的测试逻辑 System.out.println(App launched successfully!); // 例如你可以尝试定位计算器上的数字按钮并点击 // WebElement digit9 driver.findElement(By.id(com.android.calculator2:id/digit_9)); // digit9.click(); } AfterTest public void tearDown() { if (driver ! null) { driver.quit(); // 关闭会话退出App } } }关键点解析Desired Capabilities这是Appium脚本的“灵魂”它是一组键值对用于告知Appium Server本次测试的期望配置。platformName/platformVersion/deviceName标识目标设备。automationName指定使用哪个驱动引擎Android上首选UiAutomator2。appPackage和appActivity这组配置用于启动已安装在设备上的App。appActivity可以理解为App的某个具体界面。如何获取它们一个简单的方法是在手机打开目标App后在终端运行adb shell dumpsys window | findstr mCurrentFocus(Windows) 或adb shell dumpsys window | grep mCurrentFocus(Mac/Linux)。另一种情况安装并启动新APK如果你有一个未安装的.apk文件可以使用caps.setCapability(app, /path/to/your/app.apk);Appium会自动安装并启动它。运行这个测试如果一切正常你会看到手机上的计算器App被自动启动。恭喜你你已经成功打通了Java到Appium再到真机的整个链路4. 元素定位与交互将Selenium经验“平移”过来一旦App启动接下来的核心就是“找到元素并操作它”。如果你熟悉Selenium的八种定位方式id, name, class, xpath, css等那么恭喜你几乎已经掌握了Appium定位的80%。Appium支持类似的定位策略只是有些属性名在移动端有所不同。4.1 使用Appium Inspector获取元素属性在编写定位代码前你必须先知道元素的属性。这就是Appium Inspector的用武之地。启动Appium Server。打开Appium Inspector。在Inspector中填入和你的脚本中完全相同的Desired Capabilities注意这里需要额外加一个appium:options的包装并且platformName等需要加上appium:前缀具体格式参考Inspector界面示例。点击“Start Session”。Inspector会启动你指定的App并加载出UI树和实时截图。点击截图上的元素右侧就会显示该元素的所有可用属性。4.2 主要定位策略与代码示例假设我们要定位计算器上的数字“9”按钮。通过Inspector我们可能看到它的resource-id是com.android.calculator2:id/digit_9。在Java代码中我们可以这样定位并点击它import org.openqa.selenium.By; Test public void testClickDigit() { // 方式1通过resource-id定位 (最稳定、首选) // Android的resource-id对应Selenium中的By.id WebElement digit9 driver.findElement(By.id(com.android.calculator2:id/digit_9)); digit9.click(); // 方式2通过accessibility id定位 (对于iOS的accessibilityIdentifier或Android的content-desc) // 如果元素有content-desc属性可以用这个。它在跨平台时很有用。 // WebElement element driver.findElement(By.accessibilityId(一些描述)); // 方式3通过XPath定位 (功能强大但可能性能稍差且易受UI改动影响) // 当id、content-desc都没有时使用。Inspector可以帮你生成XPath。 // WebElement digit9 driver.findElement(By.xpath(//android.widget.Button[text9])); // 方式4通过UIAutomator2的定位器 (Android特有非常灵活) // 可以使用UiSelector语法进行复杂查找如文本、类名组合 // WebElement digit9 driver.findElement(AppiumBy.androidUIAutomator( // new UiSelector().text(\9\).className(\android.widget.Button\))); System.out.println(Clicked digit 9.); }定位策略选择优先级建议首选resource-id(By.id)唯一性最好定位最稳定、速度最快。鼓励开发同学为关键元素添加唯一的id。其次accessibility id(By.accessibilityId)对应Android的content-desc或iOS的accessibilityIdentifier也具有较好的语义和一定的唯一性。再次XPath或UIAutomator2当上述属性缺失或不够用时使用。XPath更通用但表达式可能冗长且易变UIAutomator2语法更贴合Android原生功能强大。尽量避免使用By.className或By.tagName在移动端同类元素太多如一堆android.widget.TextView单独使用几乎无法精确定位通常需要与其他条件结合。4.3 常用交互操作定位到元素后操作就很简单了和Selenium几乎一模一样// 点击 element.click(); // 输入文本通常用于输入框 WebElement inputField driver.findElement(By.id(some.input.field)); inputField.clear(); // 先清空 inputField.sendKeys(Hello Appium); // 获取元素文本 String text element.getText(); System.out.println(The text is: text); // 判断元素是否显示、可用、被选中 boolean isDisplayed element.isDisplayed(); boolean isEnabled element.isEnabled(); boolean isSelected element.isSelected(); // 用于复选框、单选框实操心得移动端操作有一个非常重要的点——等待。由于网络、性能等原因元素加载可能比Web更慢。除了全局的隐式等待一定要善用显式等待WebDriverWait在关键操作前等待某个条件成立如元素可点击、元素出现。这能极大提高脚本的稳定性。WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(15)); WebElement someButton wait.until(ExpectedConditions.elementToBeClickable(By.id(some.button))); someButton.click();5. 构建可维护的自动化框架Page Object Model实战写几个简单的测试方法不难难的是当你有几十上百个测试用例时如何管理。直接在每个用例里写findElement和click会导致代码极度冗余UI一变就要改无数个地方。这时就必须引入Page Object Model。5.1 Page Object Model设计POM的核心思想是“将页面封装成对象”。我们为计算器App创建一个简单的POM示例。1. 创建BasePage基类这个类封装所有页面公用的操作比如查找元素的通用方法、等待方法等。最重要的是它持有AndroidDriver实例。import io.appium.java_client.android.AndroidDriver; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; public class BasePage { protected AndroidDriver driver; protected WebDriverWait wait; public BasePage(AndroidDriver driver) { this.driver driver; this.wait new WebDriverWait(driver, Duration.ofSeconds(15)); // 使用PageFactory初始化元素可以配合FindBy注解使用懒加载元素 PageFactory.initElements(driver, this); } // 可以在这里封装一些公共方法比如通用的等待、滑动等 protected void waitForElementToBeClickable(By locator) { wait.until(ExpectedConditions.elementToBeClickable(locator)); } }2. 创建具体页面类CalculatorPage这个类代表计算器的主界面继承自BasePage。它定义了这个页面上的元素和操作。import io.appium.java_client.pagefactory.AndroidFindBy; import io.appium.java_client.pagefactory.AppiumFieldDecorator; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.PageFactory; public class CalculatorPage extends BasePage { // 使用AndroidFindBy注解来定位元素PageFactory会自动初始化它们 AndroidFindBy(id com.android.calculator2:id/digit_9) private WebElement digit9Btn; AndroidFindBy(id com.android.calculator2:id/digit_5) private WebElement digit5Btn; AndroidFindBy(id com.android.calculator2:id/op_add) private WebElement plusBtn; AndroidFindBy(id com.android.calculator2:id/eq) private WebElement equalsBtn; AndroidFindBy(id com.android.calculator2:id/result) private WebElement resultField; // 构造函数必须调用父类构造并用AppiumFieldDecorator再次初始化针对移动端 public CalculatorPage(AndroidDriver driver) { super(driver); PageFactory.initElements(new AppiumFieldDecorator(driver), this); } // 页面操作方法模拟用户点击9 public void clickDigit9() { digit9Btn.click(); } public void clickDigit5() { digit5Btn.click(); } public void clickPlus() { plusBtn.click(); } public void clickEquals() { equalsBtn.click(); } // 业务逻辑方法执行一个完整的加法计算 9 5 public String performAddition() { clickDigit9(); clickPlus(); clickDigit5(); clickEquals(); return getResult(); // 返回结果 } // 获取结果 public String getResult() { return resultField.getText(); } }3. 在测试类中使用Page Object现在我们的测试脚本变得非常清晰和易读。public class CalculatorTest { private AndroidDriver driver; private CalculatorPage calculatorPage; BeforeTest public void setUp() throws MalformedURLException { // ... 初始化driver的代码同上 ... driver new AndroidDriver(new URL(http://127.0.0.1:4723), caps); // 初始化页面对象 calculatorPage new CalculatorPage(driver); } Test public void testAddition() { // 直接调用页面对象的业务方法 String result calculatorPage.performAddition(); // 断言验证 Assert.assertEquals(result, 14, 9 5 should equal 14); System.out.println(Test passed! Result is: result); } AfterTest public void tearDown() { if (driver ! null) { driver.quit(); } } }5.2 框架扩展思考一个完整的自动化框架远不止POM你还需要考虑测试数据管理将测试数据如用户名、密码从脚本中分离存储在JSON、YAML或Excel文件中。配置文件管理将设备信息、Appium服务器地址、Capabilities等配置信息外置到.properties或.yaml文件便于不同环境切换如测试环境、预发布环境。测试报告集成Allure或ExtentReports等报告框架生成美观详细的测试报告包含截图、日志。失败重试与截图在TestNG的AfterMethod中判断测试是否失败如果失败则自动截屏并保存便于后续排查。并行测试利用TestNG或JUnit 5的并行特性配合Appium Grid实现在多台设备上同时运行测试大幅提升效率。6. 常见问题与排查技巧实录在实际操作中你一定会遇到各种各样的问题。下面是我踩过的一些坑和对应的解决方案希望能帮你节省时间。6.1 环境与连接问题问题1adb devices找不到设备或显示unauthorized。排查检查USB线是否完好、USB调试是否开启、电脑是否安装了手机驱动Windows常见问题。对于unauthorized检查手机屏幕上的授权提示。技巧可以尝试重启adb服务adb kill-server然后adb start-server。使用adb devices -l查看更详细信息。问题2Appium Server启动失败端口被占用。排查默认4723端口可能被其他进程占用。运行netstat -ano | findstr 4723(Windows) 或lsof -i :4723(Mac/Linux) 查找并终止占用进程。技巧启动Appium时可以指定其他端口appium -p 4724。问题3脚本报错org.openqa.selenium.SessionNotCreatedException: Could not start a new session...排查这是最常见的错误原因很多。Desired Capabilities错误仔细检查每个键值对特别是appPackage和appActivity是否准确platformVersion是否与手机系统匹配。Appium驱动未安装Appium 2.x下确保已运行appium driver install uiautomator2。设备未连接再次确认adb devices中有设备。App未安装或Activity名错误如果是启动已安装App确认App确实已安装。Activity名可以通过adb shell dumpsys window | grep mCurrentFocus在App启动后获取。技巧一定要看Appium Server的日志错误信息在终端输出的日志里通常非常详细会明确指出是哪个Capability有问题或者底层adb命令执行失败的原因。6.2 脚本执行问题问题4元素找不到NoSuchElementException。排查等待不足这是最常见原因。元素还没加载出来脚本就去找了。增加隐式等待时间或在关键操作前使用显式等待。定位器错误UI可能更新了用Appium Inspector重新检查元素属性。XPath尤其容易因UI微调而失效。页面内有WebView或混合内容如果元素在WebView内需要先切换上下文Context。使用driver.getContextHandles()获取所有上下文然后driver.context(“WEBVIEW_com.xxx”)切换到WebView上下文再用Selenium的方式定位元素。屏幕上有弹窗权限申请、升级提示弹窗遮挡了目标元素。需要在操作前处理掉弹窗。可以写一个通用的“处理弹窗”方法在每次操作前尝试查找并关闭常见弹窗。技巧在findElement失败时让脚本自动截屏能直观看到当时的界面状态极大方便排查。问题5脚本在模拟器上运行正常在真机上失败。排查真机性能、网络环境、系统定制化不同厂商ROM都可能产生影响。性能差异真机可能更慢需要增加等待时间。分辨率与缩放定位器如果使用了坐标或绝对位置的XPath在不同分辨率设备上会失效。务必使用与分辨率无关的属性定位如resource-id。系统弹窗差异不同厂商的权限申请窗口样式不同你的“关闭弹窗”逻辑可能需要适配。问题6如何测试需要登录的App每次测试都要手动登录吗方案复用登录状态首次登录成功后使用driver.pushFile将App的登录缓存文件如SharedPreferences保存到电脑。在后续测试开始前先卸载重装App再用driver.pushFile将缓存文件推回设备特定目录。这需要了解App的数据存储机制。使用noReset和fullResetCapabilitynoReset: true会话结束后不重置App状态不清除数据。适合连续执行多个需要保持登录状态的测试。fullReset: true每次会话都重新安装App。适合需要干净环境的测试。注意noReset有时会导致App状态异常需要根据实际情况选择。通过API登录如果App后端提供了登录接口最优雅的方式是在测试开始前用HTTP客户端如OkHttp调用登录接口获取token然后通过driver.executeScript(“mobile: shell”, …)等命令将token写入App或直接使用driver.setSetting设置如果App支持。6.3 性能与稳定性优化使用UIAutomator2而不是旧的UIAutomator1在Capabilities中明确指定automationName: UiAutomator2它更稳定功能更强。减少不必要的截图截图操作比较耗时只在失败或关键步骤时进行。合理使用等待滥用Thread.sleep()是性能杀手。多用显式等待它只在超时时间内定期检查条件满足立即返回。关闭不必要的动画在开发者选项中关闭“窗口动画缩放”、“过渡动画缩放”、“动画程序时长缩放”可以加快UI响应速度使脚本运行更稳定。定期清理长时间运行大量测试后模拟器或手机可能会变卡。定期重启设备或模拟器。走到这里你已经掌握了用JavaSeleniumAppium搭建移动自动化测试框架的核心技能。从环境搭建、元素定位到框架设计这套方法论的核心在于将Web自动化的工程化思想复用到移动端。最大的收获可能不是学会了某个API而是理解了如何通过分层、封装和配置化让自动化脚本变得易于编写、维护和扩展。在实际项目中你会遇到更多特定场景比如手势操作滑动、长按、混合应用测试、iOS与Android的差异处理等但有了这个坚实的基础那些都是可以按图索骥、逐个攻破的具体技术点了。记住多看官方文档多读Appium Server的日志那里面藏着解决问题的钥匙。
Java+Selenium+Appium移动端自动化测试:从Web思维到App实战
1. 项目概述当Selenium遇上Appium桌面Web自动化思维如何“降维打击”移动端如果你和我一样是从Web自动化测试比如用Selenium入行的第一次接触移动端App自动化时大概率会有点懵。设备怎么连元素怎么抓模拟器怎么搞一堆新名词扑面而来。但当我真正开始用Java Selenium Appium这套组合拳来做App自动化时我发现了一个有趣的现象很多在Web端玩得滚瓜烂熟的理念和技巧在移动端竟然能无缝“平移”甚至因为移动端场景更聚焦实现起来反而更简单。这个项目就是一次将成熟的Web自动化工程化思维系统性地应用到移动App测试中的实战记录。简单来说这个项目的核心目标就是利用Java语言的稳定生态、Selenium WebDriver的标准化协议思想以及Appium对移动设备的驱动能力构建一套能够模拟真实用户操作App的自动化测试框架。它解决的痛点非常明确手工重复测试App的回归用例耗时耗力、不同机型/系统版本兼容性测试成本高、以及UI交互逻辑复杂导致测试覆盖不全。无论是测试原生Android/iOS应用、混合应用Hybrid App还是内嵌的WebView页面这套组合都能提供统一的编程接口。适合谁来参考如果你是正在从Web测试转向移动端测试的工程师或者你的团队需要为App建立基础的自动化回归能力但又不想被某个商业工具绑定那么这篇从环境搭建、脚本编写到框架设计的全程实录应该能给你提供一条清晰的路径。我会重点分享如何用你熟悉的Java和Selenium WebDriver API去理解和驾驭Appium把“模拟App”这件事变得像写Web测试脚本一样顺手。2. 技术选型与架构设计为什么是Java Selenium Appium在开始敲代码之前我们先得把“为什么是它们三个”这个问题聊透。技术选型不是拍脑袋每一个选择背后都是对需求、团队技能和长期维护成本的权衡。2.1 核心组件角色解析首先我们把这三个技术拆开看理解它们各自扮演的角色Java 稳固的“地基”与“粘合剂”生态成熟在测试领域特别是大型项目和企业级应用中Java因其严格的类型检查、丰富的测试库JUnit, TestNG和强大的构建工具Maven, Gradle而备受青睐。这意味着你可以轻松管理依赖、组织测试套件、生成报告并与CI/CD工具如Jenkins无缝集成。团队技能复用如果你的开发团队和后端服务主要使用Java那么测试团队使用Java可以降低沟通成本甚至能更好地理解业务代码结构。长期可维护性Java代码的结构清晰面向对象特性便于构建如Page Object ModelPOM这样的设计模式这对于需要长期维护和多人协作的自动化项目至关重要。Selenium WebDriver 统一的“遥控器”协议这里有个关键点我们项目中提到的Selenium主要指的是其核心的WebDriver协议而不是Selenium IDE那个录制工具。WebDriver定义了一套与浏览器交互的标准化RESTful API如点击、输入、获取元素等。Appium的伟大之处在于它完全遵循并扩展了WebDriver协议即所谓的JSON Wire Protocol。这意味着如果你会用Selenium WebDriver的Java客户端库如selenium-java去操作浏览器那么你几乎可以用同一套API去操作手机App。你的WebDriver driver对象在Web测试中指向ChromeDriver在App测试中则指向AppiumDriver但调用的方法如driver.findElement()、driver.click()是完全一致的。这种协议层面的统一极大地降低了学习成本。Appium 跨平台的“设备驱动引擎”Appium在这里扮演的是“翻译官”和“执行者”的角色。它是一个用Node.js编写的HTTP服务器接收来自Java客户端遵循WebDriver协议的请求。然后Appium会根据你的配置调用不同平台底层的自动化框架来真正执行命令。例如在Android上它通常使用Google官方提供的UiAutomator2在iOS上则使用苹果的XCUITest。Appium本身不执行任何测试逻辑它只是提供了一个标准化的接口让你能用同一种语言WebDriver协议去指挥不同平台的原生测试工具。它的“跨平台”特性允许你用同一套测试脚本通过不同的Capability配置来测试Android和iOS应用这对于需要双端覆盖的产品来说价值巨大。2.2 架构设计思路从脚本到框架理解了组件我们来看如何把它们组装起来。一个可维护的自动化项目绝不能是一堆散乱的脚本。我采用的是一种分层架构这也是业界普遍认可的最佳实践。[测试脚本层] (Test Cases) | | 调用页面对象组织业务流程 | [页面对象层] (Page Objects) | | 封装元素定位与基础操作 | [驱动工具层] (Driver Utils) | | 初始化AppiumDriver管理会话 | [Appium Server] [真机/模拟器]驱动工具层这是最底层负责AppiumDriver的初始化和生命周期管理。它会读取配置文件如设备UDID、App包名、Activity名、Appium服务器地址等创建对应的AndroidDriver或IOSDriver实例并提供获取Driver、退出Driver等方法。这里会处理很多棘手的细节比如等待App启动、处理安装弹窗、设置隐式等待时间等。页面对象层这是核心体现了“模拟用户操作”的思想。每一个App的页面如登录页、首页、设置页都对应一个Java类。这个类中不包含具体的测试逻辑只做两件事1) 定义该页面上所有需要操作的元素如输入框、按钮的定位方式2) 提供操作这些元素的方法如inputUsername(String name),clickLoginButton()。这样做的好处是当App UI发生变化时你只需要修改对应的Page Object类所有用到该页面的测试脚本都不会受影响。测试脚本层这是顶层利用JUnit或TestNG等测试框架调用不同的Page Object方法组合成完整的测试流程如“登录-搜索-下单”。这里关注的是测试用例本身、断言验证以及测试数据的管理。实操心得在项目初期不要急于写复杂的测试用例。花时间把驱动工具层和第一个Page Object类搭建稳固后面新增页面和用例会像搭积木一样快。我建议先从App的一个核心流程比如登录开始走通整个链路验证架构的可行性。3. 环境搭建与核心配置实战理论说再多不如动手搭一遍。环境搭建是劝退新手的第一个门槛但只要按步骤来其实都是体力活。这里我以Windows/Mac Android真机为例给出最清晰的路径。3.1 基础环境准备Java开发环境确保安装JDK 8或以上版本推荐JDK 11或17这些LTS版本并配置好JAVA_HOME和PATH环境变量。用java -version验证。Android开发环境安装Android Studio不是为了写代码而是为了获取Android SDK和必不可少的命令行工具。配置环境变量设置ANDROID_HOME指向你的SDK安装路径如C:\Users\YourName\AppData\Local\Android\Sdk并将%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\tools添加到PATH中。安装必要组件通过Android Studio的SDK Manager确保安装了你要测试的Android版本所对应的“Platform Tools”和“Build Tools”。尤其要安装“Android SDK Platform-Tools”它包含了关键的adb命令。Node.js与Appium Server从官网安装Node.js建议LTS版本。Appium 2.x是一个Node.js应用。通过npm全局安装Appium打开终端运行npm install -g appium。安装完成后运行appium -v检查版本。Appium 2.x重要步骤安装驱动Appium 2.x采用了插件化架构核心服务器不包含任何平台驱动需要手动安装。对于Android运行appium driver install uiautomator2。对于iOS运行appium driver install xcuitest。可以通过appium driver list查看已安装的驱动。3.2 连接真机与必备工具连接Android手机开启手机的“开发者选项”通常是在关于手机中连续点击版本号。在开发者选项中开启“USB调试”。用USB线连接电脑和手机手机上可能会弹出“允许USB调试吗”的授权框选择“允许”。在电脑终端运行adb devices。如果看到设备列表中出现你的设备序列号且状态为device则表示连接成功。如果显示unauthorized检查手机上的授权提示。安装Appium Inspector这是Appium官方的元素定位工具相当于Web自动化中的“浏览器开发者工具”。你可以从Appium官网下载桌面版或者通过npm安装npm install -g appium-inspector。它是我们编写脚本时查看元素属性如resource-id, xpath, class的必备神器。3.3 创建Maven项目与依赖配置在IDE如IntelliJ IDEA中创建一个新的Maven项目。在pom.xml中添加关键依赖dependencies !-- Selenium Java Client (WebDriver协议客户端) -- dependency groupIdorg.seleniumhq.selenium/groupId artifactIdselenium-java/artifactId version4.15.0/version !-- 使用较新版本 -- /dependency !-- Appium Java Client (扩展了Selenium支持移动端特有功能) -- dependency groupIdio.appium/groupId artifactIdjava-client/artifactId version8.6.0/version /dependency !-- 测试框架这里以TestNG为例 -- dependency groupIdorg.testng/groupId artifactIdtestng/artifactId version7.8.0/version scopetest/scope /dependency !-- 日志框架便于排查问题 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version /dependency /dependencies注意事项selenium-java和java-client的版本需要匹配否则可能会出现奇怪的兼容性问题。建议去Maven仓库查看它们的最新稳定版搭配。我上面列出的版本是经过验证可稳定工作的组合。3.4 编写第一个启动脚本理解Desired Capabilities一切就绪我们来写一段最简单的代码目标是在真机上启动一个App这里以系统自带的“计算器”为例包名通常是com.android.calculator2。首先启动Appium Server。在终端直接运行appium看到[Appium] Welcome to Appium v2.x.x和[Appium] Appium REST http interface listener started on 0.0.0.0:4723就表示服务启动成功默认监听4723端口。然后创建你的第一个测试类import io.appium.java_client.android.AndroidDriver; import org.openqa.selenium.remote.DesiredCapabilities; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; public class FirstAppiumTest { private AndroidDriver driver; BeforeTest public void setUp() throws MalformedURLException { // 1. 设置Desired Capabilities这是告诉Appium你要测试什么、怎么测试的核心配置 DesiredCapabilities caps new DesiredCapabilities(); caps.setCapability(platformName, Android); // 平台 caps.setCapability(platformVersion, 13); // 手机系统版本根据你手机情况修改 caps.setCapability(deviceName, Your_Device_Name); // 设备名adb devices查到的名字 caps.setCapability(automationName, UiAutomator2); // Android默认引擎 caps.setCapability(appPackage, com.android.calculator2); // 要测试的App包名 caps.setCapability(appActivity, com.android.calculator2.Calculator); // 要启动的Activity名 // 2. 初始化AndroidDriver连接到本地的Appium Server URL appiumServerUrl new URL(http://127.0.0.1:4723); driver new AndroidDriver(appiumServerUrl, caps); // 3. 设置一个全局的隐式等待让脚本在查找元素时有一定耐心 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); } Test public void testLaunchApp() { // 这里可以写你的测试逻辑 System.out.println(App launched successfully!); // 例如你可以尝试定位计算器上的数字按钮并点击 // WebElement digit9 driver.findElement(By.id(com.android.calculator2:id/digit_9)); // digit9.click(); } AfterTest public void tearDown() { if (driver ! null) { driver.quit(); // 关闭会话退出App } } }关键点解析Desired Capabilities这是Appium脚本的“灵魂”它是一组键值对用于告知Appium Server本次测试的期望配置。platformName/platformVersion/deviceName标识目标设备。automationName指定使用哪个驱动引擎Android上首选UiAutomator2。appPackage和appActivity这组配置用于启动已安装在设备上的App。appActivity可以理解为App的某个具体界面。如何获取它们一个简单的方法是在手机打开目标App后在终端运行adb shell dumpsys window | findstr mCurrentFocus(Windows) 或adb shell dumpsys window | grep mCurrentFocus(Mac/Linux)。另一种情况安装并启动新APK如果你有一个未安装的.apk文件可以使用caps.setCapability(app, /path/to/your/app.apk);Appium会自动安装并启动它。运行这个测试如果一切正常你会看到手机上的计算器App被自动启动。恭喜你你已经成功打通了Java到Appium再到真机的整个链路4. 元素定位与交互将Selenium经验“平移”过来一旦App启动接下来的核心就是“找到元素并操作它”。如果你熟悉Selenium的八种定位方式id, name, class, xpath, css等那么恭喜你几乎已经掌握了Appium定位的80%。Appium支持类似的定位策略只是有些属性名在移动端有所不同。4.1 使用Appium Inspector获取元素属性在编写定位代码前你必须先知道元素的属性。这就是Appium Inspector的用武之地。启动Appium Server。打开Appium Inspector。在Inspector中填入和你的脚本中完全相同的Desired Capabilities注意这里需要额外加一个appium:options的包装并且platformName等需要加上appium:前缀具体格式参考Inspector界面示例。点击“Start Session”。Inspector会启动你指定的App并加载出UI树和实时截图。点击截图上的元素右侧就会显示该元素的所有可用属性。4.2 主要定位策略与代码示例假设我们要定位计算器上的数字“9”按钮。通过Inspector我们可能看到它的resource-id是com.android.calculator2:id/digit_9。在Java代码中我们可以这样定位并点击它import org.openqa.selenium.By; Test public void testClickDigit() { // 方式1通过resource-id定位 (最稳定、首选) // Android的resource-id对应Selenium中的By.id WebElement digit9 driver.findElement(By.id(com.android.calculator2:id/digit_9)); digit9.click(); // 方式2通过accessibility id定位 (对于iOS的accessibilityIdentifier或Android的content-desc) // 如果元素有content-desc属性可以用这个。它在跨平台时很有用。 // WebElement element driver.findElement(By.accessibilityId(一些描述)); // 方式3通过XPath定位 (功能强大但可能性能稍差且易受UI改动影响) // 当id、content-desc都没有时使用。Inspector可以帮你生成XPath。 // WebElement digit9 driver.findElement(By.xpath(//android.widget.Button[text9])); // 方式4通过UIAutomator2的定位器 (Android特有非常灵活) // 可以使用UiSelector语法进行复杂查找如文本、类名组合 // WebElement digit9 driver.findElement(AppiumBy.androidUIAutomator( // new UiSelector().text(\9\).className(\android.widget.Button\))); System.out.println(Clicked digit 9.); }定位策略选择优先级建议首选resource-id(By.id)唯一性最好定位最稳定、速度最快。鼓励开发同学为关键元素添加唯一的id。其次accessibility id(By.accessibilityId)对应Android的content-desc或iOS的accessibilityIdentifier也具有较好的语义和一定的唯一性。再次XPath或UIAutomator2当上述属性缺失或不够用时使用。XPath更通用但表达式可能冗长且易变UIAutomator2语法更贴合Android原生功能强大。尽量避免使用By.className或By.tagName在移动端同类元素太多如一堆android.widget.TextView单独使用几乎无法精确定位通常需要与其他条件结合。4.3 常用交互操作定位到元素后操作就很简单了和Selenium几乎一模一样// 点击 element.click(); // 输入文本通常用于输入框 WebElement inputField driver.findElement(By.id(some.input.field)); inputField.clear(); // 先清空 inputField.sendKeys(Hello Appium); // 获取元素文本 String text element.getText(); System.out.println(The text is: text); // 判断元素是否显示、可用、被选中 boolean isDisplayed element.isDisplayed(); boolean isEnabled element.isEnabled(); boolean isSelected element.isSelected(); // 用于复选框、单选框实操心得移动端操作有一个非常重要的点——等待。由于网络、性能等原因元素加载可能比Web更慢。除了全局的隐式等待一定要善用显式等待WebDriverWait在关键操作前等待某个条件成立如元素可点击、元素出现。这能极大提高脚本的稳定性。WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(15)); WebElement someButton wait.until(ExpectedConditions.elementToBeClickable(By.id(some.button))); someButton.click();5. 构建可维护的自动化框架Page Object Model实战写几个简单的测试方法不难难的是当你有几十上百个测试用例时如何管理。直接在每个用例里写findElement和click会导致代码极度冗余UI一变就要改无数个地方。这时就必须引入Page Object Model。5.1 Page Object Model设计POM的核心思想是“将页面封装成对象”。我们为计算器App创建一个简单的POM示例。1. 创建BasePage基类这个类封装所有页面公用的操作比如查找元素的通用方法、等待方法等。最重要的是它持有AndroidDriver实例。import io.appium.java_client.android.AndroidDriver; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; public class BasePage { protected AndroidDriver driver; protected WebDriverWait wait; public BasePage(AndroidDriver driver) { this.driver driver; this.wait new WebDriverWait(driver, Duration.ofSeconds(15)); // 使用PageFactory初始化元素可以配合FindBy注解使用懒加载元素 PageFactory.initElements(driver, this); } // 可以在这里封装一些公共方法比如通用的等待、滑动等 protected void waitForElementToBeClickable(By locator) { wait.until(ExpectedConditions.elementToBeClickable(locator)); } }2. 创建具体页面类CalculatorPage这个类代表计算器的主界面继承自BasePage。它定义了这个页面上的元素和操作。import io.appium.java_client.pagefactory.AndroidFindBy; import io.appium.java_client.pagefactory.AppiumFieldDecorator; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.PageFactory; public class CalculatorPage extends BasePage { // 使用AndroidFindBy注解来定位元素PageFactory会自动初始化它们 AndroidFindBy(id com.android.calculator2:id/digit_9) private WebElement digit9Btn; AndroidFindBy(id com.android.calculator2:id/digit_5) private WebElement digit5Btn; AndroidFindBy(id com.android.calculator2:id/op_add) private WebElement plusBtn; AndroidFindBy(id com.android.calculator2:id/eq) private WebElement equalsBtn; AndroidFindBy(id com.android.calculator2:id/result) private WebElement resultField; // 构造函数必须调用父类构造并用AppiumFieldDecorator再次初始化针对移动端 public CalculatorPage(AndroidDriver driver) { super(driver); PageFactory.initElements(new AppiumFieldDecorator(driver), this); } // 页面操作方法模拟用户点击9 public void clickDigit9() { digit9Btn.click(); } public void clickDigit5() { digit5Btn.click(); } public void clickPlus() { plusBtn.click(); } public void clickEquals() { equalsBtn.click(); } // 业务逻辑方法执行一个完整的加法计算 9 5 public String performAddition() { clickDigit9(); clickPlus(); clickDigit5(); clickEquals(); return getResult(); // 返回结果 } // 获取结果 public String getResult() { return resultField.getText(); } }3. 在测试类中使用Page Object现在我们的测试脚本变得非常清晰和易读。public class CalculatorTest { private AndroidDriver driver; private CalculatorPage calculatorPage; BeforeTest public void setUp() throws MalformedURLException { // ... 初始化driver的代码同上 ... driver new AndroidDriver(new URL(http://127.0.0.1:4723), caps); // 初始化页面对象 calculatorPage new CalculatorPage(driver); } Test public void testAddition() { // 直接调用页面对象的业务方法 String result calculatorPage.performAddition(); // 断言验证 Assert.assertEquals(result, 14, 9 5 should equal 14); System.out.println(Test passed! Result is: result); } AfterTest public void tearDown() { if (driver ! null) { driver.quit(); } } }5.2 框架扩展思考一个完整的自动化框架远不止POM你还需要考虑测试数据管理将测试数据如用户名、密码从脚本中分离存储在JSON、YAML或Excel文件中。配置文件管理将设备信息、Appium服务器地址、Capabilities等配置信息外置到.properties或.yaml文件便于不同环境切换如测试环境、预发布环境。测试报告集成Allure或ExtentReports等报告框架生成美观详细的测试报告包含截图、日志。失败重试与截图在TestNG的AfterMethod中判断测试是否失败如果失败则自动截屏并保存便于后续排查。并行测试利用TestNG或JUnit 5的并行特性配合Appium Grid实现在多台设备上同时运行测试大幅提升效率。6. 常见问题与排查技巧实录在实际操作中你一定会遇到各种各样的问题。下面是我踩过的一些坑和对应的解决方案希望能帮你节省时间。6.1 环境与连接问题问题1adb devices找不到设备或显示unauthorized。排查检查USB线是否完好、USB调试是否开启、电脑是否安装了手机驱动Windows常见问题。对于unauthorized检查手机屏幕上的授权提示。技巧可以尝试重启adb服务adb kill-server然后adb start-server。使用adb devices -l查看更详细信息。问题2Appium Server启动失败端口被占用。排查默认4723端口可能被其他进程占用。运行netstat -ano | findstr 4723(Windows) 或lsof -i :4723(Mac/Linux) 查找并终止占用进程。技巧启动Appium时可以指定其他端口appium -p 4724。问题3脚本报错org.openqa.selenium.SessionNotCreatedException: Could not start a new session...排查这是最常见的错误原因很多。Desired Capabilities错误仔细检查每个键值对特别是appPackage和appActivity是否准确platformVersion是否与手机系统匹配。Appium驱动未安装Appium 2.x下确保已运行appium driver install uiautomator2。设备未连接再次确认adb devices中有设备。App未安装或Activity名错误如果是启动已安装App确认App确实已安装。Activity名可以通过adb shell dumpsys window | grep mCurrentFocus在App启动后获取。技巧一定要看Appium Server的日志错误信息在终端输出的日志里通常非常详细会明确指出是哪个Capability有问题或者底层adb命令执行失败的原因。6.2 脚本执行问题问题4元素找不到NoSuchElementException。排查等待不足这是最常见原因。元素还没加载出来脚本就去找了。增加隐式等待时间或在关键操作前使用显式等待。定位器错误UI可能更新了用Appium Inspector重新检查元素属性。XPath尤其容易因UI微调而失效。页面内有WebView或混合内容如果元素在WebView内需要先切换上下文Context。使用driver.getContextHandles()获取所有上下文然后driver.context(“WEBVIEW_com.xxx”)切换到WebView上下文再用Selenium的方式定位元素。屏幕上有弹窗权限申请、升级提示弹窗遮挡了目标元素。需要在操作前处理掉弹窗。可以写一个通用的“处理弹窗”方法在每次操作前尝试查找并关闭常见弹窗。技巧在findElement失败时让脚本自动截屏能直观看到当时的界面状态极大方便排查。问题5脚本在模拟器上运行正常在真机上失败。排查真机性能、网络环境、系统定制化不同厂商ROM都可能产生影响。性能差异真机可能更慢需要增加等待时间。分辨率与缩放定位器如果使用了坐标或绝对位置的XPath在不同分辨率设备上会失效。务必使用与分辨率无关的属性定位如resource-id。系统弹窗差异不同厂商的权限申请窗口样式不同你的“关闭弹窗”逻辑可能需要适配。问题6如何测试需要登录的App每次测试都要手动登录吗方案复用登录状态首次登录成功后使用driver.pushFile将App的登录缓存文件如SharedPreferences保存到电脑。在后续测试开始前先卸载重装App再用driver.pushFile将缓存文件推回设备特定目录。这需要了解App的数据存储机制。使用noReset和fullResetCapabilitynoReset: true会话结束后不重置App状态不清除数据。适合连续执行多个需要保持登录状态的测试。fullReset: true每次会话都重新安装App。适合需要干净环境的测试。注意noReset有时会导致App状态异常需要根据实际情况选择。通过API登录如果App后端提供了登录接口最优雅的方式是在测试开始前用HTTP客户端如OkHttp调用登录接口获取token然后通过driver.executeScript(“mobile: shell”, …)等命令将token写入App或直接使用driver.setSetting设置如果App支持。6.3 性能与稳定性优化使用UIAutomator2而不是旧的UIAutomator1在Capabilities中明确指定automationName: UiAutomator2它更稳定功能更强。减少不必要的截图截图操作比较耗时只在失败或关键步骤时进行。合理使用等待滥用Thread.sleep()是性能杀手。多用显式等待它只在超时时间内定期检查条件满足立即返回。关闭不必要的动画在开发者选项中关闭“窗口动画缩放”、“过渡动画缩放”、“动画程序时长缩放”可以加快UI响应速度使脚本运行更稳定。定期清理长时间运行大量测试后模拟器或手机可能会变卡。定期重启设备或模拟器。走到这里你已经掌握了用JavaSeleniumAppium搭建移动自动化测试框架的核心技能。从环境搭建、元素定位到框架设计这套方法论的核心在于将Web自动化的工程化思想复用到移动端。最大的收获可能不是学会了某个API而是理解了如何通过分层、封装和配置化让自动化脚本变得易于编写、维护和扩展。在实际项目中你会遇到更多特定场景比如手势操作滑动、长按、混合应用测试、iOS与Android的差异处理等但有了这个坚实的基础那些都是可以按图索骥、逐个攻破的具体技术点了。记住多看官方文档多读Appium Server的日志那里面藏着解决问题的钥匙。