# actions.py 主自动化脚本 import os import logging import time import subprocess from appium import webdriver from appium.options.android import UiAutomator2Options from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException import globals.ids as ids import globals.global_variable as global_variable # 导入全局变量模块 import permissions # 导入权限处理模块 import globals.apis as apis from globals.driver_utils import init_appium_driver, ensure_appium_server_running, safe_quit_driver, is_app_launched, launch_app_manually from page_objects.login_page import LoginPage from page_objects.download_tabbar_page import DownloadTabbarPage from page_objects.screenshot_page import ScreenshotPage from page_objects.upload_config_page import UploadConfigPage from page_objects.more_download_page import MoreDownloadPage # 配置日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s: %(message)s", handlers=[ logging.FileHandler("appium_automation.log"), logging.StreamHandler() ] ) class DeviceAutomation: @staticmethod def get_device_id() -> str: # """ # 获取设备ID,优先使用已连接设备,否则使用全局配置 # """ # try: # # 检查已连接设备 # result = subprocess.run( # ["adb", "devices"], # capture_output=True, # text=True, # timeout=10 # ) # # 解析设备列表 # for line in result.stdout.strip().split('\n')[1:]: # if line.strip() and "device" in line and "offline" not in line: # device_id = line.split('\t')[0] # logging.info(f"使用已连接设备: {device_id}") # global_variable.GLOBAL_DEVICE_ID = device_id # return device_id # except Exception as e: # logging.warning(f"设备检测失败: {e}") """ 获取设备ID,优先使用无线连接设备,否则尝试开启无线调试,最后使用全局配置 """ try: # 检查已连接设备 result = subprocess.run( ["adb", "devices"], capture_output=True, text=True, timeout=10 ) # 解析设备列表 wireless_device_id = None usb_device_id = None # 先查找无线连接的设备(IP:端口格式) for line in result.stdout.strip().split('\n')[1:]: if line.strip() and "device" in line and "offline" not in line: current_device = line.split('\t')[0] # 检查是否为IP:端口格式的无线连接 if ":" in current_device and any(char.isdigit() for char in current_device): wireless_device_id = current_device logging.info(f"使用无线连接设备: {wireless_device_id}") global_variable.GLOBAL_DEVICE_ID = wireless_device_id return wireless_device_id else: # 记录第一个USB连接的设备 if not usb_device_id: usb_device_id = current_device # 如果没有找到无线连接的设备,尝试使用USB设备开启无线调试 if not wireless_device_id and usb_device_id: logging.info(f"未找到无线连接设备,尝试使用USB设备 {usb_device_id} 开启无线调试") # 尝试获取设备IP地址 try: import re import time ip_result = subprocess.run( ["adb", "-s", usb_device_id, "shell", "ip", "-f", "inet", "addr", "show", "wlan0"], capture_output=True, text=True, timeout=10 ) # 解析IP地址 ip_output = ip_result.stdout if "inet " in ip_output: # 提取IP地址 ip_match = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', ip_output) if ip_match: device_ip = ip_match.group(1) logging.info(f"获取到设备IP地址: {device_ip}") # 开启无线调试 tcpip_result = subprocess.run( ["adb", "-s", usb_device_id, "tcpip", "5555"], capture_output=True, text=True, timeout=10 ) if "restarting in TCP mode port: 5555" in tcpip_result.stdout: logging.info("无线调试已开启,端口: 5555") # 等待几秒钟让设备准备好 time.sleep(3) # 连接到无线设备 connect_result = subprocess.run( ["adb", "connect", f"{device_ip}:5555"], capture_output=True, text=True, timeout=10 ) if "connected to" in connect_result.stdout: logging.info(f"成功连接到无线设备: {device_ip}:5555") global_variable.GLOBAL_DEVICE_ID = f"{device_ip}:5555" return f"{device_ip}:5555" else: logging.warning(f"连接无线设备失败: {connect_result.stderr}") logging.info(f"使用USB设备: {usb_device_id}") global_variable.GLOBAL_DEVICE_ID = usb_device_id return usb_device_id else: logging.warning(f"开启无线调试失败: {tcpip_result.stderr}") logging.info(f"使用USB设备: {usb_device_id}") global_variable.GLOBAL_DEVICE_ID = usb_device_id return usb_device_id else: logging.warning("未找到设备IP地址") logging.info(f"使用USB设备: {usb_device_id}") global_variable.GLOBAL_DEVICE_ID = usb_device_id return usb_device_id else: logging.warning("无法获取设备IP地址,可能设备未连接到WiFi") logging.info(f"使用USB设备: {usb_device_id}") global_variable.GLOBAL_DEVICE_ID = usb_device_id return usb_device_id except Exception as e: logging.warning(f"开启无线调试时出错: {str(e)}") logging.info(f"使用USB设备: {usb_device_id}") global_variable.GLOBAL_DEVICE_ID = usb_device_id return usb_device_id except Exception as e: logging.warning(f"设备检测失败: {e}") # 使用全局配置 device_id = global_variable.GLOBAL_DEVICE_ID logging.info(f"使用全局配置设备: {device_id}") return device_id def __init__(self, device_id=None): # 如果没有提供设备ID,则自动获取 if device_id is None: self.device_id = self.get_device_id() else: self.device_id = device_id # 初始化权限 if permissions.grant_appium_permissions(self.device_id): logging.info(f"设备 {self.device_id} 权限授予成功") else: logging.warning(f"设备 {self.device_id} 权限授予失败") # 确保Appium服务器正在运行 ensure_appium_server_running(4723) # 初始化驱动 self.init_driver() # 先拼接,后创建测试结果目录 self.results_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_results') os.makedirs(self.results_dir, exist_ok=True) def init_driver(self): """初始化Appium驱动""" try: # 使用全局函数初始化驱动 self.driver, self.wait = init_appium_driver(self.device_id) # 初始化页面对象 logging.info(f"设备 {self.device_id} 开始初始化页面对象") self.login_page = LoginPage(self.driver, self.wait) self.download_tabbar_page = DownloadTabbarPage(self.driver, self.wait, self.device_id) self.screenshot_page = ScreenshotPage(self.driver, self.wait, self.device_id) self.upload_config_page = UploadConfigPage(self.driver, self.wait, self.device_id) self.more_download_page = MoreDownloadPage(self.driver, self.wait,self.device_id) logging.info(f"设备 {self.device_id} 所有页面对象初始化完成") # 检查应用是否成功启动 if is_app_launched(self.driver): logging.info(f"设备 {self.device_id} 沉降观测App已成功启动") else: logging.warning(f"设备 {self.device_id} 应用可能未正确启动") # 手动启动应用 launch_app_manually(self.driver, self.device_id) except Exception as e: logging.error(f"设备 {self.device_id} 初始化驱动失败: {str(e)}") raise def is_app_launched(self): """检查应用是否已启动""" try: return is_app_launched(self.driver) except Exception as e: logging.error(f"设备 {self.device_id} 检查应用启动状态时出错: {str(e)}") return False def is_element_present(self, by, value): """检查元素是否存在""" try: self.driver.find_element(by, value) return True except NoSuchElementException: return False def handle_app_state(self): """根据当前应用状态处理相应的操作""" try: login_btn_exists = self.login_page.is_login_page() if not login_btn_exists: logging.error(f"设备 {self.device_id} 未知应用状态,无法确定当前页面,跳转到登录页面") if self.navigate_to_login_page(self.driver, self.device_id): logging.info(f"设备 {self.device_id} 成功跳转到登录页面") return self.handle_app_state() # 递归调用处理登录后的状态 else: logging.error(f"设备 {self.device_id} 跳转到登录页面失败") return False # 处理登录页面状态 logging.info(f"设备 {self.device_id} 检测到登录页面,执行登录操作") max_retries = 1 login_success = False for attempt in range(max_retries + 1): if self.login_page.login(): login_success = True break else: if attempt < max_retries: logging.warning(f"设备 {self.device_id} 登录失败,准备重试 ({attempt + 1}/{max_retries})") time.sleep(2) # 等待2秒后重试 else: logging.error(f"设备 {self.device_id} 登录失败,已达到最大重试次数") if not login_success: return False logging.info(f"设备 {self.device_id} 登录成功,继续执行更新操作") time.sleep(1) # 执行更新操作 if not self.download_tabbar_page.download_tabbar_page_manager(): logging.error(f"设备 {self.device_id} 更新操作执行失败") return False # 获取状态为3的线路。 apis.get_line_info_and_save_global(user_name=global_variable.GLOBAL_USERNAME); # # 虚拟数据替代 # global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT = {'CDWZQ-2标-龙骨湾右线大桥-0-7号墩-平原': 'L156372', 'CDWZQ-2标-蓝家湾特大 桥-31-31-平原': 'L159206'} # global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST = list(global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT.keys()) # 点击测量导航栏按钮 measure_page_btn = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_3_layout")) ) measure_page_btn.click() # 处理平差 self.screenshot_page.screenshot_page_manager(self.device_id) # 检查是否有需要上传的断点 if not global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST: logging.info(f"设备 {self.device_id} 断点列表为空,无需执行上传操作") return False # 点击上传导航栏按钮 upload_page_btn = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_2_layout")) ) upload_page_btn.click() # 遍历断点列表,逐个执行上传操作 upload_success_count = 0 for breakpoint_name in global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST: try: logging.info(f"设备 {self.device_id} 开始处理断点 '{breakpoint_name}' 的上传") # 安全地获取断点信息 line_num = global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT.get(breakpoint_name) if line_num is None: logging.warning(f"设备 {self.device_id} 断点 '{breakpoint_name}' 在字典中未找到,跳过上传") continue if not line_num: logging.warning(f"设备 {self.device_id} 断点 '{breakpoint_name}' 未获取到line_num,跳过上传") continue # 执行上传配置管理,传入当前断点名称 if self.upload_config_page.upload_config_page_manager(self.results_dir, breakpoint_name, line_num): logging.info(f"设备 {self.device_id} 断点 '{breakpoint_name}' 上传成功") upload_success_count += 1 else: logging.error(f"设备 {self.device_id} 断点 '{breakpoint_name}' 上传失败") except Exception as e: logging.error(f"设备 {self.device_id} 处理断点 '{breakpoint_name}' 时发生异常: {str(e)}") logging.info(f"设备 {self.device_id} 上传配置管理执行完成,成功上传 {upload_success_count}/{len(global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST)} 个断点") # 如果所有断点都上传成功,返回True;否则返回False all_upload_success = upload_success_count == len(global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST) if all_upload_success: logging.info(f"设备 {self.device_id} 所有断点上传成功") # 把上传成功的断点写入日志文件"上传成功的断点.txt" with open(os.path.join(self.results_dir, "上传成功的断点.txt"), "w", encoding='utf-8') as f: for bp in global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST: f.write(f"{bp}\n") else: logging.warning(f"设备 {self.device_id} 部分断点上传失败") # 把上传成功的断点写入日志文件"上传成功的断点.txt" with open(os.path.join(self.results_dir, "上传成功的断点.txt"), "w", encoding='utf-8') as f: for bp in global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST: f.write(f"{bp}\n") # 把上传失败的断点写入日志文件"上传失败的断点.txt" with open(os.path.join(self.results_dir, "上传失败的断点.txt"), "w", encoding='utf-8') as f: for bp in set(global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST)-set(global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST): f.write(f"{bp}\n") return all_upload_success except Exception as e: logging.error(f"设备 {self.device_id} 处理应用状态时出错: {str(e)}") return False def check_and_click_confirm_popup_appium(self): """ 适用于Appium的弹窗检测函数 Returns: bool: 是否成功处理弹窗 """ try: from appium.webdriver.common.appiumby import AppiumBy # 使用self.driver而不是参数 if not hasattr(self, 'driver') or self.driver is None: logging.warning("driver未初始化,无法检测弹窗") return False # 检查弹窗消息 message_elements = self.driver.find_elements(AppiumBy.XPATH, '//android.widget.TextView[@text="是否退出测量界面?"]') if message_elements: logging.info("检测到退出测量界面弹窗") # 点击"是"按钮 confirm_buttons = self.driver.find_elements(AppiumBy.XPATH, '//android.widget.Button[@text="是" and @resource-id="android:id/button1"]') if confirm_buttons: confirm_buttons[0].click() logging.info("已点击'是'按钮") time.sleep(1) return True else: logging.warning("未找到'是'按钮") return False else: return False except Exception as e: logging.error(f"Appium检测弹窗时发生错误: {str(e)}") return False def navigate_to_login_page(self, driver, device_id): """ 补充的跳转页面函数:当设备处于未知状态时,尝试跳转到登录页面 参数: driver: 已初始化的Appium WebDriver对象 device_id: 设备ID,用于日志记录 """ try: target_package = 'com.bjjw.cjgc' target_activity = '.activity.LoginActivity' # 使用ADB命令启动Activity try: logging.info(f"尝试使用ADB命令启动LoginActivity: {target_package}/{target_activity}") adb_command = f"adb -s {device_id} shell am start -n {target_package}/{target_activity}" result = subprocess.run(adb_command, shell=True, capture_output=True, text=True) if result.returncode == 0: logging.info(f"使用ADB命令启动LoginActivity成功") time.sleep(2) # 等待Activity启动 return True else: logging.warning(f"ADB命令执行失败: {result.stderr}") except Exception as adb_error: logging.warning(f"执行ADB命令时出错: {adb_error}") except Exception as e: logging.error(f"跳转到登录页面过程中发生未预期错误: {e}") # 所有尝试都失败 return False def run_automation(self): """运行自动化流程""" try: success = self.handle_app_state() # success = self.test_handle_app_state() if success: logging.info(f"设备 {self.device_id} 自动化流程执行成功") else: logging.error(f"设备 {self.device_id} 自动化流程执行失败") return success except Exception as e: logging.error(f"设备 {self.device_id} 自动化执行过程中发生错误: {str(e)}") return False finally: self.quit() def quit(self): """关闭驱动""" safe_quit_driver(getattr(self, 'driver', None), self.device_id) # 主执行逻辑 if __name__ == "__main__": # 单个设备配置 - 现在DeviceAutomation会自动获取设备ID try: automation = DeviceAutomation() success = automation.run_automation() if success: logging.info(f"设备自动化流程执行成功") else: logging.error(f"设备自动化流程执行失败") except Exception as e: logging.error(f"设备执行出错: {str(e)}")