ADB 自动化测试实战:命令、Python 封装、元素定位与排错
发布时间:2026/10/2 9:20:18来源:尧图网络
1. 先搞懂 adb 是什么它凭什么成为自动化测试的地基刚接触 adb 自动化测试的人几乎都会经历同一个阶段把adb devices敲进命令行看到一串设备号然后陷入沉默——这玩意儿到底能帮我干什么我自己的经历比较典型最早是为了批量装包和拉日志后来发现所有手机端的自动化动作追到最底层几乎都是在拼 adb 命令。它不是什么高深的测试框架而是一根把电脑和手机连起来的线一根足够听话、足够可控的线。adb 全称 Android Debug Bridge直译过来是安卓调试桥。别被调试两个字骗了它的能力远不止调试。装应用、卸载应用、模拟点击、滑动屏幕、截图、录屏、抓日志、传文件、看当前前台是哪个页面、查内存占用、改系统设置全部能干。对做自动化测试的人来说它相当于一双远程的手和一双远程的眼睛手负责按照脚本的指令去操作界面眼负责把界面状态、日志、截图这些证据带回来。它适合谁如果你正在做 App 端的功能自动化、兼容性验证、批量设备测试、日志采集或者单纯想把日常重复的点击操作变成脚本那 adb 是你绕不开的第一课。哪怕最后你用的是 Appium、Airtest 这类成品框架它们在底层也大量依赖 adb 完成任务。先把 adb 摸熟后面学框架会轻松很多因为你知道框架帮你屏蔽了什么出问题时也知道该往哪儿查。1.1 三段式架构客户端、服务端、守护进程adb 的结构是三段式的理解它比记住命令重要得多。第一段是客户端client。你在终端敲的每一条adb xxx本质上都是启动一个客户端进程它把命令打包发给服务端。客户端跑在你的开发机上生命周期很短命令发完就退出。第二段是服务端server。这是跑在开发机后台的一个常驻进程默认监听本机 5037 端口负责管理所有已连接设备、派发命令、维护连接状态。adb start-server和adb kill-server操作的就是它。很多人遇到设备明明插着却识别不到第一步就该怀疑服务端状态出了问题杀掉重启一次往往就好了。第三段是守护进程daemon常写作 adbd。它跑在手机或模拟器里随系统启动负责真正执行命令并把结果回传。手机端开发者选项里的USB 调试开关控制的就是这个守护进程能不能被外部连接。这三段的关系有点像快递你客户端把包裹交给本地网点服务端网点再派人送进小区守护进程。任何一段断了包裹都到不了。搞清这个链路后面排查 unauthorized、offline 这些问题时你就能按链路一段段往下切而不是瞎试。1.2 自动化测试为什么绕不开它有人会问现在框架这么多直接用不就行了为什么还要学底层命令我的答案是框架解决的是怎么写用例更舒服adb 解决的是设备和系统层面到底发生了什么。这两件事在不同的层面上。举个最实际的场景脚本跑了十分钟突然卡住界面不动了。框架可能只告诉你元素找不到但真正的原因可能是应用崩了、弹了个系统权限对话框挡在前面、或者设备因为长时间运行进入了低电量模式降频。这些信息框架不一定给你但adb logcat、adb shell dumpsys、adb shell ps能给你。再比如多设备并行跑用例你得知道怎么用-s参数指定设备用例失败要留证你得会截图和拉日志跑前要保证环境干净你得会pm clear清数据、am force-stop停应用。这些细碎但关键的动作全都在 adb 的能力范围里。框架能提升你的上限adb 决定你的下限。2. 环境搭建从零把 adb 跑起来环境这一步说简单也简单说坑也真坑。我见过太多人卡在第一步不是 adb 装不上而是装上了识别不到设备或者识别到了状态不对。这一节按顺序把流程走一遍每一步我都说明为什么这么做。2.1 平台工具的安装与 PATH 配置adb 本身是单独的可执行文件但官方一般把它打包在platform-tools里发布Windows、macOS、Linux 都有对应版本。你可以只下 platform-tools不需要装完整的 Android Studio除非你还要写原生代码。下载后解压到一个固定目录比如 Windows 下放D:\tools\platform-toolsmacOS 下放/Users/你的用户名/tools/platform-tools。接着把它加进系统 PATH 环境变量这样在任何目录下都能直接敲adb不用每次写完整路径。Windows 配置 PATH 的步骤右键此电脑→属性→高级系统设置→环境变量→在系统变量里找到 Path→编辑→新建→把 platform-tools 的完整目录粘贴进去→一路确定。注意是目录不是 adb.exe 文件本身这一点经常有人填错。macOS 或 Linux 的话编辑~/.zshrc或~/.bash_profile加一行export PATH$PATH:/Users/你的用户名/tools/platform-tools保存后执行source ~/.zshrc让配置生效。验证方式很简单开一个新终端敲adb version能打印出 Android Debug Bridge version 和一串版本号就说明 PATH 配好了。如果提示 command not found八成是路径写错、没生效或者你开的终端窗口是配置之前就打开的旧窗口。注意Windows 上如果之前装过某些手机助手类软件它们可能自带一份老版本 adb版本冲突会导致奇怪的问题。用where adbWindows或which adbmacOS/Linux看一下实际调用的是哪一个必要时把旧的删掉或调整 PATH 顺序。2.2 三种连接方式USB、局域网无线、模拟器USB 连接是最稳的方式也是我日常首选。步骤是手机进入关于手机连续点七次版本号打开开发者选项然后进去打开USB 调试。插上线手机上会弹一个允许 USB 调试吗的授权框勾选始终允许再确认。之后adb devices就能看到设备了。USB 的优点是延迟低、连接稳、不怕网络抖动。缺点是线材质量和接口接触会直接影响稳定性。我踩过一次坑某根便宜数据线在传输大文件时频繁掉线换线之后一切正常。所以设备频繁 offline 时先换根线试试这是成本最低的排查动作。局域网无线连接适合需要拿着手机走动测试的场景比如测试传感器、测试投屏、或者手机要固定在某个支架上不方便插线。现在较新版本的 Android 支持在开发者选项里直接开启无线调试会给出 IP 和端口用adb pair配对后再adb connect即可。老一些的方式是通过 USB 先执行adb tcpip 5555把设备切换到网络调试模式再执行adb connect 192.168.1.100:5555这里的 IP 是手机在同一局域网里的地址可以通过手机的 WiFi 详情页看到。无线连接的好处是自由代价是受网络质量影响同一局域网、信号稳定的情况下体验还可以跨网段就会被防火墙拦住。模拟器连接是自动化测试里用得很多的形态因为可以多开、方便做分辨率覆盖。主流模拟器都会自动把自己的 adb 端口注册给本机的 5037 服务端正常情况下adb devices直接就能看到形如127.0.0.1:62001。如果看不到通常是模拟器的 adb 版本和服务端不匹配可以手动连一下adb connect 127.0.0.1:62001端口号各家不同需要查一下对应模拟器的说明。我个人的经验是模拟器适合跑大批量的功能回归和兼容性冒烟真机适合跑需要硬件能力的用例两者配合使用效率最高。2.3 连接状态自检device、offline、unauthorizedadb devices是使用频率最高的命令之一但很多人只看有没有输出不看状态。实际上状态才是关键信息。状态含义常见原因处理方向device正常在线无可以直接执行命令offline设备存在但通信异常线材接触不良、adbd 卡死、版本不匹配重启 adbd、换线、重启服务端unauthorized未授权手机未确认授权弹窗、授权记录失效手机上重新确认、撤销授权重连no permissions权限不足Linux 下 udev 规则缺失配置 udev 规则空列表完全识别不到驱动缺失、USB 调试未开、服务端异常查驱动、查开关、重启服务端我习惯在每次开跑前写一句固定动作adb kill-server adb start-server adb devices -l。-l会额外打印设备型号、传输方式等信息多设备时能帮你确认到底连的是哪台。这个动作花不了几秒但能省掉后面大量为什么脚本点不动的怀疑时间。3. adb 常用命令自动化脚本的手和眼命令很多但真正高频的其实就那么二三十条。我按自动化测试的视角重新分一下类查询类负责看输入类负责动传输类负责留证日志类负责复盘。掌握这四类日常脚本基本够用了。3.1 设备、应用包与信息查询先看设备信息adb devices -l adb shell getprop ro.product.model # 机型 adb shell getprop ro.build.version.release # 系统版本 adb shell wm size # 屏幕分辨率 adb shell wm density # 屏幕密度 adb shell dumpsys window | grep mCurrentFocus # 当前前台窗口macOS/Linuxwm size和wm density一定要记牢后面做坐标换算、做多分辨率适配全靠它。mCurrentFocus能告诉你当前顶层是哪个 Activity 或包名这是判断应用有没有起来有没有被弹窗挡住的关键依据。应用包相关adb shell pm list packages # 列出所有包名 adb shell pm list packages | grep 关键词 # 过滤 adb shell pm path com.example.app # 查应用安装路径 adb shell dumpsys package com.example.app | grep versionName # 查版本号启动和停止应用是自动化用例的起手式和收尾动作adb shell am start -n com.example.app/.MainActivity # 指定页面启动 adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1 # 按启动器方式拉起 adb shell am force-stop com.example.app # 强制停止 adb shell pm clear com.example.app # 清空应用数据这里有个细节值得说am start带-W参数会等待启动完成并输出耗时做启动性能测试时特别有用adb shell am start -W -n com.example.app/.MainActivity输出里的 ThisTime、TotalTime、WaitTime 三个值含义不同TotalTime 是应用自身启动耗时WaitTime 还包含系统调度开销。做启动优化对比时我一般固定用 TotalTime并且确保每次跑之前都先 force-stop避免热启动干扰结果。3.2 输入事件点击、滑动、按键、文本input是自动化脚本里出现频率最高的命令没有之一。adb shell input tap 500 1200 # 点击坐标 adb shell input swipe 500 1500 500 500 300 # 从(500,1500)滑到(500,500)耗时300毫秒 adb shell input text hello # 输入文本不支持中文和空格 adb shell input keyevent 3 # HOME 键 adb shell input keyevent 4 # 返回键 adb shell input keyevent 26 # 电源键 adb shell input keyevent 66 # 回车input text对中文和空格无能为力这是很多人都踩过的坑。空格可以用%s代替中文就得另想办法——常见做法是配合输入法广播或者用 UI 自动化框架的输入能力。我自己在纯 adb 脚本里处理中文输入时会先把文本通过剪贴板方式设置再模拟粘贴稳定性比直接 input 好。input swipe的最后一个参数是滑动时长。时长太短可能被识别成快速甩动或者直接被丢弃太长又拖慢用例。实测 200 到 500 毫秒之间比较稳妥需要模拟长按拖动时就把它拉到 800 毫秒以上并且在起止点之间保持足够的位移距离。3.3 截图、录屏与文件传输截图在自动化里不只是留个证据这么简单很多时候它是断言依据——比如对比某个位置的颜色、判断页面是否加载完成。adb exec-out screencap -p screen.png这里用exec-out而不是shell原因是adb shell screencap -p file会把换行符做转换导致 PNG 文件损坏、打不开。这个坑非常经典我第一次遇到时盯着打不开的图片看了半天最后才发现是换行符惹的祸。截图性能上exec-out也更快一些。录屏用adb shell screenrecord --time-limit 30 /sdcard/demo.mp4 adb pull /sdcard/demo.mp4 .screenrecord默认最长 3 分钟超过要分段。录屏本身会占用一定性能跑性能敏感用例时别开。文件传输就是adb push和adb pulladb push local.txt /sdcard/Download/ adb pull /sdcard/Download/result.txt .在自动化里这两个命令常用于推送测试数据、拉取被测应用生成的报告文件。注意路径写对安卓各版本对/sdcard的映射略有差异稳妥的写法是先adb shell ls /sdcard确认一下。3.4 logcat把日志抓成能用的东西日志是排查问题的命脉但adb logcat直接跑起来会刷到你怀疑人生。关键在过滤。adb logcat -c # 先清空历史日志 adb logcat -v time run.log # 带时间戳全量输出 adb logcat -v time -s TagName # 只看某个标签 adb logcat -v time *:E # 只看 Error 及以上 adb logcat -v time | grep 关键词 # 按关键字过滤 adb logcat -d -v time dump.log # 抓取当前缓冲并退出不阻塞-c清空缓冲这一步很关键。很多人在做问题复现时会忘记清空结果日志里混着上一次运行的内容浪费时间在错误的线索上。我的固定流程是清空日志 → 复现操作 →-d导出 → 搜索关键字。这样每次拿到的都是干净的一段。-v time让每行带上时间戳方便定位操作和日志的对应关系。还可以用-b指定缓冲区比如-b crash专门看崩溃日志这在定位闪退时非常好用adb logcat -b crash -d -v time另外如果日志里有中文出现乱码多半是终端编码问题把输出重定向到文件再用支持 UTF-8 的编辑器打开即可不要怀疑是设备的问题。4. 用 Python 把命令串成能跑的自动化脚本命令会敲了下一步是把它们组织起来。单条命令没有自动化可言只有能按顺序、按条件、可重复地执行一组动作才算入门。4.1 为什么选 Python adb 而不是一上来就上重框架我的建议是先别急着上重型框架。原因有三个。第一理解成本。用 Python 直接调 adb你能清楚看到每一步到底发了什么命令、拿到什么返回。用框架时这一步被封装了出问题你只能猜。先把底层跑通再去用框架你会用得更明白。第二依赖轻。Python 标准库就够用了subprocess负责执行命令re负责解析输出time负责等待不需要装一堆东西。在受限环境里这一点很关键。第三思路通用。等你把这套思路摸熟了换成任何框架无非是把自己封装的 tap换成框架提供的 click原理是相通的。4.2 封装一个够用的 Adb 工具类下面这个类是我自己在小项目里反复用的精简版去掉了很多没必要的抽象保留了最核心的能力。import subprocess import re import time class Adb: def __init__(self, serialNone, timeout30): self.serial serial self.timeout timeout def _base(self): cmd [adb] if self.serial: cmd [-s, self.serial] return cmd def run(self, args, timeoutNone, binaryFalse): cmd self._base() args p subprocess.run( cmd, capture_outputTrue, timeouttimeout or self.timeout, ) if binary: return p.returncode, p.stdout out p.stdout.decode(utf-8, ignore) err p.stderr.decode(utf-8, ignore) return p.returncode, out, err def shell(self, cmd, **kw): return self.run([shell] cmd.split(), **kw) def tap(self, x, y): self.shell(finput tap {x} {y}) def swipe(self, x1, y1, x2, y2, dur300): self.shell(finput swipe {x1} {y1} {x2} {y2} {dur}) def keyevent(self, code): self.shell(finput keyevent {code}) def start_app(self, pkg, activity): self.shell(fam start -n {pkg}/{activity}) def force_stop(self, pkg): self.shell(fam force-stop {pkg}) def clear_app(self, pkg): self.shell(fpm clear {pkg}) def screen_size(self): _, out, _ self.shell(wm size) m re.search(rOverride size:\s*(\d)x(\d), out) or \ re.search(rPhysical size:\s*(\d)x(\d), out) if not m: raise RuntimeError(f无法解析屏幕尺寸: {out}) return int(m.group(1)), int(m.group(2)) def screenshot(self, path): code, data self.run([exec-out, screencap, -p], binaryTrue) if code ! 0 or not data: raise RuntimeError(截图失败) with open(path, wb) as f: f.write(data) return path def dump_ui(self, remote/sdcard/ui.xml, local./ui.xml): self.shell(fuiautomator dump {remote}) self.run([pull, remote, local]) with open(local, r, encodingutf-8) as f: return f.read()这个类里有几个设计取舍值得解释。为什么用列表传参而不是拼接字符串subprocess传列表时不会经过 shell 解析避免了参数里带空格或特殊字符导致的意外也更安全。为什么固定用capture_output自动化脚本必须能把命令输出拿回来做判断否则和手动敲没区别。为什么截图单独走二进制通道前面说过screencap -p的输出是二进制用文本方式解码会破坏文件。为什么把wm size的结果优先取 Override size有些设备被人为改过显示尺寸比如为了模拟小屏这时候 Override size 才是实际渲染尺寸用 Physical size 算坐标会偏。这个细节在多分辨率测试里非常关键。4.3 元素定位uiautomator dump 与坐标换算纯 adb 没有元素这个概念它只认坐标。要拿到坐标最实用的办法是把界面结构 dump 成 XML再从里面解析。执行上面封装里的dump_ui你会拿到一段结构化的 XML里面每个节点都带着属性其中最有用的是这几个text节点显示的文字resource-id资源 ID通常形如com.example.app:id/btn_login最稳定class控件类型如android.widget.Buttonclickable是否可点击bounds节点在屏幕上的矩形范围形如[100,200][500,400]解析出 bounds 后中心点坐标就是def center_of(bounds): m re.match(r\[(\d),(\d)\]\[(\d),(\d)\], bounds) if not m: raise ValueError(fbounds 格式异常: {bounds}) x1, y1, x2, y2 map(int, m.groups()) return (x1 x2) // 2, (y1 y2) // 2拿到中心点再调tap(x, y)就能完成点击。找元素时优先用resource-id其次是text最后才考虑class加索引的方式。为什么要按这个优先级因为资源 ID 是开发在代码里定义的改动概率最低而同一类控件在页面上往往有多个单靠 class 定位很容易点错。如果开发没给关键控件设 ID可以直接提需求这比在脚本里写一堆脆弱的坐标要划算得多。还有一点要注意uiautomator dump返回的是当时的界面快照。如果你在页面还在加载时 dump拿到的可能是上一页的结构。所以标准动作是等待页面稳定 → dump → 解析 → 点击 → 再 dump 验证。这个循环虽然朴素但在没有框架的情况下已经足够可靠。4.4 一个完整用例冷启动、操作、断言、留证把前面的东西串起来写一个结构完整的用例。场景设定为冷启动应用进入登录页输入账号点击登录校验是否出现目标文案最后截图留证。import time import re def wait_for_text(adb, keyword, retries10, interval1.0): for _ in range(retries): xml adb.dump_ui() node find_node_by_text(xml, keyword) if node: return node time.sleep(interval) raise TimeoutError(f等待文本超时: {keyword}) def find_node_by_text(xml, text): pattern rnode[^]*text%s[^]*bounds(\[[^]\]) % re.escape(text) m re.search(pattern, xml) return m.group(1) if m else None def test_login(adb, pkg, activity, account): adb.clear_app(pkg) adb.start_app(pkg, activity) # 等首页稳定 wait_for_text(adb, 登录) # 定位输入框并点击 xml adb.dump_ui() bounds find_node_by_text(xml, 请输入账号) if not bounds: adb.screenshot(./fail_before_input.png) raise AssertionError(找不到账号输入框) x, y center_of(bounds) adb.tap(x, y) adb.shell(finput text {account}) # 点击登录按钮 xml adb.dump_ui() btn find_node_by_text(xml, 登录) if btn: bx, by center_of(btn) adb.tap(bx, by) # 断言结果 try: wait_for_text(adb, 首页, retries15) except TimeoutError: adb.screenshot(./fail_after_login.png) code, out, _ adb.run([logcat, -d, -v, time]) with open(./fail_log.txt, w, encodingutf-8) as f: f.write(out) raise adb.screenshot(./pass.png)这段代码里有几个我认为必须保留的习惯。失败必留证。截图、日志、界面结构三件事,至少在失败时全部保留。很多人写用例只关心断言通过没通过,结果线上偶现失败时手里什么都没有,只能靠猜。等待要有上限。wait_for_text里的 retries 是硬上限,不能写成无限循环。无限等待在自动化里是灾难,它会让整个任务挂死而不是干净地失败。清数据放在用例开头。pm clear能保证每次都是从干净状态开始,避免上一次运行的残留数据影响结果。代价是启动会慢一些,但可重复性比速度重要。坐标从 dump 结果算,不要写死。写死坐标的脚本换个分辨率就废了,这在多设备测试里是致命的。5. 高频踩坑与排查实录自动化测试的功夫,一半在写脚本,一半在解决问题。下面这些是我在实际操作中反复遇到的,按现象分类整理。5.1 unauthorized、offline、设备不识别unauthorized是最典型的一个。现象是adb devices显示设备存在但状态是 unauthorized,执行任何命令都报错。原因通常是手机上的授权弹窗没确认,或者之前的授权记录因为换了电脑、重装了系统而失效。处理顺序我一般是这样的:检查手机屏幕上有没有授权弹窗,有就勾选始终允许并确认。如果没有弹窗,进入开发者选项,点撤销 USB 调试授权,然后拔插数据线重新触发。还不行就adb kill-server,再adb start-server,重新插线。依旧不行,检查是不是装了两个版本不同的 adb,导致服务端和客户端版本不一致。offline的情况稍微复杂一点,可能是线材、可能是 adbd 进程卡死。我的处理顺序是:先换线,再adb kill-server adb start-server,再重启设备上的 adbd(adb reconnect device),最后才考虑重启设备。重启设备永远放最后,因为代价最高。完全识别不到设备时,在 Windows 上要重点查驱动。设备管理器里如果出现带感叹号的未知设备,基本可以确定是驱动问题,需要装对应厂商的 USB 驱动。macOS 和 Linux 上一般不需要额外驱动,Linux 下可能需要配置 udev 规则让普通用户有权限访问设备。5.2 点击无效、坐标偏移、等待时机命令执行成功但界面没反应是另一个高频问题。排查思路分三层。第一层,坐标是不是对的。前面提到过,屏幕尺寸要用 Override size 优先,而且input tap的坐标原点是屏幕左上角,单位是像素。如果你在脚本里用了 dp 或者按比例算但忘了乘,点击位置就会偏。第二层,时机是不是对的。点击发出去了,但目标控件还没渲染出来,或者被一层透明的遮罩挡住。这时候点击事件会被别的东西吃掉。解决办法是加等待,并且等待条件要基于界面状态判断,比如等到某个关键文本出现,而不是简单地 sleep 几秒。第三层,控件是不是真的可点。有些控件显示在屏幕上但不可交互,点击它会被父容器拦截。这时候可以通过 dump 出来的 XML 看节点的 clickable 属性,如果目标节点不可点,就往上找可点的父节点,点它的中心坐标。还有一类特殊情况:某些应用对快速连续点击做了限制,或者对点击的持续时间有要求。这时候可以把tap换成swipe,让起止点相同但持续 100 毫秒左右,模拟一次长一点的点击。5.3 logcat 抓不到、日志刷屏、中文乱码抓不到日志,先确认三件事:日志缓冲是不是被清空了、过滤条件是不是太严、应用是不是真的打了日志。我的固定流程是先用adb logcat -d -v time | head -50看一眼有没有内容,确认通道是通的,再加过滤条件。日志刷屏的问题在于没有过滤。logcat可以和 grep 组合,也可以直接用标签过滤。如果被测应用有自己的日志标签,优先用它,信息最干净。没有的话,可以按等级过滤,一般只看 Error 和 Warning 就能覆盖大部分问题。中文乱码基本是终端编码问题,把输出重定向到文件再打开就好。另外要注意,有些应用在 Release 包里会关闭详细日志,只有 Debug 包才有完整输出,排查时确认一下手里的包是哪种。5.4 问题速查表现象可能原因优先尝试的处理devices 列表为空线、驱动、调试开关、服务端异常换线、查开关、重启服务端unauthorized授权未确认或失效手机上确认、撤销授权重连offlineadbd 卡死、线材问题换线、reconnect、重启服务端tap 无反应坐标偏、时机早、控件不可点核对尺寸、加状态等待、点父节点截图打不开用了 shell 而非 exec-out改用 exec-out 取二进制input text 无效中文或含空格换剪贴板方案或改用框架输入dump 拿不到节点页面未加载完、有遮挡加等待、先关闭弹窗日志无内容缓冲被清、过滤太严、Release 包放宽过滤、确认包类型多设备命令发错未指定设备用 -s 参数指定序列号6. 从能跑到好用:稳定性、框架选型与团队落地把脚本跑通只是开始,真正难的是让它连续跑一百次不挂。这一节聊几个让它变稳的方向。6.1 adb 脚本与 Appium 这类框架的分工经常有人问,既然有 Appium,还有必要写 adb 脚本吗?我的看法是两者不是替代关系。Appium 这类框架的价值在于:统一的元素定位协议、跨平台(Android 和 iOS)一致的操作接口、更丰富的等待和重试机制、成熟的报告体系。它适合做规模化的、长期维护的用例集。adb 脚本的价值在于:轻量、直接、可控。它适合做设备层面的准备工作(装包、清数据、改设置、查状态)、做框架覆盖不到的系统级操作、做快速验证和临时排查。实际项目里,我通常把两者结合:用 adb 完成环境准备和结果采集,用框架完成界面交互和断言。这样各取所长,框架负责业务逻辑,adb 负责设备杂活。理解了 adb 的工作原理,你在用框架时遇到问题,也能更快判断是框架的问题还是设备的问题。6.2 稳定性三件套:等待、重试、清理我总结让脚本稳定的核心就三件事,缺一不可。第一是等待。所有涉及界面变化的操作后面,都要有基于状态判断的等待,不能只靠固定 sleep。等待条件建议选那种页面加载完成后一定会出现的稳定标识,比如首页特有的文本。第二是重试。网络请求、页面跳转这类天然带波动的操作,加有限次重试能显著提升通过率。重试次数不要太多,三次左右比较合适,太多会掩盖真实问题。重试之间要有间隔,并且最好在重试前先确认当前页面状态,避免在错误页面上盲目重试。第三是清理。每个用例开始前清数据、结束前停应用,能避免用例之间的相互干扰。我吃过这个亏:一组用例单独跑全通过,一起跑就有一半失败,最后发现是前一个用例登录后没退出,导致后一个用例状态不对。6.3 接进流水线时要注意的几件事把自动化跑进持续集成的流水线,有几个细节要提前想清楚。设备资源要管理好。多台设备跑并行时,每台设备要独占,不能两个任务抢同一台。可以用设备序列号做分配,跑之前先检查设备状态,跑完执行一次重连,避免上一轮残留状态影响下一轮。超时时间要设置合理。流水线里的任务最怕挂死,所以每一层都要有超时:命令层超时、用例层超时、任务层超时。任何一层卡住,都要能被上层强制结束并清理现场。结果要有明确的产物。不管成功失败,截图、日志、报告这些都要产出并保存,失败时还要额外保留现场。没有产物的自动化,出了问题等于白跑。还有就是尽量在流水线里固定设备机型组合和系统版本,变量越多,波动越难定位。兼容性覆盖可以分批次做,不要在一次任务里堆太多种设备。我在实际操作中的体会是,adb 这个东西最容易被低估。很多人把它当成一个装包工具用完就扔,但真正把它用起来之后,你会发现它能覆盖的场景比想象中多得多:从设备准备、界面操作、结果采集到问题排查,几乎每个环节都能用上。花两三天把常用命令和这套封装思路摸熟,后面无论用哪个框架,都会觉得心里有底。最后再分享一个小习惯:我给自己建了一个命令速查文件,每次踩坑解决完之后,把命令和原因补一行进去,半年下来这份文件比任何教程都好用,因为里面全是我自己真正遇到的问题。
网站建设高端定制企业官网