1. 项目概述为什么我们需要告别手动测试如果你是一名安卓开发者、测试工程师或者只是一个对App功能稳定性有要求的普通用户那么“点点点”这三个字背后可能藏着无数个加班的夜晚和重复到麻木的机械操作。每一次版本迭代哪怕只是改了一个按钮的颜色为了确保核心功能不受影响你可能都需要把几十个甚至上百个测试用例手动执行一遍。这不仅效率低下而且极易出错——人总会疲劳会走神会漏掉某个边界条件。这就是自动化测试的价值所在。它把我们从这些重复、枯燥、低价值的劳动中解放出来让机器去执行那些预设好的、标准化的操作流程。而“Python Appium 网易MuMu模拟器”这套组合正是目前移动端UI自动化测试领域最流行、最易上手的“黄金搭档”。Python提供了简洁强大的脚本语言Appium作为跨平台的移动端自动化框架而网易MuMu模拟器则为我们提供了一个稳定、免费且高性能的安卓测试环境。今天要聊的就是如何用这套工具在5分钟内搭建起一个能真正跑起来的自动化测试环境并写出你的第一个测试脚本。这不仅仅是安装几个软件更是理解一套高效工作流的开始。2. 环境搭建从零到一的5分钟实战很多人觉得环境搭建是拦路虎网上教程五花八门版本不匹配、路径配置错误等问题层出不穷。其实只要理清依赖关系按步骤操作5分钟完全足够。我们的目标是在Windows系统上用MuMu模拟器作为被测设备通过Python驱动Appium完成对模拟器中一个App比如系统自带的“设置”的简单自动化操作。2.1 核心三件套安装与配置环境搭建的核心是理清层级关系Python是“大脑”和“指挥官”Appium是“翻译官”和“调度中心”MuMu模拟器是“机器人身体”。大脑发出指令Python脚本翻译官将其转化为机器人能听懂的语言通过Appium Server并指挥身体MuMu模拟器中的App执行动作。第一步安装Python并配置环境变量这是我们的脚本运行环境。建议直接去Python官网下载最新稳定版如3.8。安装时务必勾选“Add Python to PATH”这是很多新手踩的第一个坑。安装完成后打开命令行CMD或PowerShell输入python --version和pip --version能正确显示版本号即说明安装成功。注意如果没勾选“Add Python to PATH”需要手动添加。在系统环境变量的Path中添加Python的安装路径如C:\Users\你的用户名\AppData\Local\Programs\Python\Python39和其Scripts目录路径如C:\Users\你的用户名\AppData\Local\Programs\Python\Python39\Scripts。第二步安装网易MuMu模拟器并开启调试模式从官网下载安装MuMu模拟器。安装后启动你会看到一个完整的安卓桌面。自动化测试要能控制这个“手机”必须开启“开发者选项”中的“USB调试”模式。在MuMu模拟器中进入“设置” - “关于平板电脑” - 连续点击“版本号”7次激活开发者选项。返回上一级进入新出现的“开发者选项”找到并开启“USB调试”。这一步至关重要它允许外部工具Appium通过ADB安卓调试桥与模拟器通信。第三步安装Appium及其相关依赖Appium是一个C/S架构的工具。我们需要安装两部分Appium Server服务端和Appium Client客户端库。安装Appium Server有两种方式。对于新手强烈推荐使用Appium Desktop它是一个图形化界面包含Server和元素检查器Inspector。从Appium官网下载安装即可。安装后打开直接点击“Start Server”按钮就会启动一个默认运行在http://127.0.0.1:4723的服务。这是我们的“调度中心”。安装Appium Client库这是Python用来和Appium Server通信的“语言包”。在命令行中使用pip安装pip install Appium-Python-Client。安装JDK因为Appium底层依赖Java环境。去Oracle官网或AdoptOpenJDK下载JDK 8或11版本安装后同样需要配置JAVA_HOME环境变量指向JDK安装根目录如C:\Program Files\Java\jdk-11.0.xx并将%JAVA_HOME%\bin添加到Path中。配置Android SDK关键步骤Appium需要通过ADB连接设备。最简单的方式是MuMu模拟器自带了一个ADB但为了避免冲突我们通常使用Android SDK中的ADB。你可以不安装完整的Android Studio只下载“Command line tools”。但更省事的办法是MuMu模拟器的安装目录下如D:\Program Files\MuMu\emulator\nemu\vmonitor\bin就有adb_server.exe。我们可以将这个目录路径D:\Program Files\MuMu\emulator\nemu\vmonitor\bin添加到系统的Path环境变量中。这样在命令行输入adb devices当MuMu模拟器运行时你应该能看到一个设备号例如127.0.0.1:7555 device。这个7555就是MuMu模拟器的默认ADB连接端口。至此基础环境就绪。你可以通过一个快速命令验证启动MuMu模拟器在命令行输入adb devices看到设备列表打开Appium Desktop并启动ServerPython能正常导入appium.webdriver库。这三项都成功5分钟搭建环境的目标基本就达成了。2.2 连接MuMu模拟器的专属技巧连接真机和模拟器在原理上一样但细节有差异。MuMu模拟器的ADB连接端口不是标准的5555而是7555。这就是为什么我们上面要把MuMu自带的ADB加入Path的原因——它已经帮我们做好了端口映射。当你启动MuMu模拟器后在命令行执行adb connect 127.0.0.1:7555 adb devices如果看到127.0.0.1:7555 device的字样恭喜你连接成功。如果显示unauthorized请回到模拟器屏幕查看是否有“允许USB调试”的授权弹窗点击“始终允许”。实操心得有时候adb devices什么都看不到可能是ADB服务冲突。可以尝试adb kill-server然后adb start-server重启服务。如果还不行检查是否开了多个模拟器如雷电、夜神它们的ADB端口会冲突最好先关闭其他模拟器。3. 编写你的第一个自动化测试脚本环境通了接下来就是让代码动起来。我们以打开MuMu模拟器中的“设置”应用并点击“WLAN”菜单项为例。3.1 理解Desired Capabilities脚本的“任务说明书”在编写任何Appium脚本前都必须先配置一个叫Desired Capabilities的字典。你可以把它理解为发给Appium Server的一份“任务说明书”告诉它你要测试哪个设备上的哪个App以及一些必要的配置。from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy import time desired_caps { platformName: Android, # 测试平台 platformVersion: 6.0.1, # 模拟器的安卓版本在模拟器设置中查看 deviceName: 127.0.0.1:7555, # 设备标识就是adb devices看到的 appPackage: com.android.settings, # 被测App的包名设置 appActivity: .Settings, # 被测App的启动Activity设置的主界面 automationName: UiAutomator2, # 自动化引擎Android首选UIA2 noReset: True, # 是否在会话前重置App状态True为不重置 newCommandTimeout: 600 # 命令超时时间秒 }关键参数解析appPackage和appActivity这是定位一个App内部页面的关键。如何获取有一个简单方法打开你要测试的App然后在命令行输入adb shell dumpsys window | findstr mCurrentFocusWindows。你会看到类似mCurrentFocusWindow{... com.android.settings/.Settings}的输出其中com.android.settings就是包名.Settings就是Activity。automationName对于安卓4.3UiAutomator2是更现代、更稳定的选择弃用老旧的UiAutomator1。noReset设为TrueAppium不会在测试开始前清除App的数据这对于连续测试非常有用。设为False则每次都会打开一个“干净”的App。3.2 元素定位自动化测试的“眼睛”脚本要操作界面上的按钮、输入框首先必须找到它们。Appium提供了多种定位方式最常用的是通过资源IDresource-id、文本text和XPath。如何知道一个元素的这些信息这就需要用到Appium Inspector已包含在Appium Desktop中。在Appium Desktop主界面点击“Start Inspector Session”填入上面desired_caps中的信息注意去掉appPackage和appActivity在Inspector界面单独输入然后点击“Start Session”。它会启动模拟器中的App并加载一个可以查看元素层级和属性的界面。例如我们想点击“设置”里的“WLAN”。在Inspector里点击这个条目右侧会显示其属性。我们可能会发现它有resource-id为android:id/titletext为WLAN。那么定位方式就有# 通过ID定位推荐最稳定 driver.find_element(AppiumBy.ID, android:id/title) # 注意如果resource-id是com.android.settings:id/title则用这个全称 # 通过文本内容定位 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(WLAN)) # 通过XPath定位灵活但可能性能稍差 driver.find_element(AppiumBy.XPATH, //android.widget.TextView[textWLAN])注意事项AppiumBy.ANDROID_UIAUTOMATOR使用的是UiAutomator的语法功能非常强大不仅可以按text还可以按description、className、resourceId等进行定位例如new UiSelector().resourceId(android:id/title)。3.3 完整脚本示例与逐行解读下面是一个完整的、加了详细注释的脚本实现了启动设置并点击WLANfrom appium import webdriver from appium.webdriver.common.appiumby import AppiumBy import time # 1. 定义设备与App的配置信息 desired_caps { platformName: Android, platformVersion: 6.0.1, # 请根据你的MuMu模拟器安卓版本修改 deviceName: 127.0.0.1:7555, appPackage: com.android.settings, appActivity: .Settings, automationName: UiAutomator2, noReset: True, newCommandTimeout: 600 } # 2. 连接Appium Server并传入配置启动会话 # 这里的URL就是Appium Desktop Server默认监听的地址 driver webdriver.Remote(http://127.0.0.1:4723, desired_caps) # 等待App完全启动这是一个好习惯避免元素还没加载出来就去操作 time.sleep(2) try: # 3. 定位“WLAN”选项并点击 # 方法1使用UiAutomator2通过文本定位最直观 wlan_item driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(WLAN)) wlan_item.click() print(成功点击WLAN设置项) # 操作后可以加个短暂停顿方便肉眼观察 time.sleep(1) # 4. 示例在WLAN页面尝试点击右上角的“更多选项”三个点 # 通常这种按钮没有text但有content-desc可访问性描述或resource-id # 先用Inspector查看这个元素的属性 # 假设它的content-desc是“更多选项” # more_options driver.find_element(AppiumBy.ACCESSIBILITY_ID, 更多选项) # more_options.click() # 5. 示例返回上一级 driver.back() print(已返回设置主页面) time.sleep(1) finally: # 6. 无论测试成功与否最后都要关闭会话释放资源 # 这是非常重要的收尾工作否则会话会一直占用端口 driver.quit() print(测试结束会话已关闭)脚本逻辑流解读建立连接webdriver.Remote这行代码是关键它向http://127.0.0.1:4723的Appium Server发送了desired_caps配置。Server根据配置通过ADB连接到MuMu模拟器并启动指定的com.android.settings/.Settings。元素交互find_element是“寻找”返回的是一个代表屏幕元素的Python对象。click()是“点击”是对该元素执行的操作。所有交互点击、输入、滑动都基于先定位到元素。异常处理与收尾使用try...finally结构确保即使中间步骤出错driver.quit()也会被执行关闭Appium会话避免留下僵尸进程。把这个脚本保存为first_test.py在确保MuMu模拟器已启动、Appium Server已运行的前提下在命令行运行python first_test.py。你会看到模拟器自动打开设置并跳转到WLAN页面然后返回。你的第一个自动化脚本就成功了4. 核心操作与常用API详解掌握了基础脚本后我们需要更丰富的“武器库”来模拟真实用户操作。UI自动化无外乎几种核心交互点击、输入、滑动、获取信息。4.1 模拟用户输入与手势操作文本输入在定位到输入框元素后使用send_keys()方法。# 假设我们进入了WLAN设置要输入Wi-Fi密码 # 先点击密码输入框 password_field driver.find_element(AppiumBy.ID, com.android.settings:id/password) password_field.click() # 清空原有文本如果有 password_field.clear() # 输入新密码 password_field.send_keys(MySecurePassword123)滑动/滚动当需要查看屏幕外的内容时使用滑动操作。Appium提供了swipe()方法但更推荐使用scroll或swipe的扩展方法或者直接使用UiAutomator2的滚动定位。from appium.webdriver.common.touch_action import TouchAction import time # 方法1使用TouchAction进行精确滑动从一点拖到另一点 actions TouchAction(driver) # 从屏幕中央向下滑动起始点x, y, 结束点x, y actions.press(x500, y1000).wait(200).move_to(x500, y400).release().perform() # 方法2使用driver自带的swipe方法参数起始x, y, 结束x, y, 耗时ms driver.swipe(500, 1000, 500, 400, 500) # 方法3推荐使用UiAutomator2的滚动查找更符合语义 # 滚动直到找到某个元素 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiScrollable(new UiSelector().scrollable(true)) .scrollIntoView(new UiSelector().text(关于手机)))长按模拟用户长按某个元素。element driver.find_element(AppiumBy.ID, some_item_id) actions TouchAction(driver) actions.long_press(element).wait(3000).release().perform() # wait(3000)表示长按3秒4.2 等待策略让脚本更“聪明”的关键直接使用time.sleep()是一种“硬等待”效率低下且不稳定不同手机加载速度不同。Appium推荐使用显式等待Explicit Wait它会在指定时间内轮询查找元素一旦找到就继续执行大大提升脚本效率和稳定性。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy # 设置一个最长等待10秒的等待器 wait WebDriverWait(driver, 10) # 等待直到“WLAN”这个元素出现并可点击 wlan_element wait.until( EC.element_to_be_clickable((AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(WLAN))) ) wlan_element.click() # 等待直到页面标题包含“WLAN”字样判断页面跳转成功 wait.until( EC.title_contains(WLAN) )显式等待是编写健壮自动化脚本的基石。常见的条件expected_conditions还有presence_of_element_located元素存在于DOM、visibility_of_element_located元素可见等。4.3 断言与测试验证自动化测试不只是执行操作还要验证结果。这就是断言Assert的作用。我们可以使用Python内置的assert语句。# 点击WLAN后验证页面标题或某个特定元素出现 wlan_title driver.find_element(AppiumBy.ID, android:id/title) assert wlan_title.text WLAN, f页面标题验证失败实际是{wlan_title.text} # 或者验证WLAN开关是否存在 wlan_switch driver.find_element(AppiumBy.ID, com.android.settings:id/switch_widget) assert wlan_switch is not None, 未找到WLAN开关控件 # 甚至可以验证开关的初始状态 # 假设开关选中状态对应属性checked为true # is_on wlan_switch.get_attribute(checked) true # assert not is_on, WLAN开关初始状态应为关闭将断言巧妙地插入到关键操作之后就构成了一个完整的测试用例验证点。5. 构建可维护的测试框架雏形当测试脚本越来越多时直接把所有代码写在一个文件里会变得难以维护。我们需要一点简单的架构思维让代码更清晰、复用性更高。5.1 使用Page Object模式组织代码Page ObjectPO模式是UI自动化测试的经典设计模式。其核心思想是将一个App页面封装成一个Python类页面上的元素定位符和操作作为这个类的方法。这样测试脚本用例只关心业务逻辑而不关心具体的元素定位细节。以“设置”主页为例我们创建一个setting_home_page.py文件from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class SettingHomePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) # 页面元素定位符Locators WLAN_ITEM (AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(WLAN)) BLUETOOTH_ITEM (AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(蓝牙)) DISPLAY_ITEM (AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().text(显示)) # 页面操作方法 def click_wlan(self): 点击WLAN菜单项 element self.wait.until(EC.element_to_be_clickable(self.WLAN_ITEM)) element.click() # 可以返回下一个页面的对象实现链式调用 # from wlan_page import WlanPage # return WlanPage(self.driver) def get_page_title(self): 获取页面标题用于验证 # 假设标题栏的ID是固定的 title_element self.driver.find_element(AppiumBy.ID, android:id/title) return title_element.text def is_display_item_present(self): 检查‘显示’项是否存在 try: self.driver.find_element(*self.DISPLAY_ITEM) return True except: return False然后在主测试脚本中代码会变得非常简洁和易读from appium import webdriver from setting_home_page import SettingHomePage desired_caps { ... } # 同上 driver webdriver.Remote(http://127.0.0.1:4723, desired_caps) try: # 初始化页面对象 home_page SettingHomePage(driver) # 业务逻辑清晰明了 home_page.click_wlan() # 这里可以继续操作WlanPage... # 验证 assert home_page.is_display_item_present(), 主页未加载完整 finally: driver.quit()PO模式的好处是当App界面元素ID或位置发生变化时你只需要修改对应Page类中的定位符所有用到这个元素的测试用例都不需要改动极大提升了可维护性。5.2 配置管理与数据驱动将配置如设备信息、App信息、服务器地址从代码中分离出来是一个好习惯。可以使用config.ini文件或Python的config.py模块。# config.py DESIRED_CAPS { platformName: Android, platformVersion: 6.0.1, deviceName: 127.0.0.1:7555, appPackage: com.android.settings, appActivity: .Settings, automationName: UiAutomator2, noReset: True } APPIUM_SERVER_URL http://127.0.0.1:4723主脚本中直接引用from appium import webdriver import config driver webdriver.Remote(config.APPIUM_SERVER_URL, config.DESIRED_CAPS)对于测试数据如登录用户名、密码也可以考虑使用外部文件如JSON、Excel来管理实现数据驱动测试让同一套脚本可以用不同的数据执行。6. 常见问题排查与实战技巧在实际操作中你几乎一定会遇到各种报错和意外情况。这里整理了一份“避坑指南”。6.1 连接与启动类问题问题1adb devices找不到设备或显示unauthorized。排查首先确认MuMu模拟器已启动。然后检查是否有多版本ADB冲突。在命令行输入adb version和adb devices确认使用的ADB来自MuMu目录如果已添加至Path。如果是unauthorized查看模拟器屏幕是否有授权弹窗。解决关闭所有其他模拟器。执行adb kill-serveradb start-server。重启模拟器。确保模拟器设置中的“USB调试”已开启。问题2Appium Server启动失败或脚本报错Could not find a connected Android device。排查Appium Desktop的日志窗口会输出详细错误。常见原因是desired_caps中的deviceName、platformVersion填写错误或者指定的appPackage/appActivity不存在。解决仔细核对desired_caps。deviceName可以是adb devices列出的任意标识如127.0.0.1:7555。platformVersion必须与模拟器“关于平板电脑”里的安卓版本一致只写主版本如6.0也可以。使用adb shell dumpsys window | findstr mCurrentFocus命令确认包名和Activity。问题3脚本执行时元素找不到NoSuchElementException。排查这是最常见的问题。原因可能是1) 页面还没加载出来2) 元素定位符写错了3) 元素在WebView或Hybrid应用内需要切换上下文Context。解决增加等待用WebDriverWait和expected_conditions替代硬sleep。复核定位符用Appium Inspector重新捕获元素确认resource-id、text、class等属性是否唯一且正确。注意text属性可能随语言变化resource-id更稳定。检查上下文对于内嵌H5页面需要driver.contexts获取所有上下文然后driver.switch_to.context(WEBVIEW_xxx)切换到WebView上下文才能定位网页元素。6.2 脚本执行与稳定性问题问题4脚本在真机上运行正常在MuMu模拟器上却报错或点击无效。排查模拟器和真机在渲染、性能上有差异。某些动画或过渡效果可能导致元素状态判断不准。另外MuMu模拟器默认的输入法可能干扰文本输入。解决在desired_caps中增加配置unicodeKeyboard: True, resetKeyboard: True。这会让Appium使用自带的输入法避免冲突。对于点击问题可以尝试使用TouchAction的tap方法代替元素的click()方法或者使用driver.execute_script(mobile: click, {element: element.id})这种原生执行方式。适当增加操作后的稳定等待时间time.sleep(0.5)虽然不推荐大量使用但在处理动画时有时很有效。问题5如何滑动到屏幕底部或顶部解决获取屏幕尺寸然后计算滑动起点和终点。# 获取屏幕尺寸 size driver.get_window_size() start_x size[width] * 0.5 start_y size[height] * 0.8 # 从屏幕80%高度开始 end_y size[height] * 0.2 # 滑动到屏幕20%高度 driver.swipe(start_x, start_y, start_x, end_y, 1000) # 滑动1秒6.3 高级技巧与优化建议技巧1使用UiAutomator2的强大定位器除了textUiAutomator2的定位语法非常强大# 通过类名和文本组合定位 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().className(android.widget.TextView).text(WLAN)) # 通过描述content-desc定位 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().description(更多选项)) # 通过子元素定位父元素层级定位 driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, new UiSelector().resourceId(parent_id).childSelector(new UiSelector().text(Child Text)))技巧2截图与日志记录测试失败时截图是定位问题最直接的证据。可以在断言失败或异常捕获时自动截图。import datetime try: # ... 一些测试操作 ... assert some_condition, 条件不满足 except AssertionError as e: # 生成带时间戳的截图文件名 timestamp datetime.datetime.now().strftime(%Y%m%d_%H%M%S) screenshot_path f./screenshots/failure_{timestamp}.png driver.save_screenshot(screenshot_path) print(f断言失败截图已保存至{screenshot_path}) raise e # 重新抛出异常让测试框架知道用例失败技巧3处理权限弹窗和系统对话框安装或首次启动App时经常会遇到权限申请弹窗。这些弹窗属于系统控件需要用driver.switch_to.alert处理或者更通用地用UiAutomator2定位并点击“允许”或“拒绝”按钮。可以写一个通用的处理函数def handle_permission_dialog(driver, allowTrue): 处理常见的权限弹窗 button_text 允许 if allow else 拒绝 # 尝试定位并点击 try: button driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, fnew UiSelector().text({button_text})) button.click() return True except: # 可能弹窗样式不同尝试其他定位 pass return False从手动点点点到自动化脚本执行不仅仅是工具的转变更是测试思维和工作流程的升级。这套“Python Appium MuMu模拟器”的组合以其低门槛、高灵活性和强大的社区支持成为了入门移动端自动化测试的绝佳选择。真正的挑战往往不在写出第一行能跑的代码而在于如何构建稳定、可维护、易于扩展的测试用例集。从今天这个能自动点击“WLAN”的小脚本开始逐步引入页面对象、数据驱动、持续集成等工程化实践你会发现自动化测试不再是负担而是保障质量、提升效率的可靠伙伴。