1. 不只是投屏:scrcpy在UI自动化测试中的核心价值

很多朋友第一次接触scrcpy,可能和我当初一样,只是把它当作一个“把手机屏幕显示在电脑上”的免费工具。确实,它有线无线都能连,延迟低画质好,用来演示或者录个屏非常方便。但如果你是一名测试工程师,或者正在为安卓应用的自动化测试发愁,那scrcpy的价值就远不止于此了。它完全可以成为你测试工具箱里的一把瑞士军刀。

我在实际项目中,尤其是在进行UI自动化测试时,经常遇到几个头疼的问题。比如,测试脚本运行时,手机屏幕是黑的,你只能通过日志去猜到底发生了什么,一旦测试失败,复现和定位问题非常困难。再比如,需要同时测试多台设备时,来回切换查看屏幕状态简直是一场噩梦。而scrcpy恰好能完美解决这些痛点。它能把测试过程“可视化”,让你实时看到手机上的每一步操作,这对于调试自动化脚本、验证UI交互逻辑来说,是无可替代的。更重要的是,scrcpy本身就是一个控制通道,它不仅能“看”,还能通过电脑的鼠标键盘去“操控”手机,这为我们将它集成到自动化流程中提供了无限可能。

所以,这篇进阶指南,我想和你分享的,就是如何把scrcpy从一个简单的投屏工具,升级为UI自动化测试的强大助力。我们会深入探讨多设备并行管理、自定义按键映射实现复杂操作、以及如何将录屏无缝整合到测试报告中。我会结合具体的Python脚本示例,让你看完就能上手,真正提升你的测试效率。别担心,即使你之前只用过最基础的scrcpy命令,跟着我的步骤走,也能轻松掌握这些高阶技巧。

2. 环境搭建与基础命令回顾:为自动化打好地基

在玩转高级功能之前,我们得先把基础打牢。scrcpy的安装非常简单,去它的GitHub发布页下载对应操作系统的压缩包,解压就能用。Windows用户会得到一个包含scrcpy.exeadb.exe的文件夹。我习惯把这个文件夹的路径添加到系统的环境变量PATH里,这样在任何命令行窗口都能直接调用scrcpy命令了,非常方便。

确保你的安卓手机已经开启了“开发者选项”和“USB调试”。用数据线连接电脑后,在命令行输入adb devices,如果能看到你的设备号,后面跟着device字样,就说明连接成功了。这时,直接输入scrcpy,你的手机屏幕应该就出现在电脑窗口里了。这是最基础的用法。

但对于自动化测试,我们往往需要更精细的控制。下面这几个参数是我几乎每次都会用到的,它们构成了我们后续所有高级操作的基础:

# 限制投屏分辨率,降低资源占用,特别适合多设备同时运行
scrcpy --max-size=1024

# 限制帧率,同样是为了性能,测试场景对流畅度要求不高
scrcpy --max-fps=15

# 关闭窗口边框,让投屏窗口更干净,便于程序化布局
scrcpy --window-borderless

# 指定窗口标题,当同时打开多个设备窗口时,便于区分
scrcpy --window-title='测试机-小米12'

# 不传输音频,测试场景通常不需要声音,可以节省资源
scrcpy --no-audio

你可能注意到了,我一直在强调“性能”和“区分”。这是因为在自动化测试环境中,我们通常是在服务器或一台性能较强的电脑上同时运行多个测试实例。每个实例占用更少的CPU和内存,意味着我们能并行测试更多的设备。而给窗口加上独特的标题,则是多设备管理的第一步,一眼就能认出谁是谁。把这些基础命令组合起来,就已经能形成一个很实用的测试启动命令了:scrcpy --max-size=1024 --max-fps=15 --window-borderless --window-title='Device_A' --no-audio

3. 多设备并行测试:高效管理你的测试机群

单个设备的测试效率毕竟有限。当你的应用需要覆盖多种机型、多种系统版本时,手动一台台测试会耗费大量时间。利用scrcpy进行多设备并行投屏与控制,就能让测试效率成倍提升。这里的关键在于adb对多设备的识别和scrcpy的-s(指定设备)参数。

