移动端 UI 自动化:跑用例前做一道「环境健康预检」(adb + Appium)

移动端 UI 自动化:跑用例前做一道「环境健康预检」(adb + Appium)

做移动端 UI 自动化,真正稳定跑起来之后,你会发现一大半的「用例失败」根本不是用例的锅,而是环境三件套出了岔子:

  • adb 连不上 / 设备 offline
  • 连错了设备(多设备时连到了另一台)
  • 被测 app 没装 / 装的是旧包 / 版本不对
  • Appium 服务没起来

而这些环境问题报出来的错往往很晦涩session not createdan unknown server-side errorelement not found……你对着用例代码查半天,最后发现是 adb 掉线了。

解法很简单:跑整个套件之前,先做一道「健康预检」,逐项检查、fail fast,并把失败原因用人话讲清楚。 这篇就讲这道预检该查什么、怎么查、怎么封装。

命令以 Android(adb)为主;包名、设备号用占位符(com.example.app<udid>),按你自己的填。Appium 默认端口 4723


预检该查哪几项

按「从底层到上层」的顺序,挂在前面的会让后面全废,所以逐项卡:

1. adb 本身可用 + SDK 环境变量

adb version
echo "$ANDROID_HOME"      # 没设的话很多工具会找不到 platform-tools

2. 设备在线(并处理 offline

adb devices -l

输出里设备状态必须是 device,不能是 offline / unauthorized

List of devices attached
emulator-5554   device product:... model:... 

offline 是高频坑:adb 守护进程重启、设备休眠、USB 抖动,都会让设备短暂 offline,这时候所有命令都打不进去。一条命令自愈:

adb reconnect offline       # 把 offline 的设备踢一下重连
# 还不行就重启 adb 守护进程
adb kill-server && adb start-server

3. 多设备时,锁定目标设备

机器上经常同时插着真机 + 模拟器。所有 adb 命令都带 -s <udid> 显式指定目标,否则 adb 会因为「设备不唯一」报错,或者连错机:

adb -s emulator-5554 shell ...

测试框架侧也要把目标设备固定下来(Appium capabilities 里的 udid / deviceName),别让它自己挑。

4. 被测 app 已安装 + 版本核对(很容易被忽略)

这一项专门防「装的包不对 / 没更新,白跑一整轮」——尤其是改了客户端、重新打包之后,最怕跑了半天发现手机上还是旧包。

adb -s emulator-5554 shell dumpsys package com.example.app | grep -E "versionCode|versionName"

输出:

    versionCode=10234 minSdk=23 targetSdk=35
    versionName=1.2.3

versionCode 和「你期望的那个包」对一下,不一致直接报错停下,别让用例在错的包上瞎跑。

5. Appium 服务存活

curl -s http://127.0.0.1:4723/status

健康时返回 {"value":{"ready":true,...}}。连不上就是服务没起来——先把它拉起来再说,否则 session 根本创建不了。

6.(可选)确认 app 已拉到前台

adb -s emulator-5554 shell dumpsys activity activities | grep -E "mResumedActivity|topResumedActivity"

能看到你的包名在前台,说明 app 真的起来了,而不是卡在启动页 / 被系统弹窗挡住。


封装成 pytest 预检(跑在所有用例之前)

把上面几项写成一个 session 级、autouse 的 fixture,整个套件开始前跑一次,任何一项不过就 pytest.exit,并打印人话。这样失败信息一眼能懂,不用再去翻晦涩的 Appium 报错。

import re
import subprocess
import urllib.request

import pytest

UDID = "emulator-5554"
PKG = "com.example.app"
APPIUM = "http://127.0.0.1:4723/status"
EXPECT_VERSION_CODE = None   # 填上就强校验装机版本,比如 10234


def _adb(*args: str) -> str:
    return subprocess.run(
        ["adb", "-s", UDID, *args],
        capture_output=True, text=True, timeout=30,
    ).stdout


def _die(msg: str):
    pytest.exit(f"[预检失败] {msg}", returncode=3)


@pytest.fixture(scope="session", autouse=True)
def preflight():
    print("[预检] === 环境健康检查 ===")

    # 1) 设备在线(带一次 offline 自愈)
    devices = subprocess.run(["adb", "devices"], capture_output=True, text=True).stdout
    if f"{UDID}\tdevice" not in devices:
        subprocess.run(["adb", "reconnect", "offline"], capture_output=True)
        import time; time.sleep(3)
        devices = subprocess.run(["adb", "devices"], capture_output=True, text=True).stdout
    if f"{UDID}\tdevice" not in devices:
        _die(f"设备 {UDID} 不在线(offline/未连接)。先 adb reconnect 或检查模拟器/数据线。")
    print(f"[预检] 设备 {UDID} 在线 ✓")

    # 2) app 已安装 + 版本
    pkg_info = _adb("shell", "dumpsys", "package", PKG)
    if "versionCode" not in pkg_info:
        _die(f"被测 app {PKG} 没装。先 adb install 安装。")
    m = re.search(r"versionCode=(\d+)", pkg_info)
    code = m.group(1) if m else "?"
    vn = (re.search(r"versionName=(\S+)", pkg_info) or [None, "?"])[1]
    print(f"[预检] {PKG} 已安装 versionCode={code} versionName={vn} ✓")
    if EXPECT_VERSION_CODE and code != str(EXPECT_VERSION_CODE):
        _die(f"装机版本 {code} ≠ 期望 {EXPECT_VERSION_CODE},可能没更新到最新包,先重装。")

    # 3) Appium 服务存活
    try:
        with urllib.request.urlopen(APPIUM, timeout=5) as r:
            assert r.status == 200
    except Exception as e:
        _die(f"Appium 服务连不上({APPIUM}):{e}。先把 appium 起来。")
    print("[预检] Appium 服务存活 ✓")

    print("[预检] === 通过 ===")

要点:

  • scope="session" + autouse=True:整个套件只跑一次,且自动生效,所有用例前置。
  • pytest.exit:一项不过就整轮停,而不是让上百条用例各自以晦涩的错失败一遍——省时间、省排查。
  • 失败信息第一句写人话[预检失败] 设备不在线…,而不是把一堆 adb 原始输出甩出来。
  • 版本强校验是性价比之王:改了客户端 / 重新打包后,这一条能挡住「在旧包上白跑一轮」的尴尬。

小结 / Checklist

跑移动 UI 自动化前,按这张表卡一遍环境,能挡掉一大半「假失败」:

  • ✅ adb 可用、ANDROID_HOME 已设
  • ✅ 目标设备在线(device 而非 offline);offline 用 adb reconnect / 重启 daemon 自愈
  • ✅ 多设备一律 -s <udid> 锁定,别让框架自己挑
  • ✅ 被测 app 已安装,且 versionCode 和期望的包对得上
  • ✅ Appium 服务 /status 返回 ready:true
  • ✅(可选)app 已在前台
  • ✅ 全部封装成 session 级 autouse 预检,一项不过就 pytest.exit + 人话报错

一句话:把环境问题挡在用例之前,用例失败就只剩用例自己的问题。 这道预检写一次,长期省下的排查时间远超它本身。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容