3天搭建企业级App自动化测试:Python+Appium完整实践

3天搭建企业级App自动化测试:Python+Appium完整实践 你接到一个任务3天后要在项目例会上演示“Python Appium 驱动网易严选做全流程自动化测试”并且最好看起来足够“企业级”。很多人的第一反应是先打开搜索引擎找安装教程然后从 Python 安装、Node 安装、Appium 下载一路看下去。结果真正开始写脚本时两天已经过去了还卡在“设备连接成功但会话建立失败”或者“连上 Appium 却找不到 App 入口”这件事上。这类问题的根源通常不是不够努力而是把“自动化测试”理解成了“写脚本”。脚本只是其中一环真正决定三天后站在会议室里是顺利演示还是现场翻车取决于一条更完整的链路环境、设备、元素定位、等待策略、用例组织、失败记录、报告和持续集成。今天这篇文章想把这条链路拆开用网易严选这个大家熟悉的 App 作为例子聊一聊 3 天时间里哪些事情值得做哪些事情不应该做以及“企业级”这三个字到底意味着什么。1. “3天搞定企业级”这个说法到底能信几分1.1 我更愿意把它理解成“跑通最小闭环”不要被“全部”“全流程”这类词误导。3 天时间真正能实现的不是把网易严选的几百个页面和几十条用户路径全部自动化而是从零到一跑通一条最小的自动化闭环安装好环境、连接上手机或模拟器、启动 App、操作几步核心页面、完成断言、留下失败截图和测试报告。这套闭环一旦跑通后续很多工作实际上是重复和扩展。今天加一个页面对象明天补一个异常场景后天把用例挂到 Jenkins 上定时执行。但如果第一天就走错了方向比如花了两天去研究复杂的框架设计或者把全部精力放在美化报告上那么到最后可能连一条稳定的用例都没有。所以我对“3天搞定企业级 APP 自动化”的第一判断是这个说法不是不可能但它真正指的应该是“搭建一个可以持续生长的骨架”而不是“把所有功能都自动化完”。如果你按照后一种目标来规划这 3 天大概率会焦虑并且会为了赶进度写出大量靠 sleep 硬撑的脆弱脚本。1.2 “企业级”不是技术名词而是一组工程约束很多教程里一个 Python 文件就能把 Appium 跑起来。几十行代码启动、点击、输入、断言看起来像模像样。但进入真实项目后你会发现团队对自动化的要求根本不是“能跑一次”而是“每天都能跑失败了还知道为什么”。我建议用一个简单的框架来理解企业级自动化它至少要满足可重复、可维护、可报告、可追踪、可集成这五个条件。关注点单条脚本阶段企业级阶段用例执行本机能跑通任何人在任何时间都能跑通失败处理报错后人工看终端自动截图、保存页面源码、记录日志元素定位写死的 XPath 散落各处页面对象集中管理首页变了只改一处结果呈现print 输出Allure 报告原因一目了然运行方式手动执行Jenkins 定时触发多设备并发回归价值验证一段代码成为发布或迭代前的质量信号“企业级”这三个字听起来很大但落到操作层面其实就是把脚本当成正式代码来维护。给变量起清楚的名字封装公共方法把等待写成公共函数把定位符集中到类里。这些工作不花哨却决定了自动化能不能长期活下去。1.3 三天的节奏应该怎么分配如果只给我三天时间我会这样安排第一天搭建 Python、Node、Appium、Android SDK、真机/模拟器环境跑通第一个 Appium 会话用 Appium Inspector 从页面里找到第一个定位符。第二天以网易严选的核心浏览路径为例封装页面对象实现“启动 App → 搜索商品 → 打开详情 → 加入购物车”这条可验证的业务链路并接入显式等待。第三天完善失败截图与日志接上 Allure 或 Pytest 报告把项目整理成可以被别人 clone 后直接运行的结构如果还有时间再考虑多设备并发或 Jenkins 集成中的任意一块。这三天里最关键的目标不是“用例数量多”而是“一条用例能稳定重复运行 10 次”。后面每一次新增用例都只是往这个已经被验证过的骨架上加东西。2. 第一天别急着写代码先理解 Appium 的会话模型2.1 环境准备缺哪块都会让你怀疑人生很多人装环境时喜欢照着网络上的旧教程一步步点结果版本混乱Python、Node、JDK、Appium Server、驱动之间互相打架。第一天最好先明确一件事Appium 不是单一的测试工具它是由一个 Server 加多个端驱动组成的体系。如果是用 Appium 2.x 的常见做法Server 和驱动是分开安装的。你装好 Appium Server 后还要单独安装 UiAutomator2 Driver才能驱动 Android 原生页面。这个细节在老教程里经常被忽略很多人以为 Appium 安装成功就万事大吉结果启动会话时却报“Unable to create a new remote session”。下面是一份环境清单可以做核对组件作用常见问题Python 3.8运行测试脚本注意 PATH 是否配置正确Node.jsAppium Server 依赖版本过旧会装不了 Appium 2.xJDKAndroid SDK 编译和 UIAutomator2 需要必须配置 JAVA_HOMEAndroid SDK / Platform Tools提供 adb、aapt 等工具需要配置 ANDROID_HOMEAppium Server接收客户端请求并调起驱动Appium 2.x 要单独安装驱动UiAutomator2 Driver实际控制 Android 页面元素用 appium driver install uiautomator2 安装Appium Inspector查看页面层级、定位元素不是必须但强烈建议先装真机或模拟器被测 App 的运行环境模拟器通常只适合练手真机上更容易暴露实际问题安装过程本身并不复杂但一定要记住版本要匹配。网上很多旧的 Appium 1.x 教程连接地址写的是http://127.0.0.1:4723/wd/hub而我在一些新版本里用http://127.0.0.1:4723也能通。不同版本、不同启动方式下这个地址会有差异。如果照抄别人的脚本一直连接失败先查你本地的 Appium 版本和启动日志而不是先怀疑代码。2.2 Desired Capabilities告诉 Appium 你要什么Appium 的自动化和 Selenium 走的是同一套 WebDriver 思想。你写一个 Python 脚本向 Appium Server 发起会话请求Server 再根据请求里的 Desired Capabilities 去启动对应的驱动在设备上安装启动一个中间测试服务最终控制 App。下面这段代码是第一次连接设备时的典型结构from appium import webdriver caps { platformName: Android, appium:deviceName: AndroidTD, appium:udid: 你的设备序列号, appium:appPackage: 被测应用的package, appium:appActivity: 被测应用的入口Activity, appium:automationName: UiAutomator2, appium:noReset: True, appium:unicodeKeyboard: True, appium:resetKeyboard: True, } driver webdriver.Remote(http://127.0.0.1:4723, caps) try: print(session:, driver.session_id) print(当前页面:, driver.current_activity) finally: driver.quit()这里有几个参数特别容易误解appPackage和appActivity不是拍脑袋写的。常见做法是先用adb devices查看设备再通过adb shell dumpsys window | grep mCurrentFocus查看当前前台应用的包名和 Activity。如果装了 aapt 工具也可以用aapt dump badging 你的App.apk查看入口信息。noReset: True表示不重置 App 数据适合重复跑用例时不想每次重新登录的场景。但如果你的用例要验证首次启动引导流程就需要改成 False。unicodeKeyboard和resetKeyboard主要是为了支持中文输入。很多输入框里输入中文变成乱码或直接失败就是少了这两个配置。automationName在 Android 上一般用UiAutomator2。传统 Android 驱动不建议再继续使用尤其是新版本 Appium Server 默认就不再包含它。2.3 Appium Inspector 的使用逻辑有些人在还没有跑通会话的时候就急着写定位脚本。这就像闭着眼敲门。Appium Inspector 的价值在于它能直接显示当前页面的 UI 层级树告诉你一个按钮在层级结构中叫什么、resource-id 是多少、文本内容是什么。使用逻辑一般是三步先启动 Appium Server。在 Inspector 里填入和脚本相同的 Desired Capabilities建立会话。在设备上手动操作页面查看元素层级。要注意Inspector 连接会话时也会真实地在设备上启动一次被测 App。如果 App 启动后有登录弹窗、隐私协议弹窗、权限申请弹窗这些弹窗也会出现在层级树里。千万不要以为 App 坏了这恰恰是自动化要处理的真实场景。2.4 验证第一个能稳定连接设备的最小脚本第一天晚上我建议只追求一个结果执行上面那段脚本能打印出session_id并且能正确拿到current_activity。如果这一步稳定了说明环境、设备、Capabilities 配置已经走通。如果你卡在这一步大概率不是 Python 的问题而是下面几个位置之一设备没有授权、uiautomator2 驱动没装、App 包名 Activity 填写错误、Appium Server 没有真正启动成功。不要反复重装环境先打开 Appium 的日志看它卡在哪个阶段。这条排查习惯后面会救你很多次。3. 第二天用“网易严选”串最小流程从启动到加购3.1 把业务链路拆成可以自动化的动作不要一上来就写“从打开网易严选到支付成功”。支付是非常敏感的流程涉及资金、风控、第三方支付环境不适合在日常自动化里直接操作。我会建议把业务链路截断到“加购”或“进入支付页但不要真正支付”这个层面。这样既能覆盖 UI 到业务逻辑的联动又不会产生资金风险。一条合理的最小链路可以是步骤自动化动作验证点1启动网易严选 App首页关键元素出现2点击搜索入口跳转到搜索页3输入关键词并搜索搜索结果列表出现商品4点击第一个商品商品详情页标题可读5点击加入购物车出现加购成功的提示或购物车角标变化这条链路看起来很普通但它已经串联了启动、点击、输入、跳转、滑动、断言、弹窗处理等绝大多数 UI 自动化基础操作。如果你把它跑稳了再往其他场景扩展就会顺很多。3.2 定位元素时不能靠蛮力Appium 在 Android 上最常用的定位方式有几种按我个人经验排列如下优先用resource-id在 Android 原生页面里通常比 XPath 稳定。其次用accessibility_id或文本内容适合按钮、菜单这类语义明确的元素。再次用 XPath适合没有 id、层级关系又比较特殊的元素。不推荐直接从根节点写一串带多层绝对路径的 XPath页面一改就会碎。这里指的“网易严选”只当作一个用户熟悉的示例具体页面里的元素 id 要结合你安装的测试版本去取。换一个版本id 就可能有变化。所以更合适的做法是把所有定位符收在页面对象里以后页面调整时只改一个类文件。拿到定位符之后还有一个常见陷阱你以为元素不存在其实只是它不在当前屏幕内。很多 App 的商品列表都是懒加载只有滑到附近才渲染。遇到这种情况不要死等一个元素而要加入“找不到就滑动再找”的逻辑。搜索执行后如果第一个商品在屏幕下方就需要一点上滑动作。3.3 一个容易忽略的问题等待自动化脚本里最常见的失败原因是“执行太快”。点击搜索页面还没跳转马上找搜索结果自然找不到。很多初学者会直接加time.sleep(3)这当然能解决一部分问题但会造成两种问题低配设备和高端设备性能差异大固定 3 秒在高配机器上浪费低配机器上不够。如果某个页面因为网络或弹窗原因需要 5 秒固定 3 秒的脚本就会不稳定。更好的做法是用显式等待from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 15) search_element wait.until( EC.presence_of_element_located((AppiumBy.ID, 某一个元素的resource-id)) )这段代码的意思是最多等 15 秒每 500 毫秒检查一次只要元素出现就立刻继续执行。如果 15 秒还没出现再抛出异常。相比固定sleep这种做法更快也更稳。要注意的是presence_of_element_located只代表元素出现在 DOM 里不代表它可点击。如果要做点击操作可以考虑用element_to_be_clickable。尤其是 App 里常见“按钮已经渲染但被弹窗遮住”的情况只有可点击状态能真正说明操作就绪。3.4 先跑一条冒烟测试而不是追求完整回归第二天结束时能跑通上面表格里这条链路就已经是一个里程碑了。不要急着覆盖分类、秒杀、优惠券、登录失败、弱网等大量场景。因为每多一个场景就会多出若干异常分支。你需要在第 2 天先把一条“黄金路径”打磨到稳定否则后面做再多用例都只是在验证一个不稳定的框架。第二天最后的验证标准我建议是这样的连续执行 10 次至少 8 次成功。如果失败能自动截图并且日志里能看出失败在哪个步骤。手动清理 App 数据后脚本仍然可以从启动开始执行。代码结构不是一个大平铺文件而是已经分出了页面对象层和用例层。如果这些做不到第三天先不要去做多设备并发和 Jenkins继续回头完善稳定性。稳定性一定是这一阶段的第一优先级。4. 第三天向“企业级”靠拢PO 模式、失败处理和报告4.1 为什么脚本越来越多时一定会失控你可以试试用最直接的方式写 20 条网易严选的 UI 用例直接在测试函数里写driver.find_element(...)。很快你就会发现搜索入口的定位符被复制到了 10 个文件里登录按钮的点击逻辑在 15 个地方出现过。一旦某个页面的 id 变了你要满项目搜索逐个修改。这不是某个人的习惯问题而是过程式脚本的天然瓶颈。当测试用例和页面细节耦合在一起时页面细节一改所有用例都会崩。这时候引入 Page Object 模式是成本最低、见效最快的一次重构。4.2 用 Page Object 把页面和操作分开Page Object 的核心思想不复杂一个页面对应一个类类里集中管理这个页面的定位符和能在这个页面上做的操作。测试用例看起来更像业务行为而不是一堆底层调用。class HomePage: def __init__(self, driver): self.driver driver self.search_entry (AppiumBy.ID, 首页搜索入口的resource_id) def click_search_entry(self): self.driver.find_element(*self.search_entry).click()class SearchPage: def __init__(self, driver): self.driver driver self.search_input (AppiumBy.ID, 搜索输入框的resource_id) self.search_btn (AppiumBy.TEXT, 搜索) def search(self, keyword): self.driver.find_element(*self.search_input).send_keys(keyword) self.driver.find_element(*self.search_btn).click()这样搜索引擎入口怎么进入、搜索框怎么输入都被装进了对应的页面类。测试用例只需要关心“用户在首页点搜索在搜索页搜索商品”这个业务行为。后续页面改动也只影响对应类不会炸到全部用例。对于三天内要交出的项目这个抽象级别已经足够。不需要再设计复杂的关键字驱动那会增加维护成本更适合大型团队在积累了足够多的用例之后再去考虑。4.3 失败时要留下证据而不是只抛一个红字企业级自动化与个人脚本的一个核心区别在于失败时能不能快速定位问题。很多新手写用例时只关心正常流程一但异常抛出来终端里只有红色报错项目成员根本不知道是页面没加载出来还是元素定位写错了还是 App 根本就没启动。我建议至少在测试流程中做好三件事失败时自动截图并保存到报告目录。失败时保存当前页面的page_source方便回看 UI 树。把当前 Activity、设备日志、异常堆栈一起写入日志文件。在 Pytest 里可以借助框架的钩子或 fixture 来实现统一的失败处理。比较轻量的一种思路是在用例执行失败时捕获异常再额外记录现场信息。def test_open_product(driver): try: home_page HomePage(driver) home_page.click_search_entry() # ... except Exception: driver.save_screenshot(failure_search_product.png) with open(failure_page_source.xml, w, encodingutf-8) as f: f.write(driver.page_source) raise这里强调的并不是代码本身有多高明而是你要养成一种习惯每次失败都不只是打印“找不到元素”而是把当时用户界面是什么样、页面结构是什么样、执行到了哪一步都记录下来。有了这些证据排查时间会大幅缩短。4.4 测试报告是给团队看的不是给自己看的脚本跑完以后需要在例会上展示结果。这时候一个清晰的报告比终端输出重要得多。Pytest 加 Allure 是常见组合。例如可以在项目里先安装依赖然后执行pytest --alluredir./allure-results allure serve ./allure-resultsAllure 报告能显示每条用例的步骤、截图、耗时、失败信息。团队即使不看代码也能知道哪条链路失败、失败在哪一步。对于三天后的演示来说这份报告是最有说服力的交付物。但不要只追求报告好看。报告里的截图和日志才是长期价值。如果一条用例跑失败了报告只显示“AssertionError”那和终端报错没有区别。真正有用的报告是能在 1 分钟后判断出失败是由上线变更引起的页面文案变化导致的还是测试脚本自身不稳定导致的。5. 再往前半步多设备并发与 Jenkins 集成5.1 先判断你需不需要一上来就并发很多人一听到“企业级”就想立刻做多设备并行。但对一个总共只有几条核心用例、每天也就跑一次的项目来说并发只会增加复杂度不会带来收益。我建议先做一个判断你的自动化回归要跑多久如果只有 10 条用例单台设备跑 15 分钟能完成那根本不需要并发。如果用例数量已经到几百条或者需要覆盖不同 Android 系统版本的兼容性这才有并发的必要。多设备并发的本质也不是“给每台设备写一份不同代码”而是让同一份代码能通过配置运行在不同的设备上。关键是不要在这些地方写死设备信息不要写死udid要让每台设备传入自己的设备号。不要在同一进程中让多个 driver 共用同一个端口。不要在用例里共用全局状态比如同一个登录 token。测试结果目录要按设备分离。5.2 多设备并发的常见实现思路一个可行的入门思路是“一个设备一个命令执行入口”。你可以把启动参数通过环境变量传进去再分别启动多个 Python 执行进程。例如在终端里分别执行UDIDemulator-5554 PYTEST_ADDOPTS--alluredir./result-device1 pytest tests/test_purchase_flow.py UDIDemulator-5556 PYTEST_ADDOPTS--alluredir./result-device2 pytest tests/test_purchase_flow.py然后在代码里统一读取UDID这个环境变量拼进 Desired Capabilities。这样两台设备就能并行跑同一套用例。如果使用 Python 内部的并发机制需要注意 Appium 客户端、driver 对象并发访问时的线程安全问题。最稳妥的入门策略就是先选择“多进程、每进程一设备”而不是在同一个进程里开很多线程。多设备模式下还有一个非常常见的坑Appium 会为 UiAutomator2 在设备上占用一个本地通信端口如果两台设备都使用默认端口就会冲突。很多实现方案会为每个设备单独设置systemPort。这类参数不是让你随便乱填的而是每个设备要保持独立。5.3 Jenkins 集成把自动化变成每天都跑的任务脚本跑得再稳如果只能在你自己的电脑上手动执行就不会成为团队真正依赖的质量信号。把它接入 Jenkins 后自动化的价值才会明显起来每晚自动跑第二天早上团队直接看报告。企业里比较常见的方式有几种在 Jenkins 里配置一个自由风格任务通过 shell 或 Windows 命令行执行pytest再通过 Allure 插件发布报告。使用 Pipeline 脚本把测试步骤定义成代码方便版本管理。设置定时触发器例如每个工作日凌晨 2 点执行。在构建产物或 Git 合并后触发自动化比如开发提交代码后自动冒烟。做集成时最容易忽略的反而不是 Jenkins 配置本身而是执行环境的一致性。如果你在自己电脑上能运行但换一台只装了 JDK、没有配置 Android SDK 的机器上就运行不了说明脚本的可移植性不足。这三天的最后如果有时间一定要在另一台干净环境里试一下看项目能不能按照 README 直接从环境准备跑到报告生成。5.4 日志和可观测性企业级最容易漏的一环最后日志非常容易被低估。许多自动化项目失败后团队成员第一反应不是看测试报告截图而是打开appium.log或uiautomator2日志找线索。如果这个环节没有沉淀就只能靠肉眼反复跑测试复现问题。在项目里建议至少保留四类日志测试业务日志记录每一步操作和断言结果。失败现场日志截图、页面源码、Android 系统日志。Appium Server 运行日志排查驱动与设备通信问题。设备日志特别是 App 崩溃、ANR、渲染问题时很有用。日志虽然没有报告那么好看但在很多情况下它才是定位问题的最后一根稻草。6. 真正会卡住你的通常是这些和“技术能力”无关的坑6.1 一次标准的问题排查顺序不管是用例失败还是环境连接失败都建议用一个比较稳定的排查顺序去处理而不是看到异常就开始改代码看现象是连接报错、找不到元素、还是断言失败看设备adb devices是否能看到设备设备是否熄屏、断连、弹出系统窗口看 Appium 服务Server 是否还在运行日志最后到哪里看 Capabilities包名、Activity、设备号是否匹配当前测试包看页面状态是否有升级弹窗、隐私协议弹窗、权限申请看定位Inspector 里元素是否存在有没有被遮挡看代码是没等待到位还是定位符过期这七步不一定每一步都会用到但“先确认测试对象状态再怀疑脚本”能帮你省掉大量无效修改。6.2 高频问题会话建立失败、找不到元素、输入乱码、运行变慢会话建立失败最常见的原因有几种。第一种是 uiautomator2 驱动没有安装成功Appium 连不上设备上的自动化服务。第二种是设备没有正确授权调试。第三种是appPackage和appActivity不正确App 可能启动了但会话因为入口不对而失败。遇到这种情况可以先用adb shell dumpsys window | grep mCurrentFocus手动启动 App看它实际处于哪个界面。找不到元素不要一上来就怀疑 XPath 写错了。先打开 Appium Inspector 看当前页面是什么。很多 App 在启动后会弹“隐私政策提示框”底层的元素依然存在但就是点不到。另一种情况是页面是一个 WebView 或混合页面原生定位方式根本取不到内层元素需要切换到 WebView 上下文。中文输入乱码在 Android 原生输入框中如果输入中文出现乱码或无法输入先检查 Desired Capabilities 里是否设置了unicodeKeyboard: True和resetKeyboard: True。有些输入框可能还需要在执行send_keys前先clear()一次。如果是在 WebView 里输入情况会更复杂可能需要先切换上下文或检查输入框是否聚焦。运行越来越慢通常不是 Appium 本身变慢而是没有清理测试产生的缓存、截图和日志。另一个常见原因是每个用例都以noResetFalse反复重置 App导致登录、引导、初始化逻辑一遍又一遍跑。如果想要稳定尽量在业务层设计测试账号而不是靠反复重置来保证用例独立。6.3 如果只想先跑通哪些参数可以保持默认参数建议初始值什么情况下再改noResetTrue要验证首次启动和权限引导时设为 FalseautoGrantPermissionsTrue需要验证权限弹窗点时关闭newCommandTimeout60脚本长时间不发送命令时才需要调大systemPort默认多设备并发或端口冲突时调整unicodeKeyboard / resetKeyboardTrue不需要中文输入时也可以保留一般无害automationNameUiAutomator2除非驱动方式改变不建议为了“性能”乱换这些参数没有绝对的正确值。最好的理解方式是先确认它影响的是哪一段行为再决定要不要改。如果只是照抄别人的配置出了问题你都不知道改哪个方向。三天时间并不算短但它只够你走完一条从模糊到清晰的路径。这套自动化的价值不在三天后的那场演示里而在演示之后当团队成员看到一份干净的报告、两条能稳定重复运行的流程、一段不再让人头皮发麻的代码结构他们才愿意相信自动化真的有长期投入的意义。如果今天只允许你带一句话走我会说三天不是终点而是你要跑通的第一段路。先让一条黄金路径足够稳定再把这种稳定复制到更多路径上这才是“自动化测试”从想法变成工程能力的过程。