首先,你需要将所有测试手机连接到同一个网络(Wi-Fi),并逐一开启“无线调试”功能。更常见的做法是,先用USB线连接每台手机,执行adb tcpip 5555命令将其切换到TCP/IP模式,然后拔掉数据线。接着,在电脑上使用adb connect <手机IP>:5555来无线连接每一台设备。连接成功后,运行adb devices,你会看到一个列表,里面包含了所有已连接的设备,每个设备都有一个唯一的标识符(可能是IP地址,也可能是USB的序列号)。

现在,为每台设备启动一个独立的scrcpy投屏窗口就很简单了:

# 启动设备A的投屏,并指定窗口标题和低分辨率
scrcpy -s 192.168.1.101:5555 --window-title="小米12" --max-size=1024

# 在另一个终端或脚本中,启动设备B的投屏
scrcpy -s 192.168.1.102:5555 --window-title="华为P50" --max-size=1024

但手动开多个命令行窗口显然不是自动化的作风。我们可以用Python的subprocess模块来优雅地管理这个过程。下面这个脚本示例,可以读取一个设备配置文件,然后批量启动所有设备的投屏,并将每个进程管理起来:

import subprocess
import threading
import json

def start_scrcpy_for_device(device_id, device_name):
    """为指定设备启动scrcpy进程"""
    command = [
        'scrcpy',
        '-s', device_id,
        '--window-title', f'TestDevice_{device_name}',
        '--max-size', '1024',
        '--max-fps', '15',
        '--window-borderless',
        '--no-audio'
    ]
    print(f"启动设备: {device_name} ({device_id})")
    # 启动进程,不阻塞主线程
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    return process

if __name__ == '__main__':
    # 假设有一个devices.json配置文件,里面是设备列表
    with open('devices.json', 'r') as f:
        device_list = json.load(f)

    processes = []
    for device in device_list:
        proc = start_scrcpy_for_device(device['id'], device['name'])
        processes.append((device['name'], proc))

    # 这里可以继续执行你的自动化测试主逻辑...
    # 例如,使用uiautomator2或Appium向各个设备发送测试指令

    input("按回车键结束所有投屏...")
    # 测试结束后,终止所有scrcpy进程
    for name, proc in processes:
        proc.terminate()
        print(f"已终止设备 {name} 的投屏")

通过这种方式,你可以在一个控制台上,同时监控多台设备的测试执行情况,任何一台设备上的UI异常都能被立即发现。这比单纯依赖日志输出要直观和高效得多。

4. 自定义按键映射:将复杂操作一键化

在UI自动化测试中,我们经常需要模拟一些固定的、复杂的操作序列。比如,在测试一个游戏时,可能需要反复执行“跳跃-攻击-移动”的组合;或者在测试一个文件管理器时,需要执行“长按文件-点击菜单-选择删除-确认”这一套流程。如果每次都要脚本去精确计算屏幕坐标并模拟点击,不仅编写麻烦,而且在不同分辨率设备上容易出错。

scrcpy提供了一个强大的功能:自定义按键映射。你可以将电脑键盘上的某个按键,映射为手机屏幕上的一系列触摸事件(点击、长按、滑动等)。这个功能原本是为游戏玩家设计的,但在测试中同样大放异彩。

实现自定义按键映射,你需要创建一个.scrcpy文件夹(通常在用户目录下),并在里面放置一个scrcpy-server文件(从scrcpy的GitHub仓库获取)和一个按键映射文件,例如my_test_map.json。映射文件的格式如下:

[
  {
    "key": "Ctrl+Shift+A",
    "action": "touch",
    "x": "0.5",
    "y": "0.8",
    "duration": "100"
  },
  {
    "key": "F1",
    "action": "swipe",
    "fromX": "0.9",
    "fromY": "0.5",
    "toX": "0.1",
    "toY": "0.5",
    "duration": "200"
  }
]

这个例子定义了两个映射:按下Ctrl+Shift+A,会在手机屏幕横向中点(x=0.5)、纵向80%的位置(y=0.8)模拟一个持续100毫秒的触摸(相当于点击);按下F1,会模拟一个从屏幕右侧滑到左侧的快速滑动动作,持续200毫秒。坐标使用的是比例值(0.0到1.0),这样就完美适配了不同分辨率的设备。

启动scrcpy时,通过--keyboard=uhid(模拟物理键盘输入)和--shortcut-mod参数来加载你的映射文件:

scrcpy --keyboard=uhid --shortcut-mod=lctrl,lalt --key-map=/path/to/my_test_map.json

