1. 项目概述为什么要在手机上搞自动化测试如果你是一名移动端开发者或者测试工程师对“自动化测试”这个词肯定不陌生。我们通常的做法是在一台性能强劲的电脑上连接一台或多台手机通过ADBAndroid Debug Bridge发送指令配合Appium、Airtest这类框架模拟用户点击、滑动等操作。这套流程成熟、稳定社区支持也丰富。但不知道你有没有遇到过这样的场景手头没有电脑或者电脑不在身边但你又急需验证某个功能在特定手机型号上的表现或者你希望搭建一个成本极低、可以7x24小时运行的自动化监控脚本专门用来“养号”或者检查某个App的稳定性。这时候传统的“电脑手机”模式就显得有些笨重了。“Termux uiautomator2”这个组合就是为了解决这个痛点而生的。它的核心思路是把测试引擎从电脑“搬”到手机本身。Termux是一个强大的Android终端模拟器和Linux环境应用它能在你的手机上提供一个近乎完整的Linux命令行环境。而uiautomator2则是Python语言中一个非常流行的Android UI自动化测试库它底层调用的是Android系统自带的UIAutomator框架性能稳定兼容性好。当这两者结合就意味着你的手机自己就能运行Python脚本通过uiautomator2库来操控自己的屏幕实现真正的“自驱动”自动化。这听起来可能有点“左脚踩右脚上天”的感觉但实际用起来你会发现它的应用场景非常具体且实用。比如你可以写一个脚本让备用手机在凌晨自动打开某个电商App完成签到、领金币等日常任务或者让一台旧手机持续运行你的App定时截图并上传到服务器实现简单的云真机监控再比如在没有电脑的出差途中快速写个脚本验证一下UI改动。它剥离了对PC的依赖让自动化测试变得更轻量、更灵活。当然它并非要取代传统的自动化方案而是作为一种有力的补充在特定场景下发挥独特价值。2. 环境搭建在Termux中构筑Python自动化堡垒万事开头难环境搭建是第一步也是最容易踩坑的一步。很多人在这一步就被劝退了其实只要理清步骤完全可以一气呵成。2.1 Termux基础配置与包管理首先你需要从F-Droid或Google Play商店安装Termux。这里有个小建议优先从F-Droid获取。因为Google Play版本的Termux可能更新不及时而F-Droid的版本通常更稳定且社区支持更好。安装后打开你会看到一个纯粹的命令行界面。第一步是更新软件源并升级基础包。Termux默认的包管理器是pkg它类似于Debian系的apt。执行以下命令pkg update pkg upgrade -y这个命令会同步软件源列表并升级所有已安装的包。过程中可能会询问是否继续输入y回车即可。这里有个注意事项Termux的存储空间是隔离的它的“家目录”在/data/data/com.termux/files/home而不是手机存储根目录。所以你的脚本和项目文件最好就放在这个目录下或者使用termux-setup-storage命令来在存储中创建一个符号链接方便访问手机存储。termux-setup-storage执行后会在家目录生成一个storage文件夹链接到手机的共享存储空间。接下来安装Python。虽然Termux源里有Python但为了获得更完整的开发体验我们直接安装Python3和必要的开发工具pkg install python python-pip clang make libffi libjpeg-turbo openssl -y安装clang编译器是为了后续某些Python包特别是需要编译的能顺利构建。libffi和openssl是很多网络相关库的依赖。2.2 安装与配置uiautomator2Python环境准备好后就可以安装核心的uiautomator2库了。这里直接用pip安装pip install --upgrade uiautomator2这个命令会安装uiautomator2及其依赖。安装过程可能会比较慢耐心等待即可。安装完成后最关键的一步来了在Termux环境中初始化uiautomator2。这与在PC上操作有本质区别。在PC上我们通常运行python -m uiautomator2 init来给连接的手机安装一个辅助APKatx-agent。但在Termux里我们的目标是控制本机。因此我们需要一个特殊的初始化方式。首先确保你的手机已开启“开发者选项”和“USB调试”。然后在Termux中执行python -m uiautomator2 init --server这个--server参数是关键它告诉uiautomator2以服务器模式运行而不是尝试通过USB连接外部设备。命令执行后它会尝试在你的手机上安装一个名为com.github.uiautomator的测试服务APK。如果安装失败可能因为缺少ADB权限你可以手动下载该APK安装。更常见的做法是我们利用uiautomator2的“本地模式”。实际上在Termux中更稳定、更推荐的方式是使用weditor进行元素定位并通过adb shell命令来模拟uiautomator2的部分功能或者直接使用uiautomator2的connect方法连接本机。但首先我们需要确保Termux能调用ADB。2.3 解决Termux中的ADB权限问题这是整个环境搭建中最大的挑战。在Android系统中ADB命令通常需要较高的系统权限。Termux作为一个普通应用默认没有权限直接执行adb shell等命令。我们有几种思路无线ADB调试在手机设置中开启“无线调试”并记下配对码和端口。然后我们可以尝试在Termux内部连接自己。这需要手机系统支持Android 11及以上并且步骤稍显繁琐。使用Android的run-as命令需Root如果你的手机已Root可以在Termux中尝试run-as命令来提权但这不稳定且不通用。利用uiautomator2的HTTP服务这是最优雅的解决方案。uiautomator2的atx-agent会在手机启动一个HTTP服务默认端口7912。我们可以让脚本通过HTTP协议与本机的这个服务通信从而绕过ADB权限限制。具体操作是先确保atx-agent服务在运行。你可以通过其他自动化工具比如在PC上初始化一次确保服务已安装并运行。然后在Termux的Python脚本中使用以下方式连接设备import uiautomator2 as u2 # 连接本地设备通过设备的本地IP通常是127.0.0.1和atx-agent端口 d u2.connect(127.0.0.1:7912) # 或者使用adb的无线调试端口如果已开启 # d u2.connect(手机无线调试IP:端口)如果connect失败你可能需要先通过PC一次性安装并启动atx-agent服务之后只要手机不重启该服务通常会持续运行。实操心得对于非Root手机最稳妥的流程是先用电脑通过USB连接手机执行一次python -m uiautomator2 init将atx-agent服务安装并启动到手机上。然后断开电脑在Termux中就可以通过127.0.0.1:7912这个地址来连接和控制了。这相当于把“驱动”提前装好Termux里的脚本只负责发送“指令”。3. 核心原理与脚本编写让手机自己“动”起来环境搭好我们就进入了核心环节编写自动化脚本。这里我们不仅要写代码更要理解在Termux这个特殊环境下uiautomator2是如何工作的。3.1 uiautomator2在Termux中的工作模式在标准PC环境中uiautomator2、测试脚本、ADB都在PC上手机是被控端。而在我们的方案里脚本执行引擎Python解释器和被控设备手机系统处于同一台物理设备但属于不同的进程空间。uiautomator2库通过HTTP协议与运行在手机系统层面的atx-agent服务通信。atx-agent再通过Android系统提供的Instrumentation框架调用UIAutomator API来操作UI。这个过程可以类比为你的脚本在Termux的Linux容器里是一个“指挥官”它通过无线对讲机HTTP向驻扎在手机Android系统里的“特种部队”atx-agent下达指令“特种部队”再直接操作手机界面。这种架构的优势是稳定不依赖USB连接劣势是会有微小的网络通信延迟并且需要确保“特种部队”一直在线。3.2 编写你的第一个自动化脚本让我们从一个最简单的脚本开始解锁屏幕打开设置然后返回桌面。import uiautomator2 as u2 import time # 连接到本地设备 try: d u2.connect(127.0.0.1:7912) print(设备连接成功) except Exception as e: print(f连接失败: {e}) # 可以尝试备用方案比如通过adb shell启动uiautomator2服务 exit(1) # 唤醒屏幕如果已锁屏 d.screen_on() # 等待屏幕完全唤醒 time.sleep(1) # 模拟滑动解锁这里以图案密码为例需要根据你的实际锁屏方式调整 # d.swipe_points([(x1, y1), (x2, y2), ...]) # 九宫格解锁坐标 # 更通用的方式是使用keyevent事件 d.press(home) # 先按一次HOME键确保回到锁屏界面 time.sleep(0.5) # 输入密码假设是数字密码这里需要开启“直接通过ADB输入”等安全设置不推荐在生产环境用 # d.send_keys(123456) # 我们假设设备未锁屏或已通过其他方式解锁 # 打开设置应用 d.app_start(com.android.settings) print(已启动设置) # 等待设置应用加载 time.sleep(2) # 可以做一些操作例如滚动 d.swipe(500, 1500, 500, 500, 0.5) # 从下往上滑动 # 最后按HOME键返回桌面 d.press(home) print(已返回桌面)这个脚本涵盖了连接设备、唤醒屏幕、启动应用、滑动和模拟按键等基本操作。在Termux中运行这个脚本python your_script_name.py你会看到手机屏幕自己动了起来这里有个非常重要的注意事项由于脚本在操作手机界面请务必确保手机屏幕是常亮的并且没有设置锁屏密码或使用无需密码的解锁方式如滑动否则自动化流程会在解锁环节中断。你可以在手机设置中为自动化测试专门设置一个不锁屏的用户配置文件。3.3 元素定位与交互精准操作的基石自动化测试的核心是找到界面上的元素并与之交互。uiautomator2提供了多种定位方式ResourceId定位d(resourceIdcom.android.settings:id/search_action_bar)文本定位d(textWLAN)或d(descriptionWi-Fi)类名定位d(classNameandroid.widget.TextView)组合定位d(classNameandroid.widget.Switch, resourceIdandroid:id/switch_widget)在Termux环境下我们如何获取这些元素属性呢强烈推荐使用weditor。weditor是uiautomator2配套的Web界面元素查看器。虽然在Termux里直接运行weditor的Web服务可能比较困难涉及内网穿透但你可以在一台和手机在同一局域网的电脑上运行weditor然后连接到手机的atx-agent服务。在电脑上安装并运行pip install weditor python -m weditor浏览器会自动打开。在顶部的设备地址栏输入你的手机IP和端口如192.168.1.100:7912即可实时获取手机界面的UI层次结构像Chrome开发者工具一样方便地定位元素。实操心得定位元素时优先使用resourceId因为它通常是唯一且稳定的。其次是text和description。尽量避免使用className和index因为它们容易随UI改动而变化。对于动态内容如列表项可以结合child、sibling等选择器进行相对定位。4. 实战案例构建一个自动化签到脚本理论说得再多不如一个实战案例来得直观。假设我们要为一个虚构的“每日福利”App实现自动签到。该App的签到按钮在首页签到后会有弹窗提示。4.1 需求分析与脚本设计我们的脚本需要完成以下任务确保手机屏幕点亮并处于解锁状态。启动“每日福利”App包名假设为com.example.dailybonus。等待App首页加载完成。查找并点击“签到”按钮。处理可能的弹窗如“签到成功”提示。截图保存签到结果。退出App。此外我们还需要考虑异常处理比如网络加载慢、按钮未及时出现、弹窗样式变化等。4.2 分步实现与代码详解下面是完整的脚本实现包含了详细的注释和异常处理逻辑import uiautomator2 as u2 import time import os from datetime import datetime class DailyBonusAutoSign: def __init__(self, device_address127.0.0.1:7912): 初始化连接设备 :param device_address: atx-agent服务地址 self.d None self.device_address device_address self.screenshot_dir /data/data/com.termux/files/home/storage/pictures/AutoSign # 创建截图保存目录 if not os.path.exists(self.screenshot_dir): os.makedirs(self.screenshot_dir) def connect_device(self): 连接设备 try: self.d u2.connect(self.device_address) print(f[{self._now()}] 成功连接到设备: {self.d.info}) return True except Exception as e: print(f[{self._now()}] 连接设备失败: {e}) return False def ensure_screen_on(self): 确保屏幕点亮 if not self.d.info.get(screenOn): self.d.screen_on() print(f[{self._now()}] 已唤醒屏幕) time.sleep(2) # 等待屏幕完全亮起 def start_app(self, package_name): 启动应用 print(f[{self._now()}] 正在启动应用: {package_name}) self.d.app_start(package_name) # 等待应用冷启动 time.sleep(5) def wait_for_element(self, selector, timeout10): 等待元素出现 :param selector: uiautomator2选择器如 {text: 签到} :param timeout: 超时时间秒 :return: 找到的元素对象超时返回None start_time time.time() while time.time() - start_time timeout: element self.d(**selector) if element.exists: return element time.sleep(1) print(f[{self._now()}] 等待元素超时: {selector}) return None def click_sign_button(self): 查找并点击签到按钮 # 尝试多种可能的定位方式提高容错性 selectors [ {text: 签到}, {text: 立即签到}, {resourceId: com.example.dailybonus:id/sign_in_btn}, {description: 签到}, ] for selector in selectors: btn self.wait_for_element(selector, timeout5) if btn: print(f[{self._now()}] 找到签到按钮使用定位器: {selector}) btn.click() return True print(f[{self._now()}] 未找到任何签到按钮) return False def handle_popup(self): 处理签到成功弹窗 # 常见的弹窗关闭按钮定位 close_selectors [ {text: 确定}, {text: 知道了}, {text: 关闭}, {resourceId: android:id/button1}, # 确定按钮ID ] time.sleep(2) # 等待弹窗动画 for selector in close_selectors: close_btn self.d(**selector) if close_btn.exists: print(f[{self._now()}] 找到并关闭弹窗: {selector}) close_btn.click() return True # 如果没有找到按钮尝试按返回键关闭弹窗 self.d.press(back) print(f[{self._now()}] 尝试按返回键关闭弹窗) return False def take_screenshot(self, prefixsign_success): 截图并保存 filename f{prefix}_{self._now(format%Y%m%d_%H%M%S)}.png filepath os.path.join(self.screenshot_dir, filename) self.d.screenshot(filepath) print(f[{self._now()}] 截图已保存: {filepath}) return filepath def run(self): 主执行流程 if not self.connect_device(): return self.ensure_screen_on() # 1. 启动App self.start_app(com.example.dailybonus) # 2. 点击签到 if not self.click_sign_button(): # 签到失败截图记录 self.take_screenshot(sign_failed) print(f[{self._now()}] 签到流程失败) return # 3. 处理弹窗 self.handle_popup() # 4. 签到成功截图 self.take_screenshot() # 5. 返回桌面可选 self.d.press(home) print(f[{self._now()}] 签到流程完成已返回桌面) def _now(self, format%H:%M:%S): 获取当前时间字符串 return datetime.now().strftime(format) if __name__ __main__: bot DailyBonusAutoSign() bot.run()4.3 脚本优化与定时执行上面的脚本已经可以工作但我们可以让它更健壮、更自动化。优化点1增加状态检查。在点击签到前可以先检查一下今天是否已经签过到了。可以通过查找“已签到”状态的文本来判断。def already_signed(self): 检查是否已签到 signed_indicator self.d(text已签到) return signed_indicator.exists优化点2使用更智能的等待。wait_for_element可以改为使用uiautomator2内置的wait方法它支持更丰富的条件。btn self.d(text签到).wait(timeout10.0) # 等待最多10秒 if btn: btn.click()优化点3在Termux中定时执行。这才是体现Termux价值的场景。我们可以利用Linux的cron服务来定时运行脚本。首先在Termux中安装croniepkg install cronie然后编辑cron任务crontab -e在打开的编辑器中添加一行例如每天上午9点15分执行我们的脚本假设脚本路径为~/auto_sign.py15 9 * * * cd /data/data/com.termux/files/home /data/data/com.termux/files/usr/bin/python auto_sign.py ~/sign.log 21保存退出。然后启动cron服务crond这样每天指定时间Termux就会自动运行签到脚本并将日志输出到sign.log文件中。注意事项Termux在手机锁屏或休眠后进程可能会被系统挂起或终止。为了确保cron服务长期运行你可能需要将Termux添加到手机系统的“电池优化白名单”或“后台运行白名单”中不同手机品牌设置路径不同一般在“设置-电池-应用耗电管理”里找。5. 高级技巧与疑难排坑实录在实际使用中你一定会遇到各种各样的问题。下面是我踩过的一些坑和总结出的解决方案。5.1 元素定位失败uiautomatorviewer能找到uiautomator2找不到这是一个经典问题。原因通常有几个界面层级变化有些App使用了SurfaceView、WebView或游戏引擎渲染原生UIAutomator无法识别其中的元素。对于WebView需要先使用d.webview()切换到WebView上下文才能定位。动态ID或文本元素的resourceId或text是动态生成的每次打开都不一样。解决方案是使用其他稳定属性如className结合instance索引或者使用XPath如果支持。uiautomator2对XPath支持有限可以尝试d.xpath(//android.widget.TextView[text登录])。等待时间不足元素还没加载出来就开始查找。一定要用wait方法或者显式time.sleep给予足够时间。更好的做法是等待某个特定元素出现作为页面加载完成的标志。atx-agent服务异常服务可能卡死或崩溃。尝试重启服务在Termux中可以用adb shell命令如果可用重启atx-agent或者简单粗暴地重启手机。排查步骤首先用weditor再次确认元素属性。其次在脚本中加入大量d.debug True打印信息或者直接使用d.dump_hierarchy()将当前页面XML布局保存到文件分析。最后检查连接是否稳定。5.2 脚本在后台运行时操作失效当Termux切换到后台或者手机锁屏后脚本对屏幕的操作可能会失效。这是因为Android系统为了省电会限制后台应用的活动。解决方案1推荐使用d.set_fastinput_ime(True)启用快速输入模式并尝试使用d.send_keys()输入文本而不是依赖屏幕点击。对于点击可以尝试使用d.click(x, y)绝对坐标但稳定性欠佳。解决方案2申请后台运行权限。在手机设置中为Termux应用开启“自启动”、“关联启动”、“后台弹出界面”等权限不同品牌手机名称不同。最根本的是关闭电池优化进入手机设置 - 应用管理 - Termux - 电池 - 选择“无限制”。解决方案3使用d.screen_on()保持屏幕常亮但这会非常耗电。可以折中在脚本执行期间保持亮屏执行完毕后再关闭。5.3 性能优化与稳定性提升当脚本越来越复杂运行时间变长时稳定性就至关重要。减少不必要的截图d.screenshot()操作比较耗时只在关键成功/失败节点截图。使用更精准的选择器宽泛的选择器如d(classNameandroid.view.View)会遍历大量节点降低效率。尽量使用resourceId或组合选择器缩小范围。加入重试机制对于关键操作如点击按钮使用循环重试几次。def click_with_retry(self, selector, retries3): for i in range(retries): if self.d(**selector).exists: self.d(**selector).click() time.sleep(1) # 等待操作响应 # 检查操作是否成功例如判断新页面元素出现 if self.operation_success(): return True print(f第{i1}次点击尝试失败) time.sleep(2) return False日志记录不要只用print将日志写入文件方便后续排查。可以使用Python内置的logging模块。5.4 常见错误代码与解决方法速查表错误现象可能原因解决方案ConnectionRefusedError: [Errno 111] Connection refusedatx-agent服务未运行1. 检查设备地址是否正确 (127.0.0.1:7912)。2. 通过PC重新运行python -m uiautomator2 init安装并启动服务。3. 重启手机。RuntimeError: USB device xxxx not found脚本尝试使用USB模式连接确保连接代码使用的是u2.connect(127.0.0.1:7912)而不是u2.connect_usb()或u2.connect()默认尝试USB。元素存在但click()无效1. 元素非可点击状态。2. 坐标偏移。3. 点到了其他遮挡层。1. 使用element.info[clickable]检查。2. 尝试element.click.wait(timeout10)。3. 使用element.click.gone(maxretry3)等待元素消失作为点击成功的标志。4. 改用element.tap()。SessionNotCreatedExceptionatx-agent会话异常重启atx-agent服务adb shell am force-stop com.github.uiautomator然后重新启动。脚本突然停止无错误Termux进程被系统清理1. 按5.2方案设置后台权限。2. 考虑使用Android的“工作资料”或“免打扰模式”运行Termux。3. 将关键脚本拆分成循环执行的小任务。uiautomator2.exceptions.UiObjectNotFoundError超时未找到元素1. 增加wait超时时间。2. 检查选择器是否正确用weditor复核。3. 确认操作后页面是否成功跳转。6. 扩展思路不止于自动化测试掌握了“Termux uiautomator2”的基本用法后你的思路可以打开它远不止用于简单的App测试。场景一自动化数据收集与监控。你可以编写脚本定时打开新闻App抓取头条新闻的标题和摘要保存到本地文件或发送到服务器。结合requests库就能构建一个简单的移动端信息采集器。场景二自动化重复性手机操作。比如自动清理微信缓存需辅助功能权限、自动备份特定App的聊天记录到指定位置、每天定时给家人发送问候消息等。这些操作虽然可以通过Tasker等工具实现但用Python脚本编写更灵活、更强大。场景三与其他Termux服务联动。Termux可以安装Python的Web框架如Flask、数据库如SQLite等。你可以打造一个“手机服务器”让uiautomator2脚本作为执行器接收来自Web界面的指令实现远程控制手机完成特定任务。一个简单的远程控制示例 在Termux中安装Flaskpip install flask编写一个简单的API服务 (server.py)from flask import Flask, request import uiautomator2 as u2 import threading app Flask(__name__) d u2.connect(127.0.0.1:7912) app.route(/click, methods[POST]) def click_element(): data request.json selector data.get(selector, {}) # 例如 {text: 确定} if selector: element d(**selector) if element.exists: element.click() return {status: success, message: fClicked {selector}} else: return {status: error, message: Element not found}, 404 return {status: error, message: No selector provided}, 400 if __name__ __main__: # 在Termux中监听所有网络接口端口5000 app.run(host0.0.0.0, port5000, debugFalse)运行这个脚本你的手机就变成了一个Web服务器。在同一局域网下的电脑或另一台手机通过浏览器或curl发送一个POST请求如http://手机IP:5000/clickJSON数据为{selector: {text: 确定}}就能远程控制手机点击“确定”按钮了。当然这只是一个极简的演示实际应用中需要加入身份验证、任务队列、更丰富的指令集等。但从这个例子可以看出将Termux的Linux环境能力与uiautomator2的UI操控能力结合能创造出许多有趣且实用的自动化方案把一台普通的安卓手机变成高度智能的自动化工具。