1112 lines
49 KiB
Python
1112 lines
49 KiB
Python
# page_objects/section_mileage_config_page.py
|
||
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, StaleElementReferenceException
|
||
from globals import apis, global_variable
|
||
from globals.ex_apis import get_weather_simple
|
||
import time
|
||
import logging
|
||
|
||
import globals.ids as ids # 导入元素ID
|
||
from globals.driver_utils import check_session_valid, reconnect_driver
|
||
from check_station import CheckStation
|
||
|
||
class SectionMileageConfigPage:
|
||
def __init__(self, driver, wait, device_id):
|
||
self.driver = driver
|
||
self.wait = wait
|
||
self.device_id = device_id
|
||
self.logger = logging.getLogger(__name__)
|
||
self.check_station_page = CheckStation(self.driver, self.wait,self.device_id)
|
||
|
||
|
||
def is_on_config_page(self):
|
||
"""检查是否在断面里程配置页面"""
|
||
try:
|
||
title_bar = self.wait.until(
|
||
EC.presence_of_element_located((AppiumBy.ID, ids.MEASURE_TITLE_ID))
|
||
)
|
||
return True
|
||
except TimeoutException:
|
||
self.logger.warning("未找到断面里程配置页面的标题栏")
|
||
return False
|
||
|
||
def scroll_to_find_element(self, element_id, max_scroll_attempts=5):
|
||
"""
|
||
下滑页面直到找到指定元素
|
||
|
||
参数:
|
||
element_id: 要查找的元素ID
|
||
max_scroll_attempts: 最大下滑次数
|
||
|
||
返回:
|
||
bool: 是否找到元素
|
||
"""
|
||
try:
|
||
for attempt in range(max_scroll_attempts):
|
||
# 检查元素是否存在
|
||
try:
|
||
element = self.driver.find_element(AppiumBy.ID, element_id)
|
||
if element.is_displayed():
|
||
# self.logger.info(f"找到元素: {element_id}")
|
||
return True
|
||
except NoSuchElementException:
|
||
pass
|
||
|
||
# 如果没找到,下滑页面
|
||
self.logger.info(f"第 {attempt + 1} 次下滑查找元素: {element_id}")
|
||
window_size = self.driver.get_window_size()
|
||
start_x = window_size['width'] // 2
|
||
self.driver.swipe(start_x, 1600, start_x, 500, 500)
|
||
time.sleep(1) # 等待滑动完成
|
||
|
||
self.logger.warning(f"下滑 {max_scroll_attempts} 次后仍未找到元素: {element_id}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"下滑查找元素时出错: {str(e)}")
|
||
return False
|
||
|
||
def scroll_to_find_all_elements(self, element_ids, max_scroll_attempts=10):
|
||
"""
|
||
下滑页面直到找到所有指定元素
|
||
|
||
参数:
|
||
element_ids: 要查找的元素ID列表
|
||
max_scroll_attempts: 最大下滑次数
|
||
|
||
返回:
|
||
bool: 是否找到所有元素
|
||
"""
|
||
try:
|
||
found_count = 0
|
||
target_count = len(element_ids)
|
||
|
||
for attempt in range(max_scroll_attempts):
|
||
# 检查每个元素是否存在
|
||
current_found = 0
|
||
for element_id in element_ids:
|
||
try:
|
||
element = self.driver.find_element(AppiumBy.ID, element_id)
|
||
if element.is_displayed():
|
||
current_found += 1
|
||
except NoSuchElementException:
|
||
pass
|
||
|
||
if current_found > found_count:
|
||
found_count = current_found
|
||
self.logger.info(f"找到 {found_count}/{target_count} 个元素")
|
||
|
||
if found_count == target_count:
|
||
self.logger.info(f"成功找到所有 {target_count} 个元素")
|
||
return True
|
||
|
||
# 如果没找到全部,下滑页面
|
||
self.logger.info(f"第 {attempt + 1} 次下滑,已找到 {found_count}/{target_count} 个元素")
|
||
window_size = self.driver.get_window_size()
|
||
start_x = window_size['width'] // 2
|
||
self.driver.swipe(start_x, 1600, start_x, 500, 500)
|
||
time.sleep(1) # 等待滑动完成
|
||
|
||
self.logger.warning(f"下滑 {max_scroll_attempts} 次后仍未找到所有元素,只找到 {found_count}/{target_count} 个")
|
||
return found_count > 0 # 至少找到一个元素也算部分成功
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"下滑查找所有元素时出错: {str(e)}")
|
||
return False
|
||
|
||
def check_work_base_consistency(self):
|
||
"""
|
||
判断第一个工作基点和最后一个工作基点是否一致
|
||
|
||
返回:
|
||
bool: 是否一致,True表示一致,False表示不一致
|
||
"""
|
||
try:
|
||
# 获取工作基点路径文本
|
||
point_order_element = self.wait.until(
|
||
EC.presence_of_element_located((AppiumBy.ID, "com.bjjw.cjgc:id/select_point_order_name"))
|
||
)
|
||
point_order_text = point_order_element.text
|
||
# self.logger.info(f"工作基点路径: {point_order_text}")
|
||
|
||
# 解析第一个工作基点和最后一个工作基点
|
||
if "--->" in point_order_text:
|
||
parts = point_order_text.split("--->")
|
||
|
||
# 提取第一个工作基点
|
||
first_base = parts[0].strip()
|
||
# 提取最后一个工作基点
|
||
last_base = parts[-1].strip()
|
||
|
||
# self.logger.info(f"第一个工作基点: {first_base}")
|
||
# self.logger.info(f"最后一个工作基点: {last_base}")
|
||
|
||
# 判断是否一致(去除可能的工作基点标识)
|
||
first_base_clean = first_base.replace("(工作基点)", "").strip()
|
||
last_base_clean = last_base.replace("(工作基点)", "").strip()
|
||
|
||
is_consistent = first_base_clean == last_base_clean
|
||
# self.logger.info(f"工作基点一致性: {is_consistent}")
|
||
|
||
return is_consistent
|
||
else:
|
||
self.logger.warning("无法解析工作基点路径格式")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"检查工作基点一致性时出错: {str(e)}")
|
||
return False
|
||
|
||
def get_observation_type_based_on_base(self):
|
||
"""
|
||
根据工作基点一致性返回观测类型
|
||
|
||
返回:
|
||
str: 观测类型
|
||
"""
|
||
try:
|
||
is_consistent = self.check_work_base_consistency()
|
||
|
||
if is_consistent:
|
||
obs_type = "往:aBFFB 返:aBFFB"
|
||
# self.logger.info("工作基点一致,选择观测类型: 往:aBFFB 返:aBFFB")
|
||
else:
|
||
obs_type = "aBFFB"
|
||
# self.logger.info("工作基点不一致,选择观测类型: aBFFB")
|
||
|
||
return obs_type
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"获取观测类型时出错: {str(e)}")
|
||
return "aBFFB" # 默认值
|
||
|
||
def select_weather(self, weather_option="阴"):
|
||
"""选择天气"""
|
||
try:
|
||
# 先下滑查找天气元素
|
||
if not self.scroll_to_find_element(ids.MEASURE_WEATHER_ID):
|
||
self.logger.error("未找到天气下拉框")
|
||
return False
|
||
|
||
# 点击天气下拉框
|
||
weather_spinner = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, ids.MEASURE_WEATHER_ID))
|
||
)
|
||
weather_spinner.click()
|
||
time.sleep(1) # 等待选项弹出
|
||
|
||
# 等待选择对话框出现
|
||
self.wait.until(
|
||
EC.presence_of_element_located((AppiumBy.ID, ids.MEASURE_SELECT_ID))
|
||
)
|
||
|
||
# 选择指定天气选项
|
||
weather_xpath = f"//android.widget.TextView[@resource-id='{ids.SELECT_DIALOG_TEXT1_ID}' and @text='{weather_option}']"
|
||
weather_option_element = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.XPATH, weather_xpath))
|
||
)
|
||
weather_option_element.click()
|
||
self.logger.info(f"已选择天气: {weather_option}")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"选择天气失败: {str(e)}")
|
||
return False
|
||
|
||
def select_observation_type(self, obs_type=None):
|
||
"""选择观测类型"""
|
||
try:
|
||
# 如果未指定观测类型,根据工作基点自动选择
|
||
if obs_type is None:
|
||
obs_type = self.get_observation_type_based_on_base()
|
||
|
||
# 先下滑查找观测类型元素
|
||
if not self.scroll_to_find_element(ids.MEASURE_TYPE_ID):
|
||
self.logger.error("未找到观测类型下拉框")
|
||
return False
|
||
|
||
# 点击观测类型下拉框
|
||
obs_type_spinner = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, ids.MEASURE_TYPE_ID))
|
||
)
|
||
obs_type_spinner.click()
|
||
time.sleep(2) # 等待选项弹出
|
||
|
||
# 等待选择对话框出现
|
||
self.wait.until(
|
||
EC.presence_of_element_located((AppiumBy.ID, ids.MEASURE_SELECT_ID))
|
||
)
|
||
|
||
# 选择指定观测类型
|
||
obs_type_xpath = f"//android.widget.TextView[@resource-id='{ids.SELECT_DIALOG_TEXT1_ID}' and @text='{obs_type}']"
|
||
obs_type_option_element = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.XPATH, obs_type_xpath))
|
||
)
|
||
obs_type_option_element.click()
|
||
self.logger.info(f"已选择观测类型: {obs_type}")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"选择观测类型失败: {str(e)}")
|
||
return False
|
||
|
||
def enter_temperature(self, temperature="25"):
|
||
"""填写温度"""
|
||
try:
|
||
# 先下滑查找温度输入框
|
||
if not self.scroll_to_find_element(ids.MEASURE_TEMPERATURE_ID):
|
||
self.logger.error("未找到温度输入框")
|
||
return False
|
||
|
||
# 找到温度输入框并输入温度值
|
||
temp_input = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, ids.MEASURE_TEMPERATURE_ID))
|
||
)
|
||
temp_input.clear() # 清空原有内容
|
||
temp_input.send_keys(temperature)
|
||
# self.logger.info(f"已输入温度: {temperature}")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"输入温度失败: {str(e)}")
|
||
return False
|
||
|
||
def enter_barometric_pressure(self, pressure="800"):
|
||
"""填写气压"""
|
||
try:
|
||
# 先下滑查找气压输入框
|
||
if not self.scroll_to_find_element("com.bjjw.cjgc:id/point_list_barometric_et"):
|
||
self.logger.error("未找到气压输入框")
|
||
return False
|
||
|
||
# 找到气压输入框并输入气压值
|
||
pressure_input = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/point_list_barometric_et"))
|
||
)
|
||
pressure_input.clear() # 清空原有内容
|
||
pressure_input.send_keys(pressure)
|
||
# self.logger.info(f"已输入气压: {pressure}")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"输入气压失败: {str(e)}")
|
||
return False
|
||
|
||
def click_save_button(self):
|
||
"""点击保存按钮"""
|
||
try:
|
||
# 先下滑查找保存按钮
|
||
if not self.scroll_to_find_element(ids.MEASURE_SAVE_ID):
|
||
self.logger.error("未找到保存按钮")
|
||
return False
|
||
|
||
save_button = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, ids.MEASURE_SAVE_ID))
|
||
)
|
||
save_button.click()
|
||
self.logger.info("已点击保存按钮")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"点击保存按钮失败: {str(e)}")
|
||
return False
|
||
|
||
def click_conn_level_btn(self):
|
||
"""点击连接水准仪按钮"""
|
||
try:
|
||
conn_level_btn = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, ids.CONNECT_LEVEL_METER))
|
||
)
|
||
conn_level_btn.click()
|
||
self.logger.info("已点击连接水准仪按钮1")
|
||
|
||
# 等待设备选择对话框出现
|
||
self.wait.until(
|
||
EC.presence_of_element_located((AppiumBy.ID, "android:id/content"))
|
||
)
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"点击连接水准仪按钮失败1: {str(e)}")
|
||
return False
|
||
|
||
def connect_to_device(self):
|
||
"""连接设备,处理可能出现的弹窗"""
|
||
try:
|
||
# 检查已配对设备列表中是否有设备
|
||
paired_devices_list_xpath = "//android.widget.ListView[@resource-id='com.bjjw.cjgc:id/paired_devices']"
|
||
try:
|
||
# 等待配对设备列表出现
|
||
paired_list = self.wait.until(
|
||
EC.visibility_of_element_located((AppiumBy.XPATH, paired_devices_list_xpath))
|
||
)
|
||
|
||
# 获取列表中的所有子元素(设备项)
|
||
device_items = paired_list.find_elements(AppiumBy.CLASS_NAME, "android.widget.TextView")
|
||
|
||
if device_items:
|
||
# 获取第一个设备的文本
|
||
first_device_text = device_items[0].text
|
||
# self.logger.info(f"找到设备: {first_device_text}")
|
||
|
||
|
||
# 检查第一个设备是否不是"没有已配对的设备"
|
||
if "没有已配对的设备" not in first_device_text:
|
||
# 存在元素,点击第一个设备
|
||
first_device = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.XPATH, f"{paired_devices_list_xpath}/android.widget.TextView[1]"))
|
||
)
|
||
first_device.click()
|
||
self.logger.info(f"已点击第一个设备: {first_device_text}")
|
||
# 处理可能出现的弹窗
|
||
# if self._handle_alert_dialog():
|
||
# # 弹窗已处理,重新尝试连接
|
||
# self.logger.info("弹窗已处理,重新尝试连接设备")
|
||
# return self._retry_connection(paired_devices_list_xpath)
|
||
# else:
|
||
# # 没有弹窗,正常检查连接状态
|
||
# return self._check_connection_status()
|
||
# 新增:最多尝试3次连接
|
||
max_retry_times = 3 # 最大重试次数
|
||
current_retry = 0 # 当前重试计数器(初始为0,代表第1次尝试)
|
||
|
||
while current_retry < max_retry_times:
|
||
current_retry += 1 # 每次循环先+1,记录当前是第几次尝试
|
||
self.logger.info(f"第 {current_retry} 次尝试连接设备(最多{max_retry_times}次)")
|
||
|
||
if self._handle_alert_dialog():
|
||
# 弹窗已处理,执行本次重试连接
|
||
self.logger.info("弹窗已处理,查看按钮状态")
|
||
connect_success = self._retry_connection(paired_devices_list_xpath)
|
||
|
||
# 若本次连接成功,立即返回True,终止重试
|
||
if connect_success:
|
||
self.logger.info(f"第 {current_retry} 次尝试连接成功")
|
||
return True
|
||
else:
|
||
# 本次连接失败,判断是否还有剩余重试次数
|
||
remaining_times = max_retry_times - current_retry
|
||
if remaining_times > 0:
|
||
self.logger.warning(f"1:第 {current_retry} 次尝试连接失败,剩余 {remaining_times} 次重试机会")
|
||
else:
|
||
self.logger.error(f"1:第 {current_retry} 次尝试连接失败,已达到最大重试次数({max_retry_times}次)")
|
||
else:
|
||
# 未检测到弹窗,直接尝试连接(逻辑与原代码一致,仅增加重试计数)
|
||
# self.logger.info("未检测到弹窗,尝试连接设备")
|
||
# connect_success = self._retry_connection(paired_devices_list_xpath)
|
||
connect_success = self._check_connection_status()
|
||
|
||
if connect_success:
|
||
self.logger.info(f"第 {current_retry} 次尝试连接成功")
|
||
return True
|
||
else:
|
||
remaining_times = max_retry_times - current_retry
|
||
if remaining_times > 0:
|
||
self.logger.warning(f"2:第 {current_retry} 次尝试连接失败,剩余 {remaining_times} 次重试机会")
|
||
else:
|
||
self.logger.error(f"2:第 {current_retry} 次尝试连接失败,已达到最大重试次数({max_retry_times}次)")
|
||
# 循环结束:3次均失败,返回False
|
||
return False
|
||
else:
|
||
self.logger.info("第一个设备是'没有已配对的设备',不点击,等待用户手动连接")
|
||
return self._wait_for_manual_connection()
|
||
|
||
else:
|
||
self.logger.info("没有找到已配对设备")
|
||
return False
|
||
|
||
except TimeoutException:
|
||
self.logger.info("没有已配对设备;配对设备失败")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"连接设备过程中出错: {str(e)}")
|
||
return False
|
||
|
||
def _handle_alert_dialog(self):
|
||
"""处理连接蓝牙失败警告弹窗"""
|
||
try:
|
||
# 等待弹窗出现(短暂等待)
|
||
alert_dialog = WebDriverWait(self.driver, 5).until(
|
||
EC.visibility_of_element_located((AppiumBy.ID, "android:id/content"))
|
||
)
|
||
|
||
# 查找关闭报警按钮
|
||
close_alert_btn = WebDriverWait(self.driver, 3).until(
|
||
EC.element_to_be_clickable((AppiumBy.XPATH, "//android.widget.Button[@text='关闭报警']"))
|
||
)
|
||
|
||
close_alert_btn.click()
|
||
self.logger.info("已点击'关闭报警'按钮")
|
||
|
||
# # 等待弹窗消失
|
||
# WebDriverWait(self.driver, 5).until(
|
||
# EC.invisibility_of_element_located((AppiumBy.ID, "android:id/content"))
|
||
# )
|
||
# self.logger.info("弹窗已关闭")
|
||
|
||
return True
|
||
|
||
except TimeoutException:
|
||
self.logger.info("未检测到弹窗,继续正常流程")
|
||
return False
|
||
except Exception as e:
|
||
self.logger.error(f"处理弹窗时出错: {str(e)}")
|
||
return False
|
||
|
||
def _retry_connection(self, paired_devices_list_xpath):
|
||
"""重新尝试连接设备"""
|
||
try:
|
||
# 再次点击连接蓝牙设备按钮
|
||
connect_btn = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, ids.CONNECT_LEVEL_METER))
|
||
)
|
||
connect_btn.click()
|
||
self.logger.info("已重新点击连接蓝牙设备按钮")
|
||
|
||
# 等待设备列表重新出现
|
||
paired_list = self.wait.until(
|
||
EC.visibility_of_element_located((AppiumBy.XPATH, paired_devices_list_xpath))
|
||
)
|
||
|
||
# 获取设备列表并点击第一个设备
|
||
device_items = paired_list.find_elements(AppiumBy.CLASS_NAME, "android.widget.TextView")
|
||
if device_items and "没有已配对的设备" not in device_items[0].text:
|
||
first_device = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.XPATH, f"{paired_devices_list_xpath}/android.widget.TextView[1]"))
|
||
)
|
||
first_device.click()
|
||
self.logger.info("已重新点击第一个设备")
|
||
|
||
# 检查连接状态
|
||
return self._check_connection_status()
|
||
else:
|
||
self.logger.warning("重新尝试时未找到可用设备")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"重新尝试连接时出错: {str(e)}")
|
||
return False
|
||
|
||
def _check_connection_status(self):
|
||
"""检查连接状态"""
|
||
try:
|
||
# 等待连接状态更新
|
||
time.sleep(3)
|
||
|
||
conn_level_btn = self.driver.find_element(AppiumBy.ID, ids.CONNECT_LEVEL_METER)
|
||
if "已连接上" in conn_level_btn.text:
|
||
self.logger.info(f"蓝牙设备连接成功: {conn_level_btn.text}")
|
||
return True
|
||
else:
|
||
self.logger.warning(f"蓝牙设备连接失败: {conn_level_btn.text}")
|
||
return False
|
||
except NoSuchElementException:
|
||
self.logger.warning("未找到连接按钮")
|
||
return False
|
||
|
||
def _wait_for_manual_connection(self):
|
||
"""等待用户手动连接"""
|
||
max_wait_time = 60 # 总最大等待时间:600秒
|
||
poll_interval = 5 # 每次检查后的休眠间隔:30秒
|
||
btn_wait_time = 15 # 单个循环内,等待"连接按钮"出现的最大时间:15秒
|
||
start_time = time.time() # 记录总等待的开始时间戳
|
||
|
||
while time.time() - start_time < max_wait_time:
|
||
conn_level_btn = None # 初始化按钮对象,避免上一轮残留值影响
|
||
try:
|
||
# 第一步:先等待15秒,直到按钮出现或超时(解决按钮延迟加载问题)
|
||
conn_level_btn = WebDriverWait(self.driver, btn_wait_time).until(
|
||
EC.presence_of_element_located((AppiumBy.ID, ids.CONNECT_LEVEL_METER))
|
||
)
|
||
self.logger.debug("已检测到连接按钮")
|
||
|
||
if "已连接上" in conn_level_btn.text:
|
||
self.logger.info(f"用户手动连接成功: {conn_level_btn.text}")
|
||
return True
|
||
else:
|
||
# 未连接,计算总剩余等待时间并记录
|
||
elapsed_total = time.time() - start_time
|
||
remaining_total = max_wait_time - elapsed_total
|
||
self.logger.debug(f"等待手动连接中...总剩余时间: {remaining_total:.0f}秒")
|
||
# 情况1:10秒内未找到按钮(btn_wait_time超时)
|
||
except TimeoutException:
|
||
elapsed_total = time.time() - start_time
|
||
remaining_total = max_wait_time - elapsed_total
|
||
self.logger.warning(f"未检测到连接按钮,{remaining_total:.0f}秒内将再次检查")
|
||
|
||
# 情况2:其他意外异常(如驱动异常)
|
||
except Exception as e:
|
||
self.logger.error(f"检查连接状态时出现意外错误: {str(e)}")
|
||
|
||
# 无论是否找到按钮,都休眠poll_interval秒再进入下一轮循环
|
||
time.sleep(poll_interval)
|
||
|
||
self.logger.warning(f"等待{max_wait_time}秒后仍未连接成功,终止等待")
|
||
return False
|
||
|
||
def wait_for_measurement_data(self, timeout=900):
|
||
"""
|
||
等待并轮询测量数据接口,每10秒访问一次,直到有数据返回
|
||
|
||
参数:
|
||
timeout: 最大等待时间(秒)
|
||
|
||
返回:
|
||
bool: 是否成功获取到数据
|
||
"""
|
||
try:
|
||
start_time = time.time()
|
||
|
||
while time.time() - start_time < timeout:
|
||
try:
|
||
task_data = apis.get_end_with_num()
|
||
if not task_data:
|
||
# self.logger.info("接口返回但没有数据,继续等待...")
|
||
time.sleep(10)
|
||
# # 1. 获取屏幕尺寸,计算中心坐标(通用适配所有设备)
|
||
# screen_size = self.driver.get_window_size()
|
||
# center_x = screen_size['width'] / 2
|
||
# center_y = screen_size['height'] / 2
|
||
# # 2. 点击屏幕中心(点击时长500ms,和常规操作一致)
|
||
# self.driver.tap([(center_x, center_y)], 500)
|
||
# self.logger.info(f"已点击屏幕中心(坐标:{center_x}, {center_y}),间隔30秒触发")
|
||
|
||
continue
|
||
self.logger.info(f"接口返回数据:{task_data}")
|
||
if task_data.get('status') == 3:
|
||
self.logger.info("测量任务状态为3,测量结束")
|
||
return True
|
||
else:
|
||
self.logger.info("测量任务状态不为3,继续等待...")
|
||
continue
|
||
except Exception as e:
|
||
self.logger.error(f"处理接口响应时出错: {str(e)}")
|
||
|
||
self.logger.error(f"在 {timeout} 秒内未获取到有效数据")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"等待测量结束时发生错误: {str(e)}")
|
||
return False
|
||
|
||
|
||
def wait_for_measurement_end(self, timeout=900):
|
||
"""
|
||
等待按钮变成"测量结束",最多15分钟,包含驱动重新初始化机制
|
||
|
||
Args:
|
||
timeout: 超时时间,默认900秒(15分钟)
|
||
|
||
Returns:
|
||
bool: 是否成功等到测量结束按钮
|
||
"""
|
||
try:
|
||
# 更新WebDriverWait等待时间为900秒
|
||
self.wait = WebDriverWait(self.driver, 900)
|
||
self.logger.info(f"等待测量结束按钮出现,最多等待 {timeout} 秒")
|
||
|
||
start_time = time.time()
|
||
reinit_attempts = 0
|
||
max_reinit_attempts = 3 # 最大重新初始化次数
|
||
|
||
while time.time() - start_time < timeout:
|
||
try:
|
||
# 使用ID查找测量控制按钮
|
||
measure_btn = self.driver.find_element(
|
||
AppiumBy.ID,
|
||
"com.bjjw.cjgc:id/btn_control_begin_or_end"
|
||
)
|
||
|
||
if "测量结束" in measure_btn.text:
|
||
self.logger.info("检测到测量结束按钮,暂停两秒等待电脑执行点击任务")
|
||
time.sleep(5)
|
||
return True
|
||
|
||
except NoSuchElementException:
|
||
# 按钮未找到,继续等待
|
||
pass
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
self.logger.warning(f"查找测量结束按钮时出现异常: {error_msg}")
|
||
|
||
# 检测是否是UiAutomator2服务崩溃
|
||
if 'UiAutomator2 server' in error_msg and 'instrumentation process is not running' in error_msg and reinit_attempts < max_reinit_attempts:
|
||
reinit_attempts += 1
|
||
self.logger.info(f"检测到UiAutomator2服务崩溃,尝试第 {reinit_attempts} 次重新初始化驱动")
|
||
|
||
# 尝试重新初始化驱动
|
||
if self._reinit_driver():
|
||
self.logger.info("驱动重新初始化成功")
|
||
else:
|
||
self.logger.error("驱动重新初始化失败")
|
||
# 继续尝试,而不是立即失败
|
||
|
||
# 等待一段时间后再次检查
|
||
time.sleep(5)
|
||
|
||
# 每30秒输出一次等待状态
|
||
if int(time.time() - start_time) % 30 == 0:
|
||
elapsed = int(time.time() - start_time)
|
||
self.logger.info(f"已等待 {elapsed} 秒,仍在等待测量结束...")
|
||
|
||
self.logger.error("等待测量结束按钮超时")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"等待测量结束时发生严重错误: {str(e)}")
|
||
return False
|
||
|
||
def _reinit_driver(self):
|
||
"""
|
||
重新初始化Appium驱动
|
||
|
||
Returns:
|
||
bool: 是否成功重新初始化
|
||
"""
|
||
try:
|
||
# 首先尝试关闭现有的驱动
|
||
if hasattr(self, 'driver') and self.driver:
|
||
try:
|
||
self.driver.quit()
|
||
except:
|
||
self.logger.warning("关闭现有驱动时出现异常")
|
||
|
||
# 导入必要的模块
|
||
from appium import webdriver
|
||
from appium.options.android import UiAutomator2Options
|
||
|
||
# 重新创建驱动配置
|
||
options = UiAutomator2Options()
|
||
options.platform_name = "Android"
|
||
options.device_name = self.device_id
|
||
options.app_package = "com.bjjw.cjgc"
|
||
options.app_activity = ".activity.LoginActivity"
|
||
options.automation_name = "UiAutomator2"
|
||
options.no_reset = True
|
||
options.auto_grant_permissions = True
|
||
options.new_command_timeout = 300
|
||
options.udid = self.device_id
|
||
|
||
# 重新连接驱动
|
||
self.logger.info(f"正在重新初始化设备 {self.device_id} 的驱动...")
|
||
self.driver = webdriver.Remote("http://localhost:4723", options=options)
|
||
|
||
# 重新初始化等待对象
|
||
from selenium.webdriver.support.ui import WebDriverWait
|
||
self.wait = WebDriverWait(self.driver, 20)
|
||
|
||
self.logger.info(f"设备 {self.device_id} 驱动重新初始化完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"设备 {self.device_id} 驱动重新初始化失败: {str(e)}")
|
||
return False
|
||
|
||
def click_start_measure_btn(self):
|
||
"""点击开始测量按钮并处理确认弹窗"""
|
||
try:
|
||
# 查找并点击开始测量按钮
|
||
start_measure_btn = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/btn_control_begin_or_end"))
|
||
)
|
||
|
||
# 检查按钮文本
|
||
btn_text = start_measure_btn.text
|
||
self.logger.info(f"测量按钮文本: {btn_text}")
|
||
|
||
if "点击开始测量" in btn_text or "开始测量" in btn_text:
|
||
start_measure_btn.click()
|
||
self.logger.info("已点击开始测量按钮")
|
||
|
||
# 处理确认弹窗
|
||
dialog_result = self._handle_start_measure_confirm_dialog()
|
||
|
||
# 如果确认弹窗处理成功,检查线路前测表,查看是否要添加转点
|
||
if not dialog_result:
|
||
logging.error(f"设备 {self.device_id} 处理开始测量弹窗失败")
|
||
return False
|
||
|
||
else:
|
||
self.logger.warning(f"测量按钮状态不是开始测量,当前状态: {btn_text}")
|
||
return False
|
||
|
||
return True
|
||
|
||
|
||
except TimeoutException:
|
||
self.logger.error("等待开始测量按钮超时")
|
||
return False
|
||
except Exception as e:
|
||
self.logger.error(f"点击开始测量按钮时出错: {str(e)}")
|
||
return False
|
||
|
||
|
||
def add_transition_point(self):
|
||
"""添加转点"""
|
||
try:
|
||
# 查找并点击添加转点按钮
|
||
add_transition_btn = self.wait.until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/btn_add_ZPoint"))
|
||
)
|
||
add_transition_btn.click()
|
||
self.logger.info("已点击添加转点按钮")
|
||
return True
|
||
except TimeoutException:
|
||
self.logger.error("等待添加转点按钮超时")
|
||
return False
|
||
except Exception as e:
|
||
self.logger.error(f"添加转点时出错: {str(e)}")
|
||
return False
|
||
|
||
# def chang_status(self):
|
||
# """修改断点状态1->2"""
|
||
# try:
|
||
# # 修改断点状态1->2
|
||
# user_name = global_variable.GLOBAL_USERNAME
|
||
# line_num = global_variable.GLOBAL_LINE_NUM
|
||
|
||
# if line_num:
|
||
# success = apis.change_breakpoint_status(user_name, line_num, 2)
|
||
# if success:
|
||
# self.logger.info(f"成功修改断点状态: 线路{line_num} 状态1->2")
|
||
# return True
|
||
# else:
|
||
# self.logger.error(f"修改断点状态失败: 线路{line_num} 状态1->2")
|
||
# return False
|
||
# else:
|
||
# self.logger.warning("未找到线路编码,跳过修改断点状态")
|
||
# return False
|
||
# except Exception as e:
|
||
# self.logger.error(f"修改状态时出错: {str(e)}")
|
||
# return False
|
||
|
||
|
||
def _handle_start_measure_confirm_dialog(self):
|
||
"""处理开始测量确认弹窗"""
|
||
try:
|
||
# 等待确认弹窗出现
|
||
confirm_dialog = WebDriverWait(self.driver, 10).until(
|
||
EC.presence_of_element_located((AppiumBy.ID, "android:id/content"))
|
||
)
|
||
self.logger.info("检测到开始测量确认弹窗")
|
||
|
||
# 检查弹窗标题和消息
|
||
try:
|
||
title_element = self.driver.find_element(AppiumBy.ID, "android:id/alertTitle")
|
||
message_element = self.driver.find_element(AppiumBy.ID, "android:id/message")
|
||
self.logger.info(f"弹窗标题: {title_element.text}, 消息: {message_element.text}")
|
||
except NoSuchElementException:
|
||
self.logger.info("无法获取弹窗详细信息")
|
||
|
||
# 点击"是"按钮
|
||
yes_button = WebDriverWait(self.driver, 5).until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "android:id/button1"))
|
||
)
|
||
|
||
if yes_button.text == "是":
|
||
yes_button.click()
|
||
self.logger.info("已点击'是'按钮确认开始测量")
|
||
|
||
# # 等待弹窗消失
|
||
# WebDriverWait(self.driver, 5).until(
|
||
# EC.invisibility_of_element_located((AppiumBy.ID, "android:id/content"))
|
||
# )
|
||
self.logger.info("确认弹窗已关闭")
|
||
return True
|
||
else:
|
||
self.logger.error(f"确认按钮文本不是'是',实际文本: {yes_button.text}")
|
||
return False
|
||
|
||
except TimeoutException:
|
||
self.logger.warning("未检测到开始测量确认弹窗,可能不需要确认")
|
||
return True # 没有弹窗也认为是成功的
|
||
except Exception as e:
|
||
self.logger.error(f"处理开始测量确认弹窗时出错: {str(e)}")
|
||
return False
|
||
|
||
def click_system_back_button(self):
|
||
"""点击手机系统返回按钮"""
|
||
try:
|
||
self.driver.back()
|
||
self.logger.info("已点击手机系统返回按钮")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"点击手机系统返回按钮失败: {str(e)}")
|
||
return False
|
||
|
||
def add_breakpoint_to_tested_list(self):
|
||
"""添加测量结束的断点到列表和字典"""
|
||
breakpoint_name = global_variable.GLOBAL_CURRENT_PROJECT_NAME
|
||
line_num = global_variable.GLOBAL_LINE_NUM
|
||
if breakpoint_name and breakpoint_name not in global_variable.GLOBAL_TESTED_BREAKPOINT_LIST:
|
||
global_variable.GLOBAL_TESTED_BREAKPOINT_LIST.append(breakpoint_name)
|
||
global_variable.GLOBAL_BREAKPOINT_DICT[breakpoint_name] = {
|
||
'breakpoint_name': breakpoint_name,
|
||
'line_num': line_num
|
||
}
|
||
|
||
logging.info(f"成功添加断点 '{breakpoint_name}' 到上传列表")
|
||
logging.info(f"断点详细信息: 线路编码={line_num}")
|
||
return True
|
||
else:
|
||
logging.warning(f"断点名为空或已存在于列表中")
|
||
return False
|
||
|
||
def click_back_button(self):
|
||
"""点击手机系统返回按钮"""
|
||
if not check_session_valid(self.driver, self.device_id):
|
||
self.logger.warning(f"设备 {self.device_id} 会话无效,尝试重新连接驱动...")
|
||
if not reconnect_driver(self.device_id, self.driver):
|
||
self.logger.error(f"设备 {self.device_id} 驱动重连失败")
|
||
try:
|
||
self.driver.back()
|
||
self.logger.info("已点击手机系统返回按钮")
|
||
return True
|
||
except Exception as e:
|
||
self.logger.error(f"点击手机系统返回按钮失败: {str(e)}")
|
||
return False
|
||
|
||
# def check_listview_stability(self, timeout=20, poll_interval=2, target_count=2):
|
||
# """
|
||
# 检查列表视图中级2 LinearLayout数量的稳定性
|
||
|
||
# 参数:
|
||
# timeout: 超时时间(秒),默认20秒
|
||
# poll_interval: 轮询间隔(秒),默认2秒
|
||
# target_count: 目标数量,默认2个
|
||
|
||
# 返回:
|
||
# str:
|
||
# - "flash": 20秒内数量无变化
|
||
# - "error": 变化后20秒内未达到目标数量
|
||
# - "stable": 达到目标数量并保持稳定
|
||
# """
|
||
# listview_id = "com.bjjw.cjgc:id/auto_data_list"
|
||
# start_time = time.time()
|
||
# last_count = None
|
||
# change_detected = False
|
||
# change_time = None
|
||
|
||
# self.logger.info(f"开始监控列表 {listview_id} 中级2 LinearLayout数量,目标数量: {target_count},超时时间: {timeout}秒")
|
||
|
||
# try:
|
||
# while time.time() - start_time < timeout:
|
||
# try:
|
||
# # 获取当前层级2 LinearLayout数量
|
||
# current_count = self._get_level2_linear_layout_count()
|
||
|
||
# # 首次获取或数量发生变化
|
||
# if last_count is None:
|
||
# self.logger.info(f"初始层级2 LinearLayout数量: {current_count}")
|
||
# last_count = current_count
|
||
# change_time = time.time()
|
||
# elif current_count != last_count:
|
||
# self.logger.info(f"层级2 LinearLayout数量发生变化: {last_count} -> {current_count}")
|
||
# last_count = current_count
|
||
# change_detected = True
|
||
# change_time = time.time()
|
||
|
||
# # 检查是否达到目标数量
|
||
# if current_count >= target_count:
|
||
# self.logger.info(f"已达到目标数量 {target_count},继续监控稳定性")
|
||
# # 重置计时器,继续监控是否稳定
|
||
# start_time = time.time()
|
||
|
||
# # 检查是否在变化后20秒内未达到目标数量
|
||
# if change_detected and change_time and (time.time() - change_time) > timeout:
|
||
# if last_count < target_count:
|
||
# self.logger.error(f"变化后{timeout}秒内未达到目标数量{target_count},当前数量: {last_count}")
|
||
# return "error"
|
||
|
||
# # 检查是否20秒内无变化
|
||
# if change_time and (time.time() - change_time) > timeout:
|
||
# self.logger.info(f"层级2 LinearLayout数量在{timeout}秒内无变化,返回flash")
|
||
# return "flash"
|
||
|
||
# time.sleep(poll_interval)
|
||
|
||
# except StaleElementReferenceException:
|
||
# self.logger.warning("元素已过时,重新获取")
|
||
# continue
|
||
# except Exception as e:
|
||
# self.logger.error(f"获取层级2 LinearLayout数量时出错: {str(e)}")
|
||
# if change_detected and change_time and (time.time() - change_time) > timeout:
|
||
# return "error"
|
||
# else:
|
||
# time.sleep(poll_interval)
|
||
# continue
|
||
|
||
# # 超时处理
|
||
# if change_detected:
|
||
# if last_count >= target_count:
|
||
# self.logger.info(f"已达到目标数量{target_count}并保持稳定")
|
||
# return "stable"
|
||
# else:
|
||
# self.logger.error(f"变化后{timeout}秒内未达到目标数量{target_count},当前数量: {last_count}")
|
||
# return "error"
|
||
# else:
|
||
# self.logger.info(f"层级2 LinearLayout数量在{timeout}秒内无变化,返回flash")
|
||
# return "flash"
|
||
|
||
# except Exception as e:
|
||
# self.logger.error(f"监控列表稳定性时出错: {str(e)}")
|
||
# return "error"
|
||
|
||
# def _get_level2_linear_layout_count(self):
|
||
# """
|
||
# 获取层级2 LinearLayout的数量
|
||
|
||
# 返回:
|
||
# int: 层级2 LinearLayout的数量
|
||
# """
|
||
# try:
|
||
# # 定位到ListView
|
||
# listview = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/auto_data_list")
|
||
|
||
# # 获取层级2的LinearLayout(ListView的直接子元素)
|
||
# # 使用XPath查找直接子元素
|
||
# level2_layouts = listview.find_elements(AppiumBy.XPATH, "./android.widget.LinearLayout")
|
||
|
||
# count = len(level2_layouts)
|
||
# self.logger.debug(f"当前层级2 LinearLayout数量: {count}")
|
||
|
||
# return count
|
||
|
||
# except NoSuchElementException:
|
||
# self.logger.error(f"未找到列表元素: com.bjjw.cjgc:id/auto_data_list")
|
||
# return 0
|
||
# except Exception as e:
|
||
# self.logger.error(f"获取层级2 LinearLayout数量时出错: {str(e)}")
|
||
# return 0
|
||
|
||
def handle_measurement_dialog(self):
|
||
"""处理测量弹窗 - 选择继续测量"""
|
||
try:
|
||
self.logger.info("检查线路弹出测量弹窗...")
|
||
|
||
# 直接尝试点击"继续测量"按钮
|
||
remeasure_btn = WebDriverWait(self.driver, 2).until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/measure_remeasure_all_btn"))
|
||
)
|
||
remeasure_btn.click()
|
||
self.logger.info("已点击'重新测量'按钮")
|
||
try:
|
||
WebDriverWait(self.driver, 2).until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "android:id/button1"))
|
||
)
|
||
except TimeoutException:
|
||
self.logger.info("未找到'是'按钮,可能弹窗未出现")
|
||
return False
|
||
|
||
confirm_btn = WebDriverWait(self.driver, 2).until(
|
||
EC.element_to_be_clickable((AppiumBy.ID, "android:id/button1"))
|
||
)
|
||
confirm_btn.click()
|
||
self.logger.info("已点击'是'按钮")
|
||
return True
|
||
|
||
except TimeoutException:
|
||
self.logger.info("未找到继续测量按钮,可能没有弹窗")
|
||
return True # 没有弹窗也认为是成功的
|
||
except Exception as e:
|
||
self.logger.error(f"点击线路测量弹窗按钮时出错: {str(e)}")
|
||
return False
|
||
def section_mileage_config_page_manager(self, address=None, obs_type=None):
|
||
"""执行完整的断面里程配置"""
|
||
try:
|
||
|
||
if not self.handle_measurement_dialog():
|
||
self.logger.error("检测到线路测量弹窗,但处理测量弹窗失败")
|
||
return False
|
||
|
||
self.logger.info("检测到线路测量弹窗,且处理测量弹窗成功")
|
||
|
||
if not self.is_on_config_page():
|
||
self.logger.error("不在断面里程配置页面")
|
||
return False
|
||
|
||
# 下滑查找所有必要元素
|
||
required_elements = [
|
||
ids.MEASURE_WEATHER_ID,
|
||
ids.MEASURE_TYPE_ID,
|
||
ids.MEASURE_TEMPERATURE_ID,
|
||
"com.bjjw.cjgc:id/point_list_barometric_et"
|
||
]
|
||
|
||
if not self.scroll_to_find_all_elements(required_elements):
|
||
self.logger.warning("未找到所有必要元素,但继续尝试配置")
|
||
|
||
# 临时地址
|
||
address = apis.get_one_addr(global_variable.GLOBAL_USERNAME) or "四川省资阳市"
|
||
|
||
|
||
# 获取实时天气信息
|
||
if address:
|
||
weather, temperature, pressure = get_weather_simple(address)
|
||
self.logger.info(f"获取到实时天气: {weather}, 温度: {temperature}°C, 气压: {pressure}hPa")
|
||
else:
|
||
# 使用默认值
|
||
weather, temperature, pressure = "阴", 25.0, 720.0
|
||
|
||
# 选择天气
|
||
if not self.select_weather(weather):
|
||
return False
|
||
|
||
time.sleep(1) # 短暂等待
|
||
|
||
# 选择观测类型(如果未指定,根据工作基点自动选择)
|
||
if not self.select_observation_type(obs_type):
|
||
return False
|
||
|
||
time.sleep(1) # 短暂等待
|
||
|
||
# 填写温度
|
||
if not self.enter_temperature(temperature):
|
||
return False
|
||
|
||
time.sleep(1) # 短暂等待
|
||
|
||
# 填写气压
|
||
if not self.enter_barometric_pressure(pressure):
|
||
return False
|
||
|
||
time.sleep(1) # 短暂等待
|
||
|
||
|
||
# 点击保存
|
||
if not self.click_save_button():
|
||
return False
|
||
|
||
# 连接水准仪
|
||
if not self.click_conn_level_btn():
|
||
return False
|
||
|
||
# 连接蓝牙设备
|
||
if not self.connect_to_device():
|
||
return False
|
||
|
||
# 连接设备成功,要点击“点击开始测量”
|
||
if not self.click_start_measure_btn():
|
||
return False
|
||
|
||
# if not self.check_station_page.run():
|
||
# self.logger.error("检查站页面运行失败")
|
||
# return False
|
||
|
||
|
||
# # 添加断点到列表
|
||
# if not self.add_breakpoint_to_tested_list():
|
||
# return False
|
||
|
||
# # 点击返回按钮
|
||
# if not self.click_back_button():
|
||
# return False
|
||
|
||
# # 测量结束。点击手机物理返回按钮,返回测量页面
|
||
# # 点击了手机独步导航栏返回键
|
||
# if not self.click_system_back_button():
|
||
# return False
|
||
|
||
self.logger.info("断面里程配置完成,正在执行测量")
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.logger.error(f"断面里程配置过程中出错: {str(e)}")
|
||
return False |