在自动化脚本中,我们可以利用Python的pyautoguipynput库,来模拟按下这些定义好的键盘快捷键,从而触发手机上对应的复杂操作。这相当于为你的测试脚本增加了一个“宏命令”层,让脚本的逻辑更清晰,也更易于维护。比如,你可以把“登录”这个操作映射到F2键,然后在需要登录的地方,脚本只需要“按下F2”即可,无需关心底层具体的点击坐标。

5. 自动化录屏与测试报告整合:让Bug无处可藏

测试报告如果只有文字日志和截图,有时候还是难以还原某些偶发性的、或与操作时序相关的Bug。一段完整的操作录屏,就是最有力的证据。scrcpy的录屏功能不仅质量高,而且可以轻松集成到自动化流程中。

最基础的录屏命令是scrcpy -r output.mp4。但自动化测试需要的是精准控制:在测试用例开始时录屏,在用例结束时停止,并且将录制的视频文件与对应的测试用例关联起来。

这里有一个非常重要的技巧:不要直接关闭scrcpy窗口来停止录屏。这可能会导致视频文件损坏。正确的做法是,向scrcpy进程发送一个Ctrl+C信号(在命令行中),或者使用adb shell pkill -f scrcpy命令来终止手机端的scrcpy-server。在我们的Python脚本中,可以这样实现:

import subprocess
import time
import signal
import os

class ScrcpyRecorder:
    def __init__(self, device_id, test_case_name):
        self.device_id = device_id
        self.video_path = f"./reports/video_{test_case_name}_{int(time.time())}.mp4"
        self.process = None

    def start_recording(self):
        """开始录屏"""
        # 确保报告目录存在
        os.makedirs(os.path.dirname(self.video_path), exist_ok=True)
        command = [
            'scrcpy',
            '-s', self.device_id,
            '-r', self.video_path,
            '--no-audio',
            '--max-size', '800'  # 报告视频不需要太高清,节省空间
        ]
        print(f"开始录屏,视频将保存至: {self.video_path}")
        # 注意:这里使用Popen并让其在后台运行,不捕获输出,避免阻塞
        self.process = subprocess.Popen(command, start_new_session=True)
        # 等待一下确保录屏启动
        time.sleep(2)

    def stop_recording(self):
        """优雅地停止录屏"""
        if self.process:
            print("正在停止录屏...")
            # 向进程组发送SIGINT信号(模拟Ctrl+C)
            os.killpg(os.getpgid(self.process.pid), signal.SIGINT)
            self.process.wait(timeout=5)  # 等待进程结束,最多等5秒
            print(f"录屏已停止,视频文件: {self.video_path}")
        return self.video_path

# 在测试用例中的使用示例
def test_user_login():
    recorder = ScrcpyRecorder('192.168.1.101:5555', 'test_user_login')
    try:
        recorder.start_recording()
        # 这里是你的实际测试步骤:启动APP、输入账号密码、点击登录...
        # 例如使用 uiautomator2: device(text="登录").click()
        time.sleep(5)  # 模拟测试执行时间
        # 假设这里断言登录成功
        assert True
    finally:
        # 无论测试成功还是失败,都停止录屏并保存视频
        video_file = recorder.stop_recording()
        # 你可以将video_file路径写入你的测试报告(如Allure、pytest-html报告)
        # 例如,使用Allure报告:allure.attach.file(video_file, name='操作录屏', attachment_type=allure.attachment_type.MP4)

将这段录屏逻辑封装成一个装饰器或者pytestfixture,你就可以非常方便地为每一个重要的测试用例自动录制视频。最后,在生成的HTML测试报告中,每个用例旁边都会有一个视频链接,评审人员点击即可观看测试全过程,定位问题的效率会得到质的飞跃。

6. 结合Python实现全自动控制:打造闭环测试流程

前面我们分别讲了多设备管理、按键映射和自动录屏。现在,让我们把这些珍珠串成项链,结合Python脚本,打造一个从环境准备、测试执行到报告收集的全自动闭环测试流程。这里我会使用uiautomator2这个优秀的Python库作为设备控制的核心,因为它与adb和scrcpy同源,兼容性非常好。

假设我们有一个简单的测试场景:在多台设备上同时安装APK,运行一个冒烟测试,并收集每台设备的录屏和日志。整个脚本的骨架会是这样的:

import uiautomator2 as u2
import subprocess
import threading
import time
from pathlib import Path

