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'))

欢迎技术交流:

相关推荐
2501_938782092 小时前
《Ubuntu 系统下 MySQL 安装前的环境检查与依赖准备指南》
hive·mysql·ubuntu·adb
Java 码农2 小时前
mysql8.4.6 LTS 主从架构搭建
mysql·adb·架构
初学者_xuan3 小时前
零基础新手小白快速了解掌握服务集群与自动化运维(十六)集群部署模块——Keepalived双机热备
运维·自动化·github
半梦半醒*4 小时前
k8s——资源管理
linux·运维·docker·容器·kubernetes·自动化
敲代码的嘎仔5 小时前
JavaWeb零基础学习Day5——MySQL
java·数据库·学习·程序人生·mysql·adb·改行学it
小白学大数据6 小时前
Python爬虫定时任务:自动化抓取豆瓣每日最新短评
爬虫·python·自动化
首发运维6 小时前
certbot+shell+阿里云api+k8s实现自动化更新SSL证书
阿里云·kubernetes·自动化
运维李哥不背锅9 小时前
Ansible 的变量与模板:实现更灵活的自动化配置
java·自动化·ansible
守城小轩15 小时前
基于Chrome140的TK账号自动化(关键词浏览)——脚本撰写(二)
自动化·rpa·浏览器自动化·tk自动化·rpa自动化
2501_9387738715 小时前
从 0 到 1 搭建 TikTok 自动化系统:内容、投放、客服全环节自动化实践
运维·自动化