Appium自动化常用adb操作封装

一、前置说明

在Appium自动化中,经常需要使用adb命令与设备进行交互,所以有必要把常用的adb操作封装成一个类

二、代码实现

python 复制代码
import os
import platform
import re
import subprocess

from common import path
from common.exception import AndroidSDKUninstalledError, AndroidDevicesNotFoundError
from common.logger import logger


class ADBRunner:

    def __init__(self):
        self._check_adb_is_installed()

    @staticmethod
    def run_adb(command):
        try:
            result = subprocess.run(command, capture_output=True, text=True, check=True, shell=True)
            logger.debug(f"Execute adb command: {command}")
            return result.stdout.strip()
        except subprocess.CalledProcessError as e:
            logger.error(f"Execute adb command failure: {e}")
            return None

    def _check_adb_is_installed(self):
        result = self.run_adb("adb --version")
        if not result:
            raise AndroidSDKUninstalledError('Android SDK is not installed or configured.')
        return result

    def adb_connect_device(self, device):
        return self.run_adb(f'adb connect {device}')

    def get_connected_device_udids(self):
        """
        获取所有连接设备的序列号udid
        """
        res = self.run_adb('adb devices')
        pattern = r'\b((?!of\b)\S+)\s+device'
        devices = re.findall(pattern, res)
        if not devices:
            raise AndroidDevicesNotFoundError('No connected mobile devices found.')
        logger.info(f'Devices found: {devices}')
        return devices

    @property
    def _grep(self):
        if platform.system() == 'Windows':
            return 'findstr'
        else:
            return 'grep'

    def get_activities(self, udid=None):
        """
        获取当前设备的所有top activities, 输出结果示例:
        ['com.android.settings/.Settings c4e2e17 pid=3637',
        'com.mumu.launcher/.Launcher 9392da7 pid=1434',
        'com.android.browser/com.android.settings de68b73 pid=3722']
        """
        if not udid:
            udid = self.get_connected_device_udids()[0]
        command = f'adb -s {udid} shell dumpsys activity top | {self._grep} ACTIVITY'
        tops = self.run_adb(command).split('ACTIVITY')
        tops = [top.strip(' ').strip('\n') for top in tops if top]
        logger.debug(f'The top activities on device {udid} are: {tops}')
        return tops

    def get_app_package_and_activity(self, udid=None):
        """
        从 com.android.settings/.Settings c4e2e17 pid=3637,获取包名和活动页面名称
        输出:['com.android.settings', '.Settings']
        用途:
            capabilities = {
                "platformName": "Android",
                "automationName": "uiautomator2",
                "deviceName": "9YS0220306003185",
                "appPackage": "com.tencent.mm",  # 包名
                "appActivity": ".ui.LauncherUI",  # 活动页面名称

            }
        """
        last_activity_info = self.get_activities(udid)[-1]
        pattern = r'(\S+) (\S+) pid=(\d+)'
        match = re.match(pattern, last_activity_info)
        if match:
            package, activity = match.group(1).split('/')
            return [package, activity]
        else:
            raise

    def get_apk_path(self, package_name, udid=None):
        """
        从设备中使用包名,获取应用程序的APK路径。
        """
        if not udid:
            udid = self.get_connected_device_udids()[0]

        # 使用pm path命令获取应用程序的APK路径
        command = f'adb -s {udid} shell pm path {package_name}'
        result = self.run_adb(command)

        if result and result.startswith('package:'):
            apk_path = result.replace('package:', '').strip()
            return apk_path
        else:
            logger.error(f"APK path for package '{package_name}' not found on the device {udid}.")
            return None

    def get_apk_version(self, package_name, udid=None):
        """
        获取应用程序的版本号。
        """
        if not udid:
            udid = self.get_connected_device_udids()[0]

        # 使用dumpsys package命令获取应用程序的版本号
        command = f'adb -s {udid} shell dumpsys package {package_name} | {self._grep} versionCode'
        result = self.run_adb(command)

        match = re.search(r'versionCode=(\d+)', result)
        if match:
            version_code = match.group(1)
            return version_code
        else:
            logger.error(f"Version code not found for package '{package_name}'.")
            return None

    def pull_apk_from_device(self, package_name, output_dir=None, apk_name=None, udid=None):
        """
        根据包名从当前设备中将应用程序的APK文件复制至本地。
        用于:
            capabilities = {
                "platformName": "Android",
                "automationName": "uiautomator2",
                "deviceName": "9YS0220306003185",
                "app": apk_path,  # 用在这里
                # "appPackage": "com.tencent.mm",
                # "appActivity": ".ui.LauncherUI",
            }
        """
        if not udid:
            udid = self.get_connected_device_udids()[0]

        if not output_dir:
            output_dir = path.get_apk_resources_dir()

        # 获取应用程序的APK路径
        apk_path = self.get_apk_path(package_name)
        apk_name = apk_name if apk_name else package_name
        apk_version = self.get_apk_version(package_name)

        if apk_path:
            # 构建本地输出路径
            local_output_path = os.path.join(output_dir, f"{apk_name}_{apk_version}.apk")

            # 使用adb pull命令将APK复制到本地
            command = f'adb -s {udid} pull {apk_path} {local_output_path}'
            result = self.run_adb(command)

            if result:
                logger.info(f"APK successfully pulled to: {local_output_path}")
                return local_output_path
            else:
                logger.error("Failed to pull APK from device.")


if __name__ == '__main__':
    adb = ADBRunner()
    print(adb.get_connected_device_udids())
    print(adb.get_app_package_and_activity())
    print(adb.get_apk_path('cn.com.open.mooc'))
    print(adb.get_apk_version('cn.com.open.mooc'))
    print(adb.pull_apk_from_device('cn.com.open.mooc'))

欢迎技术交流:

相关推荐
代码敲上天.7 小时前
数据库语句优化
android·数据库·adb
Elastic 中国社区官方博客11 小时前
Elasticsearch:使用 LLM 实现传统搜索自动化
大数据·人工智能·elasticsearch·搜索引擎·ai·自动化·全文检索
看山还是山,看水还是。13 小时前
MySQL 管理
数据库·笔记·mysql·adb
人工智能的苟富贵20 小时前
Android Debug Bridge(ADB)完全指南
android·adb
南种北李1 天前
Linux自动化构建工具Make/Makefile
linux·运维·自动化
IG工程师1 天前
关于 S7 - 1200 通过存储卡进行程序更新
经验分享·笔记·自动化
怒放的生命.1 天前
电器自动化入门08:隔离变压器、行程开关介绍及选型
运维·自动化·电气自动化·电工基础
我爱学Python!1 天前
基于 LangChain 的自动化测试用例的生成与执行
人工智能·自然语言处理·langchain·自动化·llm·测试用例·大语言模型
ZOHO项目管理软件2 天前
自动化分配客服售后工单的高效策略
运维·自动化
JasonLiu19192 天前
论文推荐 |【Agent】自动化Agent设计系统
人工智能·自动化·llm·agent·智能体