class AutomatedTestOrchestrator:
    def __init__(self, device_configs):
        self.devices = []
        for config in device_configs:
            d = {
                'id': config['id'],
                'name': config['name'],
                'scrcpy_proc': None,
                'u2_device': u2.connect(config['id']),
                'test_result': None,
                'video_path': None
            }
            self.devices.append(d)

    def setup_environment(self):
        """步骤1:为所有设备启动投屏并安装APK"""
        threads = []
        for d in self.devices:
            t = threading.Thread(target=self._setup_single_device, args=(d,))
            t.start()
            threads.append(t)
        for t in threads:
            t.join()
        print("所有设备环境准备就绪。")

    def _setup_single_device(self, device):
        # 启动scrcpy投屏
        cmd = f"scrcpy -s {device['id']} --window-title={device['name']} --max-size=1024 --window-borderless --no-audio"
        device['scrcpy_proc'] = subprocess.Popen(cmd, shell=True)
        time.sleep(2)
        # 使用uiautomator2安装APK
        device['u2_device'].app_install('http://your-server/app-under-test.apk')
        print(f"{device['name']}: APK安装完成。")

    def run_tests(self):
        """步骤2:在所有设备上并行执行测试用例"""
        threads = []
        for device in self.devices:
            t = threading.Thread(target=self._run_test_on_device, args=(device,))
            t.start()
            threads.append(t)
        for t in threads:
            t.join()

    def _run_test_on_device(self, device):
        d = device['u2_device']
        recorder = ScrcpyRecorder(device['id'], f"{device['name']}_smoke_test")
        try:
            recorder.start_recording()
            # 开始测试
            d.app_start("com.example.app")
            time.sleep(3)
            # 示例:点击ID为btn_login的按钮
            d(resourceId="btn_login").click()
            time.sleep(2)
            # 更多测试步骤...
            # 假设测试通过
            device['test_result'] = 'PASS'
        except Exception as e:
            device['test_result'] = 'FAIL'
            print(f"{device['name']} 测试失败: {e}")
        finally:
            device['video_path'] = recorder.stop_recording()
            # 收集日志
            logcat = d.shell('logcat -d -v time').output
            Path(f"./reports/{device['name']}_logcat.txt").write_text(logcat)

    def teardown(self):
        """步骤3:清理环境,生成报告"""
        for device in self.devices:
            if device['scrcpy_proc']:
                device['scrcpy_proc'].terminate()
            # 卸载APP
            device['u2_device'].app_uninstall("com.example.app")
        # 这里可以调用函数,将所有设备的 test_result, video_path, 日志 整合到一个HTML报告中
        self.generate_html_report()
        print("测试完成,报告已生成。")

    def generate_html_report(self):
        # 简化的报告生成逻辑,实际可使用Jinja2模板
        html_content = "<html><body><h1>多设备测试报告</h1><table border='1'>"
        for d in self.devices:
            html_content += f"""
            <tr>
                <td>{d['name']}</td>
                <td>{d['test_result']}</td>
                <td><a href='{d['video_path']}'>查看录屏</a></td>
                <td><a href='./reports/{d['name']}_logcat.txt'>查看日志</a></td>
            </tr>
            """
        html_content += "</table></body></html>"
        Path("./reports/report.html").write_text(html_content)

# 主程序
if __name__ == '__main__':
    configs = [
        {'id': '192.168.1.101:5555', 'name': 'Xiaomi12'},
        {'id': '192.168.1.102:5555', 'name': 'HuaweiP50'},
    ]
    orchestrator = AutomatedTestOrchestrator(configs)
    orchestrator.setup_environment()
    orchestrator.run_tests()
    orchestrator.teardown()

这个框架虽然简化,但清晰地展示了思路:用线程池管理多设备并行,用scrcpy提供可视化监控和录屏证据,用uiautomator2执行精准的UI操作,最后将所有产出物打包成一份完整的测试报告。你可以根据自己项目的实际需求,填充具体的测试逻辑,例如加入更复杂的用例管理、失败重试机制、性能数据监控等。

在实际使用中,我发现这套组合拳特别适合做兼容性测试、冒烟测试和回归测试。它极大地减少了人工干预,让测试人员能够更专注于测试用例的设计和复杂问题的分析,而不是重复的执行操作。希望这些从实战中总结出来的技巧,能帮你真正解锁scrcpy在自动化测试领域的潜力,让测试工作变得更加高效和可靠。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