os_util 工具类和方法的实现
一、前置说明
- 总体目录:《从 0-1 搭建企业级 APP 自动化测试框架》
- 上节回顾:在 init_appium_and_devices 的实现思路分析 小节中,分析了实现
init_appium_and_devices
的思路,梳理出了必要的工具类和方法。 - 本节目标:完成
os_util
模块工具类和方法的编写,为具体实现做准备。 - 其它说明:
- 工具类和方法,是实现目的的手段,不是框架的重点;
- 可以使用
ChatGPT
工具辅助实现工具类和方法,验证即可; - 对工具类和方法,进行分类整理,使用途更明确。
二、代码实现
utils/os_util.py
import os
import platform
import logging
import shutil
import subprocess
import timeimport psutil
import win32gui
import win32processlogger = logging.getLogger(__name__)class RunCMDError(Exception):...class OSName:@propertydef os_name(self):return platform.system()def is_windows(self):return self.os_name.lower() == 'windows'def is_linux(self):return self.os_name.lower() == 'linux'def is_mac(self):return self.os_name.lower() == 'darwin'osname = OSName()class CMDRunner:@staticmethoddef run_command(command, timeout=5, retry_interval=0.5, expected_text=''):"""执行命令,并等待返回结果。无论是执行成功还是执行失败,都会返回结果。:param command: 待执行的命令:param timeout: 超时时间该参数的作用说明:- 在连续执行多条命令时,会遇到:因为执行速度过快,上一条命令未执行完成就执行了下一条命令,导致下一条命令执行失败的问题;- 因此需要增加失败重跑的机制:只要未执行成功,就进入失败重跑,直到到达指定的超时时间为止。:param retry_interval: 失败重试的间隔等待时间:param expected_text: 期望字符串,如果期望字符串为真,则以"字符串是否在输出结果中"来判断是否执行成功;该参数的作用举例说明:- 如果你在命令行中输入:adb connect 127.0.0.1:7555- 返回的结果是:* daemon not running; starting now at tcp:5037* daemon started successfullycannot connect to 127.0.0.1:7555: 由于目标计算机积极拒绝,无法连接。 (10061)- 虽然命令已执行成功,但是并没有达到 "连接成功" 的预期;- 此时,你可以使用 run_command('adb connect 127.0.0.1:62001', timeout=60, expected_text='already connected to 127.0.0.1:7555'):return: (output, status), 返回输出结果和状态,无论是成功还是失败,都会返回输出结果和状态"""# 定义失败重跑的截止时间end_time = time.time() + timeoutattempts = 0while True:try:# subprocess.run() 方法用于执行命令并等待其完成,然后返回一个 CompletedProcess 对象,该对象包含执行结果的属性,# 它适用于需要等待命令完成并获取结果的情况。result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)if result.returncode == 0:# 如果 returncode==0,表示执行成功output = result.stdout.strip()# 如果期望字符串为真,则通过"字符串是否在输出结果中"来判断是否执行成功status = expected_text in output if expected_text else True# 通常情况下,执行成功时,命令行不会返回任何结果,此时result为'',因此添加这个逻辑output = output or 'successful'else:# 否则,从result.stderr.strip() 获取 outputoutput = result.stderr.strip()status = expected_text in output if expected_text else False# 如果 status 为真,则返回 (output, status), 否则进入 while 循环,进行失败重试if status:logger.debug(f"Execute adb command successfully: {command}"f"\nOutput is: "f"\n=========================="f"\n{output}"f"\n==========================")return output, statusexcept subprocess.TimeoutExpired as e:logger.warning(f"Command timed out: {e}")output, status = '', Falseexcept Exception as e:logger.warning(f"Unexpected error while executing command: {e}")output, status = '', False# 添加等待时间,等待上一条命令执行完成time.sleep(retry_interval)attempts += 1logger.debug(f'Execute adb command failure: {command}'f'\nRetrying... Attempt {attempts}')# 如果超过截止时间,则跳出循环if time.time() > end_time:breaklogger.warning(f"Execute adb command failure: {command}"f"\nOutput is: "f"\n=========================="f"\n{output}"f"\n==========================")return output, statusdef run_command_strict(self, command, timeout=5, retry_interval=0.5, expected_text=''):"""严格执行命令,如果执行失败,则抛出RunCMDError异常;如果执行成功,则返回输出结果.:param command: 待执行的命令:param timeout: 超时时间:param retry_interval: 失败重试的间隔等待时间:param expected_text: 期望字符串:return: output, 返回命令执行成功之后的输出结果"""output, status = self.run_command(command, timeout, retry_interval, expected_text)if not status:raise RunCMDError(output)return outputcmd_runner = CMDRunner()class PortManager:@staticmethoddef is_port_in_use(port) -> bool:"""判断指定端口是否被占用"""try:# 获取当前系统中所有正在运行的网络连接connections = psutil.net_connections()# 检查是否有连接占用指定端口for conn in connections:if conn.laddr.port == int(port):logger.debug(f"Port {port} is in use.")return Truelogger.debug(f"Port {port} is not in use.")return Falseexcept Exception as e:# 处理异常,例如权限不足等情况logger.error(f"Error checking port {port}: {e}")return Falsedef generate_available_ports(self, start_port, count):"""生成可用的端口号:param start_port: 起始端口号:param count: 生成端口的个数:return: 列表"""available_ports = []port = start_portwhile len(available_ports) < count:if not self.is_port_in_use(port):available_ports.append(port)port += 1return available_portsport_manager = PortManager()class ProcessManager:@staticmethoddef find_pid_by_window_name(window_name):"""根据窗口名称查找对应的进程ID列表"""pids = [] # 存储找到的进程ID列表system = platform.system() # 获取操作系统类型if system == "Windows":# 使用 win32gui 库查找窗口def callback(hwnd, hwnd_list):try:if win32gui.IsWindowVisible(hwnd): # 检查窗口是否可见window_text = win32gui.GetWindowText(hwnd) # 获取窗口标题文本if window_name.lower() in window_text.lower(): # 如果窗口标题包含指定的窗口名称(不区分大小写)_, pid = win32process.GetWindowThreadProcessId(hwnd) # 获取窗口对应的进程IDhwnd_list.append(pid) # 将进程ID添加到列表中except:passreturn Truewin32gui.EnumWindows(callback, pids) # 枚举所有窗口,并调用回调函数进行查找else:# 在 macOS 和 Linux 上使用 psutil 库查找进程 PIDfor proc in psutil.process_iter(['pid', 'name']): # 遍历所有进程,并获取进程的PID和名称try:process_name = proc.info['name'].lower() # 获取进程名称(转换为小写)if window_name.lower() in process_name: # 如果进程名称包含指定的窗口名称(不区分大小写)pids.append(proc.info['pid']) # 将进程ID添加到列表中except (psutil.NoSuchProcess, psutil.AccessDenied):passreturn pids@staticmethoddef find_pid_by_port(port):"""根据端口号查找对应的进程ID列表"""pids = []for conn in psutil.net_connections():try:if conn.laddr.port == port:pids.append(conn.pid)except (psutil.NoSuchProcess, psutil.AccessDenied):passreturn pids@staticmethoddef find_pid_by_process_name(process_name):"""根据进程名称查找对应的进程ID列表"""pids = []for proc in psutil.process_iter(['pid', 'name']):try:if process_name.lower() in proc.info['name'].lower():pids.append(proc.info['pid'])except (psutil.NoSuchProcess, psutil.AccessDenied):passreturn pids@staticmethoddef kill_process_by_pid(pid):"""根据进程PID查杀进程。此方法是 kill_process_by_port、kill_process_by_name、kill_process_by_window_name 的底层方法。"""try:process = psutil.Process(pid)process.kill()logger.debug(f"Killed process with PID {process.pid}")# 杀死进程时,可能会遇到权限等问题except Exception as e:logger.debug(f"Failed to kill process: {e}")def kill_process_by_port(self, port):"""根据端口号,查杀进程"""pids = self.find_pid_by_port(int(port))for pid in pids:self.kill_process_by_pid(pid)def kill_process_by_name(self, process_name):"""根据进程名,查杀进程"""pids = self.find_pid_by_process_name(process_name)for pid in pids:self.kill_process_by_pid(pid)def kill_process_by_window_name(self, window_name):"""根据窗口名称,查杀进程"""pids = self.find_pid_by_window_name(window_name)for pid in pids:self.kill_process_by_pid(pid)process_manager = ProcessManager()class ApplicationManager:@staticmethoddef check_app_installed(app_name):"""使用 shutil.which() 方法来检查应用是否已安装,可适用于所有平台。只会从Path环境变量中判断,如果已安装但未设置为环境变量,会返回False."""if shutil.which(app_name) is not None:return Trueelse:return False@staticmethoddef set_environment_variable(key, value):"""设置系统变量,比如设置: JAVA_HOME、ANDROID_HOME"""os.environ[key] = value@staticmethoddef set_path_env_variable(path):"""设置Path环境变量warning: 这种方式设置环境变量,在windows平台会遇到1024截断的问题, todo"""if 'PATH' in os.environ:os.environ['PATH'] = path + os.pathsep + os.environ['PATH']else:os.environ['PATH'] = path@staticmethoddef start_application(executable_path, is_run_in_command=False):"""启动应用程序,但不必等待其启动完成。:param executable_path: 可执行文件的路径,比如: .exe文件:param is_run_in_command: 是否要在命令行中启动,比如:在命令行中输入 `appium` 启动 appium server"""try:if osname.is_windows():if is_run_in_command:# 如果程序需要在命令行中启动,则需要需要使用 start 启动命令行窗口command = f"start {executable_path}"else:command = executable_path# subprocess.Popen() 方法用于启动命令,但不必等待其完成,# 这对于需要启动长时间运行的程序或不需要等待程序完成的情况非常有用, 例如等待命令完成、发送信号等。subprocess.Popen(command, shell=True)logger.debug(f"Started program '{executable_path}'")else:if is_run_in_command:# 类Unix系统使用nohup命令启动程序command = f"nohup {executable_path} &"else:command = executable_pathsubprocess.Popen(command, shell=True, preexec_fn=os.setsid)logger.debug(f"Started program '{executable_path}'")except Exception as e:logger.warning(f"Error starting program '{executable_path}': {str(e)}")application_manager = ApplicationManager()if __name__ == '__main__':logging.basicConfig(level=logging.DEBUG)# 按窗口名称查杀 uiautomatorviewerprocess_manager.kill_process_by_window_name('automator')# 按端口号杀进 appiumprocess_manager.kill_process_by_port(4723)# 按进程名称查杀夜神模拟器process_manager.kill_process_by_name('nox.exe')
三、要点小结
请注意:
- 工具类和方法,是实现目的的手段,不是框架的重点内容;
- 可以使用
ChatGPT
工具辅助实现工具类和方法,验证即可; - 对工具类和方法,进行分类整理,使用途更明确。
- 以本节为例,以上所有方法都与系统操作相关,因此将模块文件命名为 os_util.py
- 再将方法进行分类:CMDRunner、PortManager、ProcessManager、ApplicationManager
点击返回主目录
相关文章:
os_util 工具类和方法的实现
一、前置说明 总体目录:《从 0-1 搭建企业级 APP 自动化测试框架》上节回顾:在 init_appium_and_devices 的实现思路分析 小节中,分析了实现 init_appium_and_devices 的思路,梳理出了必要的工具类和方法。本节目标:完…...
uview表单校验带星号
uView表单校验带星号可以通过设置required属性来实现。在uView中,可以使用组件来实现表单校验,具体步骤如下: 1、在需要校验的表单元素上添加required属性,例如: <u-form :model"detailInfo" ref"d…...

vue+element实现动态表格:根据后台返回的属性名和字段动态生成可变表格
现有一个胡萝卜厂生产不同品种的胡萝卜,为了便于客户了解产品,现需在官网展示胡萝卜信息。现有的萝卜信息:编号(id)、名称(name)、保质期(age)、特点(remark&…...

云渲染UE4像素流送搭建(winows、ubuntu单实例与多实例像素流送)
windows/ubuntu20.4下UE4.27.2像素流送 像素流送技术可以将服务器端打包的虚幻引擎应用程序在客户端的浏览器上运行,用户可以通过浏览器操作虚幻引擎应用程序,客户端无需下载虚幻引擎,本文实现两台机器通过物理介质网线实现虚幻引擎应用程序…...

Unity VR Pico apk安装失败:INSTALL_FAILED_UPDATE_INCOMPATIBLE
我的报错: PICO4企业版。安装apk,报错“安装失败。(所属的Unity项目打包的apk,被我在同一台pico4安装了20次) 调试方法: PIco4发布使用UNITY开发的Vr应用,格式为apk,安装的时候发生…...
Prompt 提示工程学习笔记
一、Prompt设计的四个关键要素: 任务描述、输入数据、上下文信息、提示风格 (1)任务描述:描述想要让LLM遵循的指令。描述应详细清晰,可进一步使用关键词突出特殊设置,从而更好地指导LLM工作。 ࿰…...

STM32实现三个小灯亮
led.c #include"led.h"void Led_Init(void) {GPIO_InitTypeDef GPIO_VALUE; //???RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//???GPIO_VALUE.GPIO_ModeGPIO_Mode_Out_PP;//???? ????GPIO_VALUE.GPIO_PinGPIO_Pin_1|GPIO_Pin_2|GPIO_P…...

1861_什么是H桥
Grey 全部学习内容汇总: GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1861_什么是H桥 H桥电路可以…...

【计算机四级(网络工程师)笔记】操作系统运行机制
目录 一、中央处理器(CPU) 1.1CPU的状态 1.2指令分类 二、寄存器 2.1寄存器分类 2.2程序状态字(PSW) 三、系统调用 3.1系统调用与一般过程调用的区别 3.2系统调用的分类 四、中断与异常 4.1中断 4.2异常 🌈嗨ÿ…...

Swagger快速入门
1、Swagger快速入门 1.1 swagger介绍 官网:https://swagger.io/ Swagger 是一个规范和完整的Web API框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。 功能主要包含以下几点: A. 使得前后端分离开发更加方便,有利于团队协作…...

数据结构之<堆>的介绍
1.简介 堆是一种特殊的数据结构,通常用于实现优先队列。堆是一个可以被看作近似完全二叉树的结构,并且具有一些特殊的性质,根据这些性质,堆被分为最大堆(或者大根堆,大顶堆)和最小堆两种。 2.…...

使用Ubuntu22+Minikube快速搭建K8S开发环境
安装Vmware 这一步,可以参考我的如下课程。 安装Ubuntu22 下载ISO镜像 这里我推荐从清华镜像源下载,速度会快非常多。 下载地址:https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04.3/ 如果你报名了我的这门视频课程…...

【中小型企业网络实战案例 二】配置网络互连互通
【中小型企业网络实战案例 一】规划、需求和基本配置-CSDN博客 热门IT技术视频教程:https://xmws-it.blog.csdn.net/article/details/134398330?spm1001.2014.3001.5502 配置接入层交换机 1.以接入交换机ACC1为例,创建ACC1的业务VLAN 10和20。 <…...

Azure Machine Learning - Azure OpenAI GPT 3.5 Turbo 微调教程
本教程将引导你在Azure平台完成对 gpt-35-turbo-0613 模型的微调。 关注TechLead,分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师&…...

运维大模型探索之 Text2PromQL 问答机器人
作者:陈昆仪(图杨) 大家下午好,我是来自阿里云可观测团队的算法工程师陈昆仪。今天分享的主题是“和我交谈并获得您想要的PromQL”。今天我跟大家分享在将AIGC技术运用到可观测领域的探索。 今天分享主要包括5个部分:…...
虚拟机VMware:变动ip修改固定ip
1、配置ip地址 vi /etc/sysconfig/network-scripts/ifcfg-ens33修改为: 修改如下:TYPE"Ethernet" # 网络类型为以太网 BOOTPROTO"static" # 手动分配ip NAME"ens33" # 网卡…...

Docker部署Nexus Maven私服并实现远程访问Nexus界面
🎬 鸽芷咕:个人主页 🔥 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 1. Docker安装Nexus2. 本地访问Nexus3. Linux安装Cpolar4. 配置Nexus界面公网地址5. 远程访问 Nexus界面6. 固定N…...

苏州科技大学计算机817程序设计(java) 学习笔记
之前备考苏州科技大学计算机(专业课:817程序设计(java))。 学习Java和算法相关内容,现将笔记及资料统一整理归纳移至这里。 部分内容不太完善,欢迎提议。 目录 考情分析 考卷题型 刷题攻略…...

虚幻学习笔记22—C++同步和异步加载
一、前言 之前提到的静态和动态加载都是同步的加载,同时其中的引用基本都是硬引用。如果资源比较大的话会出现卡顿的现象,下面将介绍一种异步加载的方式。同时,还将介绍一种区别与之前的Load的方法。 在说明同步和异步加载之前需要先讲一下虚…...

华清远见嵌入式学习——ARM——作业3
作业要求: 代码效果图: 代码: led.h #ifndef __LED_H__ #define __LED_H__#define RCC_GPIO (*(unsigned int *)0x50000a28) #define GPIOE_MODER (*(unsigned int *)0x50006000) #define GPIOF_MODER (*(unsigned int *)0x50007000) #defi…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...

企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...