串起截图和上传
This commit is contained in:
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -52,25 +52,27 @@ missing module named jaraco.text.yield_lines - imported by setuptools._vendor.ja
|
||||
missing module named _manylinux - imported by packaging._manylinux (delayed, optional), setuptools._vendor.packaging._manylinux (delayed, optional), setuptools._vendor.wheel.vendored.packaging._manylinux (delayed, optional)
|
||||
missing module named trove_classifiers - imported by setuptools.config._validate_pyproject.formats (optional)
|
||||
missing module named pyimod02_importers - imported by C:\Program Files\Python312\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), C:\Program Files\Python312\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
|
||||
missing module named collections.Mapping - imported by collections (optional), pytz.lazy (optional)
|
||||
missing module named collections.Callable - imported by collections (optional), socks (optional)
|
||||
missing module named simplejson - imported by requests.compat (conditional, optional)
|
||||
missing module named dummy_threading - imported by requests.cookies (optional)
|
||||
missing module named 'OpenSSL.crypto' - imported by urllib3.contrib.pyopenssl (delayed, conditional)
|
||||
missing module named zstandard - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named compression - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotli - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotlicffi - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named 'cryptography.x509' - imported by urllib3.contrib.pyopenssl (delayed, optional)
|
||||
missing module named cryptography - imported by urllib3.contrib.pyopenssl (top-level), requests (conditional, optional)
|
||||
missing module named OpenSSL - imported by urllib3.contrib.pyopenssl (top-level)
|
||||
missing module named 'h2.events' - imported by urllib3.http2.connection (top-level)
|
||||
missing module named 'h2.connection' - imported by urllib3.http2.connection (top-level)
|
||||
missing module named h2 - imported by urllib3.http2.connection (top-level)
|
||||
missing module named brotli - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named brotlicffi - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||
missing module named win_inet_pton - imported by socks (conditional, optional)
|
||||
missing module named cryptography - imported by urllib3.contrib.pyopenssl (top-level), requests (conditional, optional)
|
||||
missing module named 'OpenSSL.crypto' - imported by urllib3.contrib.pyopenssl (delayed, conditional)
|
||||
missing module named 'cryptography.x509' - imported by urllib3.contrib.pyopenssl (delayed, optional)
|
||||
missing module named OpenSSL - imported by urllib3.contrib.pyopenssl (top-level)
|
||||
missing module named collections.Callable - imported by collections (optional), socks (optional)
|
||||
missing module named 'pyodide.ffi' - imported by urllib3.contrib.emscripten.fetch (delayed, optional)
|
||||
missing module named pyodide - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||
missing module named js - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||
missing module named wsaccel - imported by websocket._utils (optional)
|
||||
missing module named 'python_socks.sync' - imported by websocket._http (optional)
|
||||
missing module named 'python_socks._types' - imported by websocket._http (optional)
|
||||
missing module named python_socks - imported by websocket._http (optional)
|
||||
missing module named 'wsaccel.xormask' - imported by websocket._abnf (optional)
|
||||
missing module named six.moves.range - imported by six.moves (top-level), dateutil.rrule (top-level)
|
||||
runtime module named six.moves - imported by dateutil.tz.tz (top-level), dateutil.tz._factories (top-level), dateutil.tz.win (top-level), dateutil.rrule (top-level)
|
||||
missing module named dateutil.tz.tzfile - imported by dateutil.tz (top-level), dateutil.zoneinfo (top-level)
|
||||
@@ -306,6 +308,7 @@ missing module named setuptools_scm - imported by matplotlib (delayed, condition
|
||||
missing module named markupsafe - imported by pandas.io.formats.style_render (top-level)
|
||||
missing module named botocore - imported by pandas.io.common (delayed, conditional, optional)
|
||||
missing module named sets - imported by pytz.tzinfo (optional)
|
||||
missing module named collections.Mapping - imported by collections (optional), pytz.lazy (optional)
|
||||
missing module named UserDict - imported by pytz.lazy (optional)
|
||||
missing module named 'scipy.sparse' - imported by pandas.core.arrays.sparse.array (conditional), pandas.core.arrays.sparse.scipy_sparse (delayed, conditional), pandas.core.arrays.sparse.accessor (delayed), pandas.core.dtypes.common (delayed, conditional, optional)
|
||||
missing module named pandas.core.internals.Block - imported by pandas.core.internals (conditional), pandas.io.pytables (conditional)
|
||||
@@ -340,8 +343,5 @@ missing module named numpy_distutils - imported by numpy.f2py.diagnose (delayed,
|
||||
missing module named yaml - imported by numpy.__config__ (delayed)
|
||||
missing module named numpy._distributor_init_local - imported by numpy (optional), numpy._distributor_init (optional)
|
||||
missing module named pytest - imported by pandas._testing._io (delayed), pandas._testing (delayed)
|
||||
missing module named wsaccel - imported by websocket._utils (optional)
|
||||
missing module named 'python_socks.sync' - imported by websocket._http (optional)
|
||||
missing module named 'python_socks._types' - imported by websocket._http (optional)
|
||||
missing module named python_socks - imported by websocket._http (optional)
|
||||
missing module named 'wsaccel.xormask' - imported by websocket._abnf (optional)
|
||||
missing module named simplejson - imported by requests.compat (conditional, optional)
|
||||
missing module named dummy_threading - imported by requests.cookies (optional)
|
||||
|
||||
@@ -282,6 +282,9 @@ imports:
|
||||
• <a href="#functools">functools</a>
|
||||
• <a href="#genericpath">genericpath</a>
|
||||
• <a href="#globals.apis">globals.apis</a>
|
||||
• <a href="#globals.driver_utils">globals.driver_utils</a>
|
||||
• <a href="#globals.global_variable">globals.global_variable</a>
|
||||
• <a href="#globals.ids">globals.ids</a>
|
||||
• <a href="#heapq">heapq</a>
|
||||
• <a href="#io">io</a>
|
||||
• <a href="#keyword">keyword</a>
|
||||
@@ -291,6 +294,12 @@ imports:
|
||||
• <a href="#ntpath">ntpath</a>
|
||||
• <a href="#operator">operator</a>
|
||||
• <a href="#os">os</a>
|
||||
• <a href="#page_objects.download_tabbar_page">page_objects.download_tabbar_page</a>
|
||||
• <a href="#page_objects.login_page">page_objects.login_page</a>
|
||||
• <a href="#page_objects.more_download_page">page_objects.more_download_page</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
• <a href="#page_objects.upload_config_page">page_objects.upload_config_page</a>
|
||||
• <a href="#permissions">permissions</a>
|
||||
• <a href="#posixpath">posixpath</a>
|
||||
• <a href="#pyi_rth__tkinter.py">pyi_rth__tkinter.py</a>
|
||||
• <a href="#pyi_rth_inspect.py">pyi_rth_inspect.py</a>
|
||||
@@ -5117,7 +5126,6 @@ imported by:
|
||||
• <a href="#appium.webdriver">appium.webdriver</a>
|
||||
• <a href="#globals.driver_utils">globals.driver_utils</a>
|
||||
• <a href="#main">main</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -5234,7 +5242,6 @@ imported by:
|
||||
• <a href="#appium.options.android.uiautomator2">appium.options.android.uiautomator2</a>
|
||||
• <a href="#globals.driver_utils">globals.driver_utils</a>
|
||||
• <a href="#main">main</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8023,7 +8030,6 @@ imported by:
|
||||
• <a href="#appium.webdriver.webelement">appium.webdriver.webelement</a>
|
||||
• <a href="#globals.driver_utils">globals.driver_utils</a>
|
||||
• <a href="#main">main</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -18024,6 +18030,7 @@ imported by:
|
||||
• <a href="#page_objects.more_download_page">page_objects.more_download_page</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
• <a href="#page_objects.upload_config_page">page_objects.upload_config_page</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -18047,6 +18054,7 @@ imported by:
|
||||
• <a href="#page_objects.login_page">page_objects.login_page</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
• <a href="#page_objects.upload_config_page">page_objects.upload_config_page</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -18066,6 +18074,7 @@ imported by:
|
||||
• <a href="#page_objects.download_tabbar_page">page_objects.download_tabbar_page</a>
|
||||
• <a href="#page_objects.login_page">page_objects.login_page</a>
|
||||
• <a href="#page_objects.screenshot_page">page_objects.screenshot_page</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -18621,7 +18630,6 @@ imports:
|
||||
• <a href="#importlib">importlib</a>
|
||||
• <a href="#importlib._bootstrap">importlib._bootstrap</a>
|
||||
• <a href="#importlib._bootstrap_external">importlib._bootstrap_external</a>
|
||||
• <a href="#importlib.machinery">importlib.machinery</a>
|
||||
• <a href="#sys">sys</a>
|
||||
• <a href="#warnings">warnings</a>
|
||||
|
||||
@@ -18776,8 +18784,7 @@ imports:
|
||||
</div>
|
||||
<div class="import">
|
||||
imported by:
|
||||
<a href="#importlib">importlib</a>
|
||||
• <a href="#importlib.abc">importlib.abc</a>
|
||||
<a href="#importlib.abc">importlib.abc</a>
|
||||
• <a href="#inspect">inspect</a>
|
||||
• <a href="#packaging.tags">packaging.tags</a>
|
||||
• <a href="#pkg_resources">pkg_resources</a>
|
||||
@@ -38261,6 +38268,7 @@ imports:
|
||||
<div class="import">
|
||||
imported by:
|
||||
<a href="#main">main</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -38288,6 +38296,7 @@ imports:
|
||||
imported by:
|
||||
<a href="#main">main</a>
|
||||
• <a href="#page_objects.more_download_page">page_objects.more_download_page</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -38314,6 +38323,7 @@ imports:
|
||||
imported by:
|
||||
<a href="#main">main</a>
|
||||
• <a href="#page_objects.upload_config_page">page_objects.upload_config_page</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -38324,10 +38334,7 @@ imported by:
|
||||
<a target="code" href="///D:/Projects/cjgc_upload/page_objects/screenshot_page.py" type="text/plain"><tt>page_objects.screenshot_page</tt></a>
|
||||
<span class="moduletype">SourceModule</span> <div class="import">
|
||||
imports:
|
||||
<a href="#appium">appium</a>
|
||||
• <a href="#appium.options.android">appium.options.android</a>
|
||||
• <a href="#appium.webdriver">appium.webdriver</a>
|
||||
• <a href="#appium.webdriver.common.appiumby">appium.webdriver.common.appiumby</a>
|
||||
<a href="#appium.webdriver.common.appiumby">appium.webdriver.common.appiumby</a>
|
||||
• <a href="#datetime">datetime</a>
|
||||
• <a href="#globals.driver_utils">globals.driver_utils</a>
|
||||
• <a href="#globals.global_variable">globals.global_variable</a>
|
||||
@@ -38347,6 +38354,7 @@ imports:
|
||||
<div class="import">
|
||||
imported by:
|
||||
<a href="#main">main</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -38379,6 +38387,7 @@ imports:
|
||||
<div class="import">
|
||||
imported by:
|
||||
<a href="#main">main</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -51168,6 +51177,7 @@ imports:
|
||||
<div class="import">
|
||||
imported by:
|
||||
<a href="#main">main</a>
|
||||
• <a href="#scheduler.py">scheduler.py</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Binary file not shown.
0
dist/appium_automation.log
vendored
Normal file
0
dist/appium_automation.log
vendored
Normal file
BIN
dist/沉降观测自动上传.exe
vendored
BIN
dist/沉降观测自动上传.exe
vendored
Binary file not shown.
Binary file not shown.
@@ -31,7 +31,7 @@ def parse_time_config():
|
||||
|
||||
# 使用正则匹配用户名、时间、状态
|
||||
# 兼容 wangshun 16:40:20 true 和 cdwzq3liangchaoyong 15:06:35 true
|
||||
match = re.search(r'(\w+)\s+(\d{2}:\d{2}:\d{2})\s+true', line)
|
||||
match = re.search(r'(\w+)\s+(\d{2}:\d{2}:\d{2})\s+ok', line)
|
||||
if match:
|
||||
username = match.group(1)
|
||||
scheduled_time = match.group(2)
|
||||
@@ -49,7 +49,7 @@ def get_remote_tasks():
|
||||
# 1. 先获取本地文件中的配置
|
||||
local_times = parse_time_config()
|
||||
if not local_times:
|
||||
print("❌ time.txt 中没有有效的 true 任务或文件为空")
|
||||
print("❌ time.txt 中没有有效的 ok 任务或文件为空")
|
||||
return {}
|
||||
|
||||
# 2. 从服务器获取账户
|
||||
|
||||
5
main.py
5
main.py
@@ -131,7 +131,10 @@ class DeviceAutomation(object):
|
||||
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.get_username());
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -73,22 +73,32 @@ class LoginPage:
|
||||
pass
|
||||
|
||||
# 点击登录按钮
|
||||
login_btn = self.wait.until(
|
||||
EC.element_to_be_clickable((AppiumBy.ID, ids.LOGIN_BTN))
|
||||
)
|
||||
login_btn.click()
|
||||
self.logger.info("已点击登录按钮")
|
||||
max_retries = 3
|
||||
retry_count = 0
|
||||
|
||||
# 等待登录完成
|
||||
time.sleep(3)
|
||||
while retry_count < max_retries:
|
||||
login_btn = self.wait.until(
|
||||
EC.element_to_be_clickable((AppiumBy.ID, ids.LOGIN_BTN))
|
||||
)
|
||||
login_btn.click()
|
||||
self.logger.info(f"已点击登录按钮 (尝试 {retry_count + 1}/{max_retries})")
|
||||
|
||||
# 检查是否登录成功
|
||||
if self.is_login_successful():
|
||||
self.logger.info("登录成功")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning("登录后未检测到主页面元素")
|
||||
return False
|
||||
# 等待登录完成
|
||||
time.sleep(3)
|
||||
|
||||
# 检查是否登录成功
|
||||
if self.is_login_successful():
|
||||
self.logger.info("登录成功")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning("登录后未检测到主页面元素,准备重试")
|
||||
retry_count += 1
|
||||
if retry_count < max_retries:
|
||||
self.logger.info(f"等待2秒后重新尝试登录...")
|
||||
time.sleep(2)
|
||||
|
||||
self.logger.error(f"登录失败,已尝试 {max_retries} 次")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"登录过程中出错: {str(e)}")
|
||||
|
||||
@@ -178,7 +178,7 @@ class MoreDownloadPage:
|
||||
"""滑动年份选择器的滚轮"""
|
||||
try:
|
||||
# 获取年份选择器滚轮元素
|
||||
year_wheel = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/wheelView2")
|
||||
year_wheel = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/wheelView1")
|
||||
|
||||
# 获取滚轮的位置和尺寸
|
||||
location = year_wheel.location
|
||||
@@ -191,7 +191,7 @@ class MoreDownloadPage:
|
||||
# 计算滑动距离 - 滚轮高度的1/5
|
||||
swipe_distance = size['height'] // 5
|
||||
|
||||
for i in range(3):
|
||||
for i in range(1):
|
||||
# 执行滑动操作 - 从中心向上滑动1/5高度
|
||||
self.driver.swipe(center_x, center_y - swipe_distance, center_x, center_y, 500)
|
||||
|
||||
@@ -369,7 +369,7 @@ class MoreDownloadPage:
|
||||
continue
|
||||
return False
|
||||
|
||||
def wait_for_loading_dialog(self, timeout=900, download_type="unknown", retry_count=0):
|
||||
def wait_for_loading_dialog(self, timeout=1200, download_type="unknown", retry_count=0):
|
||||
"""
|
||||
检查加载弹窗的出现和消失,支持最多1次重试(总共执行2次)
|
||||
"""
|
||||
|
||||
@@ -19,7 +19,8 @@ import globals.global_variable as global_variable # 导入全局变量模块
|
||||
class ScreenshotPage:
|
||||
def __init__(self, driver, wait, device_id=None):
|
||||
self.driver = driver
|
||||
self.wait = wait
|
||||
# self.wait = wait
|
||||
self.wait = WebDriverWait(self.driver, 0.5)
|
||||
self.device_id = device_id
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.all_items = set()
|
||||
@@ -233,7 +234,7 @@ class ScreenshotPage:
|
||||
def check_apply_btn(self):
|
||||
"""检查是否有平差处理按钮"""
|
||||
try:
|
||||
apply_btn = WebDriverWait(self.driver, 5).until(
|
||||
apply_btn = WebDriverWait(self.driver, 1).until(
|
||||
EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/point_measure_btn"))
|
||||
)
|
||||
if apply_btn.is_displayed():
|
||||
@@ -548,117 +549,6 @@ class ScreenshotPage:
|
||||
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:
|
||||
# 使用XPath查找文本为"测量结束"的按钮
|
||||
measurement_end_button = self.driver.find_element(
|
||||
AppiumBy.XPATH,
|
||||
"//android.widget.Button[@text='测量结束']"
|
||||
)
|
||||
|
||||
if measurement_end_button.is_displayed() and measurement_end_button.is_enabled():
|
||||
self.logger.info(f"设备检测到测量结束按钮")
|
||||
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(f"设备驱动重新初始化成功")
|
||||
else:
|
||||
self.logger.error(f"设备驱动重新初始化失败")
|
||||
# 继续尝试,而不是立即失败
|
||||
|
||||
# 等待一段时间后再次检查
|
||||
time.sleep(3)
|
||||
|
||||
# 每30秒输出一次等待状态
|
||||
if int(time.time() - start_time) % 30 == 0:
|
||||
elapsed = int(time.time() - start_time)
|
||||
self.logger.info(f"设备 {self.device_id} 已等待 {elapsed} 秒,仍在等待测量结束...")
|
||||
|
||||
self.logger.error(f"设备 {self.device_id} 等待测量结束按钮超时")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"设备 {self.device_id} 等待测量结束时发生错误: {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, 1)
|
||||
|
||||
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 handle_confirmation_dialog(self, device_id, timeout=2):
|
||||
"""
|
||||
处理确认弹窗,点击"是"按钮
|
||||
@@ -672,31 +562,68 @@ class ScreenshotPage:
|
||||
"""
|
||||
# 等待弹窗出现(最多等待2秒)
|
||||
try:
|
||||
dialog_message = WebDriverWait(self.driver, timeout).until(
|
||||
EC.presence_of_element_located((AppiumBy.XPATH, "//android.widget.TextView[@text='是否退出测量界面?']"))
|
||||
)
|
||||
# dialog_message = WebDriverWait(self.driver, timeout).until(
|
||||
# EC.presence_of_element_located((AppiumBy.XPATH, "//android.widget.TextView[@text='是否退出测量界面?']"))
|
||||
# )
|
||||
|
||||
self.logger.info(f"设备 {device_id} 检测到确认弹窗")
|
||||
# self.logger.info(f"设备 {device_id} 检测到确认弹窗")
|
||||
|
||||
# 查找并点击"是"按钮
|
||||
confirm_button = self.driver.find_element(
|
||||
AppiumBy.XPATH,
|
||||
"//android.widget.Button[@text='是' and @resource-id='android:id/button1']"
|
||||
)
|
||||
# # 查找并点击"是"按钮
|
||||
# confirm_button = self.driver.find_element(
|
||||
# AppiumBy.XPATH,
|
||||
# "//android.widget.Button[@text='是' and @resource-id='android:id/button1']"
|
||||
# )
|
||||
|
||||
if confirm_button.is_displayed() and confirm_button.is_enabled():
|
||||
self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮")
|
||||
confirm_button.click()
|
||||
time.sleep(0.5)
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"设备 {device_id} '是'按钮不可点击")
|
||||
return False
|
||||
# if confirm_button.is_displayed() and confirm_button.is_enabled():
|
||||
# self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮")
|
||||
# confirm_button.click()
|
||||
# time.sleep(0.5)
|
||||
# return True
|
||||
# else:
|
||||
# self.logger.error(f"设备 {device_id} '是'按钮不可点击")
|
||||
# return False
|
||||
|
||||
except TimeoutException:
|
||||
# 超时未找到弹窗,认为没有弹窗,返回成功
|
||||
self.logger.info(f"设备 {device_id} 等待 {timeout} 秒未发现确认弹窗,可能没有弹窗,返回成功")
|
||||
return True
|
||||
# except TimeoutException:
|
||||
# # 超时未找到弹窗,认为没有弹窗,返回成功
|
||||
# self.logger.info(f"设备 {device_id} 等待 {timeout} 秒未发现确认弹窗,可能没有弹窗,返回成功")
|
||||
# return True
|
||||
max_attempts = 2
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
dialog_message = WebDriverWait(self.driver, timeout).until(
|
||||
EC.presence_of_element_located((AppiumBy.XPATH, "//android.widget.TextView[@text='是否退出测量界面?']"))
|
||||
)
|
||||
|
||||
self.logger.info(f"设备 {device_id} 检测到确认弹窗 (第 {attempt + 1} 次)")
|
||||
|
||||
# 查找并点击"是"按钮
|
||||
confirm_button = self.driver.find_element(
|
||||
AppiumBy.XPATH,
|
||||
"//android.widget.Button[@text='是' and @resource-id='android:id/button1']"
|
||||
)
|
||||
|
||||
if confirm_button.is_displayed() and confirm_button.is_enabled():
|
||||
self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮 (第 {attempt + 1} 次)")
|
||||
confirm_button.click()
|
||||
time.sleep(0.5)
|
||||
|
||||
# 如果是第一次尝试,继续检查是否还有弹窗
|
||||
if attempt < max_attempts - 1:
|
||||
self.logger.info(f"设备 {device_id} 等待 1 秒后检查是否还有弹窗")
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"设备 {device_id} '是'按钮不可点击")
|
||||
return False
|
||||
except TimeoutException:
|
||||
# 超时未找到弹窗,认为没有弹窗,返回成功
|
||||
self.logger.info(f"设备 {device_id} 等待 {timeout} 秒未发现确认弹窗,可能没有弹窗,返回成功")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"设备 {device_id} 处理确认弹窗时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def click_back_button(self, device_id):
|
||||
"""点击手机系统返回按钮"""
|
||||
@@ -765,7 +692,7 @@ class ScreenshotPage:
|
||||
if confirm_button and confirm_button.is_displayed() and confirm_button.is_enabled():
|
||||
self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮")
|
||||
confirm_button.click()
|
||||
time.sleep(1)
|
||||
time.sleep(0.5)
|
||||
|
||||
# 验证弹窗是否消失
|
||||
try:
|
||||
@@ -785,7 +712,7 @@ class ScreenshotPage:
|
||||
except Exception as e:
|
||||
self.logger.warning(f"设备 {device_id} 查找确认弹窗时出现异常: {str(e)}")
|
||||
|
||||
time.sleep(1)
|
||||
time.sleep(0.5)
|
||||
|
||||
self.logger.error(f"设备 {device_id} 等待返回确认弹窗超时")
|
||||
return False
|
||||
@@ -884,7 +811,7 @@ class ScreenshotPage:
|
||||
# time.sleep(2)
|
||||
self.logger.info(f"已点击平差处理按钮,检查是否在测量页面")
|
||||
|
||||
# 检测是否存在测量列表(修正逻辑)
|
||||
# 检测是否存在测量列表
|
||||
has_measurement_list = self.check_measurement_list(device_id)
|
||||
if not has_measurement_list:
|
||||
self.logger.info(f"设备 {device_id} 存在测量列表,重新执行平差流程")
|
||||
@@ -943,7 +870,7 @@ class ScreenshotPage:
|
||||
|
||||
|
||||
# 3. 验证是否成功返回到上一页面
|
||||
time.sleep(1) # 等待页面跳转完成
|
||||
time.sleep(0.5) # 等待页面跳转完成
|
||||
|
||||
# 可以添加页面验证逻辑,比如检查是否返回到预期的页面
|
||||
# 这里可以根据实际应用添加特定的页面元素验证
|
||||
@@ -967,7 +894,7 @@ class ScreenshotPage:
|
||||
"""
|
||||
try:
|
||||
self.logger.info(f"设备 {device_id} 开始执行测量结束后的操作流程")
|
||||
time.sleep(5)
|
||||
time.sleep(0.5)
|
||||
|
||||
# 1. 下滑列表到最底端
|
||||
if not self.scroll_list_to_bottom(device_id):
|
||||
@@ -1071,7 +998,7 @@ class ScreenshotPage:
|
||||
if self.click_last_spinner(device_id):
|
||||
return True
|
||||
self.logger.warning(f"设备 {device_id} 第{attempt + 1}次点击失败,准备重试")
|
||||
time.sleep(1) # 重试前等待
|
||||
time.sleep(0.5) # 重试前等待
|
||||
except Exception as e:
|
||||
self.logger.error(f"设备 {device_id} 第{attempt + 1}次尝试失败: {str(e)}")
|
||||
|
||||
@@ -1133,7 +1060,8 @@ class ScreenshotPage:
|
||||
new_driver, new_wait = reconnect_driver(actual_device_id, self.driver)
|
||||
if new_driver:
|
||||
self.driver = new_driver
|
||||
self.wait = new_wait
|
||||
# self.wait = new_wait
|
||||
self.wait = WebDriverWait(self.driver, 2)
|
||||
self.logger.info(f"设备 {actual_device_id} 驱动重连成功")
|
||||
else:
|
||||
self.logger.error(f"设备 {actual_device_id} 驱动重连失败")
|
||||
@@ -1189,8 +1117,8 @@ class ScreenshotPage:
|
||||
|
||||
retry_count += 1
|
||||
if retry_count < max_retries:
|
||||
self.logger.info(f"设备 {device_id} 将在1秒后进行第{retry_count+1}次重试")
|
||||
time.sleep(1) # 等待1秒后重试
|
||||
self.logger.info(f"设备 {device_id} 将在0.5秒后进行第{retry_count+1}次重试")
|
||||
time.sleep(0.5) # 等待0.5秒后重试
|
||||
|
||||
self.logger.error(f"设备 {device_id} 经过{max_retries}次重试后仍无法展开下拉菜单")
|
||||
return False
|
||||
@@ -1222,7 +1150,7 @@ class ScreenshotPage:
|
||||
'percent': 0.5
|
||||
})
|
||||
|
||||
time.sleep(1)
|
||||
time.sleep(0.5)
|
||||
self.logger.info(f"设备 {device_id} 额外下滑完成")
|
||||
return True
|
||||
|
||||
@@ -1343,8 +1271,10 @@ class ScreenshotPage:
|
||||
|
||||
# 检查GLOBAL_UPLOAD_BREAKPOINT_DICT是否为空,如果为空则初始化一些测试数据
|
||||
if not global_variable.get_upload_breakpoint_dict():
|
||||
self.logger.warning("global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT为空,正在初始化测试数据")
|
||||
global_variable.set_upload_breakpoint_dict({'CDWZQ-2标-龙骨湾右线大桥-0-7号墩-平原': 'L156372', 'CDWZQ-2标-蓝家湾特大 桥-31-31-平原': 'L159206'})
|
||||
self.logger.warning("上传列表为空,无法执行平差操作")
|
||||
return False
|
||||
|
||||
# global_variable.set_upload_breakpoint_dict({'CDWZQ-2标-龙骨湾右线大桥-0-7号墩-平原': 'L156372', 'CDWZQ-2标-蓝家湾特大 桥-31-31-平原': 'L159206'})
|
||||
|
||||
breakpoint_names = list(global_variable.get_upload_breakpoint_dict().keys())
|
||||
processed_breakpoints = []
|
||||
@@ -1451,22 +1381,3 @@ class ScreenshotPage:
|
||||
time.sleep(10)
|
||||
|
||||
return False
|
||||
def run_automation_test(self):
|
||||
# 滑动列表到底部
|
||||
if not self.scroll_list_to_bottom(self.device_id):
|
||||
self.logger.error(f"设备 {self.device_id} 下滑列表到底部失败")
|
||||
return False
|
||||
|
||||
# 2. 点击最后一个spinner
|
||||
if not self.click_last_spinner_with_retry(self.device_id):
|
||||
self.logger.error(f"设备 {self.device_id} 点击最后一个spinner失败")
|
||||
return False
|
||||
|
||||
# 3. 再下滑一次
|
||||
if not self.scroll_down_once(self.device_id):
|
||||
self.logger.warning(f"设备 {self.device_id} 再次下滑失败,但继续执行")
|
||||
|
||||
# 4. 点击平差处理按钮
|
||||
if not self.click_adjustment_button(self.device_id):
|
||||
self.logger.error(f"设备 {self.device_id} 点击平差处理按钮失败")
|
||||
return False
|
||||
|
||||
@@ -40,7 +40,7 @@ class UploadConfigPage:
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取当前activity时出错: {str(e)}")
|
||||
|
||||
# 尝试返回到主页面(如果不在主页面)
|
||||
# 尝试返回到主页面(如果不在主页面)保存上传
|
||||
self.logger.info("尝试返回到主页面...")
|
||||
max_back_presses = 5 # 最多按返回键次数
|
||||
back_press_count = 0
|
||||
@@ -932,6 +932,19 @@ class UploadConfigPage:
|
||||
condition_button.click()
|
||||
self.logger.info(f"成功点击{work_type_name}工况选择按钮")
|
||||
|
||||
# 检查是否有ListView,如果没有就再次点击
|
||||
try:
|
||||
# 尝试查找ListView
|
||||
self.wait.until(
|
||||
EC.presence_of_element_located((AppiumBy.CLASS_NAME, "android.widget.ListView"))
|
||||
)
|
||||
self.logger.info(f"找到{work_type_name}工况选择的ListView")
|
||||
except TimeoutException:
|
||||
# 没有找到ListView,再次点击按钮
|
||||
self.logger.warning(f"未找到{work_type_name}工况选择的ListView,再次点击按钮")
|
||||
condition_button.click()
|
||||
self.logger.info(f"再次成功点击{work_type_name}工况选择按钮")
|
||||
|
||||
# 选择主要的工况选项
|
||||
if self._select_condition_option(workinfo_name):
|
||||
self.logger.info(f"成功为{work_type_name}选择主要工况: {workinfo_name}")
|
||||
@@ -1120,10 +1133,24 @@ class UploadConfigPage:
|
||||
"com.bjjw.cjgc:id/point_workinfo_sp"
|
||||
)
|
||||
|
||||
|
||||
# 验证按钮是否可见和可用
|
||||
if workinfo_button.is_displayed() and workinfo_button.is_enabled():
|
||||
workinfo_button.click()
|
||||
self.logger.info(f"已点击测点 {point_id} 的工况选择按钮")
|
||||
self.logger.info(f"已点击测点 {point_id} 的次要工况选择按钮")
|
||||
# 检查是否有ListView,如果没有就再次点击
|
||||
try:
|
||||
# 尝试查找ListView
|
||||
self.wait.until(
|
||||
EC.presence_of_element_located((AppiumBy.CLASS_NAME, "android.widget.ListView"))
|
||||
)
|
||||
self.logger.info(f"找到{point_id}次要工况选择的ListView")
|
||||
except TimeoutException:
|
||||
# 没有找到ListView,再次点击按钮
|
||||
self.logger.warning(f"未找到{point_id}次要工况选择的ListView,再次点击按钮")
|
||||
workinfo_button.click()
|
||||
self.logger.info(f"再次成功点击{point_id}次要工况选择按钮")
|
||||
|
||||
|
||||
# 选择对应的工况选项
|
||||
if self._select_minor_conditions_option(workinfoname, work_type):
|
||||
@@ -1370,33 +1397,6 @@ class UploadConfigPage:
|
||||
|
||||
return points
|
||||
|
||||
def _set_single_point_work_condition(self, point_data: Dict, workinfo_name: str, work_type: str) -> bool:
|
||||
"""为单个测点设置工况信息"""
|
||||
try:
|
||||
point_name = point_data.get('point_name')
|
||||
self.logger.info(f"开始为测点 {point_name} 设置工况: {workinfo_name}")
|
||||
|
||||
# 使用保存的元素引用点击工况选择按钮
|
||||
workinfo_element = point_data.get('workinfo_element')
|
||||
if workinfo_element:
|
||||
workinfo_element.click()
|
||||
time.sleep(1) # 等待选项弹出
|
||||
|
||||
# 选择指定的工况
|
||||
if self._select_condition_option(workinfo_name):
|
||||
self.logger.info(f"成功为测点 {point_name} 设置工况: {workinfo_name}")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning(f"为测点 {point_name} 选择工况选项失败")
|
||||
return False
|
||||
else:
|
||||
self.logger.warning(f"未找到测点 {point_name} 的工况选择按钮")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"为测点 {point_name} 设置工况时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def _select_condition_option(self, condition_name: str) -> bool:
|
||||
"""选择具体的工况选项
|
||||
|
||||
@@ -1434,18 +1434,18 @@ class UploadConfigPage:
|
||||
self.logger.debug("未找到列表项形式的工况选项")
|
||||
|
||||
# 方法3: 尝试点击屏幕特定位置(备选方案)
|
||||
try:
|
||||
# 获取屏幕尺寸
|
||||
window_size = self.driver.get_window_size()
|
||||
x = window_size['width'] // 2
|
||||
y = window_size['height'] // 2
|
||||
# try:
|
||||
# # 获取屏幕尺寸
|
||||
# window_size = self.driver.get_window_size()
|
||||
# x = window_size['width'] // 2
|
||||
# y = window_size['height'] // 2
|
||||
|
||||
# 点击屏幕中央(假设选项在中间)
|
||||
self.driver.tap([(x, y)])
|
||||
self.logger.info("通过点击屏幕中央选择工况")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.debug(f"点击屏幕中央失败: {str(e)}")
|
||||
# # 点击屏幕中央(假设选项在中间)
|
||||
# self.driver.tap([(x, y)])
|
||||
# self.logger.info("通过点击屏幕中央选择工况")
|
||||
# return True
|
||||
# except Exception as e:
|
||||
# self.logger.debug(f"点击屏幕中央失败: {str(e)}")
|
||||
|
||||
self.logger.error(f"所有方法都无法选择工况选项: {condition_name}")
|
||||
return False
|
||||
@@ -1606,7 +1606,7 @@ class UploadConfigPage:
|
||||
self.logger.info("开始等待上传完成")
|
||||
|
||||
# 等待弹窗显示
|
||||
upload_list = WebDriverWait(self.driver, 10).until(
|
||||
upload_list = WebDriverWait(self.driver, 2).until(
|
||||
EC.presence_of_element_located((AppiumBy.ID, "android:id/customPanel"))
|
||||
)
|
||||
#等待弹窗消失
|
||||
@@ -1862,13 +1862,13 @@ class UploadConfigPage:
|
||||
return False
|
||||
|
||||
|
||||
# 表达填写完成,点击"保存上传"并处理弹窗
|
||||
if not self.click_save_upload_and_handle_dialogs():
|
||||
self.logger.error("点击保存上传并处理弹窗失败")
|
||||
return False
|
||||
# # 表达填写完成,点击"保存上传"并处理弹窗
|
||||
# if not self.click_save_upload_and_handle_dialogs():
|
||||
# self.logger.error("点击保存上传并处理弹窗失败")
|
||||
# return False
|
||||
|
||||
# # 暂不上传,使用返回按钮替代。
|
||||
# self.driver.back()
|
||||
# 暂不上传,使用返回按钮替代。
|
||||
self.driver.back()
|
||||
|
||||
|
||||
# 等待上传,查看loading弹窗。没有就下一个
|
||||
|
||||
12
scheduler.py
12
scheduler.py
@@ -73,7 +73,7 @@ def parse_time_config():
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
# 匹配:用户名 时间 true (仅获取待处理任务)
|
||||
match = re.search(r'(\w+)\s+(\d{1,2}:\d{2}:\d{2})\s+true$', line)
|
||||
match = re.search(r'(\w+)\s+(\d{1,2}:\d{2}:\d{2})\s+ok$', line)
|
||||
if match:
|
||||
username, scheduled_time = match.group(1), match.group(2)
|
||||
time_map[username] = scheduled_time
|
||||
@@ -83,7 +83,7 @@ def parse_time_config():
|
||||
|
||||
def get_combined_tasks():
|
||||
"""
|
||||
结合接口(is_ok==1)和本地文件(true)筛选任务
|
||||
结合接口(is_ok==1)和本地文件(ok)筛选任务
|
||||
"""
|
||||
try:
|
||||
local_times = parse_time_config()
|
||||
@@ -99,12 +99,12 @@ def get_combined_tasks():
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
for account in accounts:
|
||||
if account.get('is_ok') == 1:
|
||||
if account.get('is_ok') == 1 or account.get('username') == "CZSCZQ13A1xuliguo":
|
||||
user = account.get('username')
|
||||
ip = account.get('device_ip')
|
||||
port = account.get('device_port')
|
||||
|
||||
# 只有在 time.txt 中是 true 的账号才会被加入
|
||||
# 只有在 time.txt 中是 ok 的账号才会被加入
|
||||
if user in local_times and ip and port:
|
||||
address = f"{ip}:{port}"
|
||||
# full_time = f"{today} {local_times[user]}"
|
||||
@@ -128,9 +128,9 @@ def run_task(address, target_time, username):
|
||||
"""
|
||||
单个执行线程:锁定状态 -> 等待 -> 执行 -> 完成
|
||||
"""
|
||||
# 1. 尝试将状态从 true 改为 running (锁定任务)
|
||||
# 1. 尝试将状态从 ok 改为 running (锁定任务)
|
||||
# 如果此时文件状态已被其他逻辑修改,则放弃执行,防止重复
|
||||
if not update_file_status(username, "true", "running"):
|
||||
if not update_file_status(username, "ok", "running"):
|
||||
return f"⏭️ {username} 状态已变更,跳过执行。"
|
||||
|
||||
print(f"🚀 [任务锁定] 设备: {address} | 用户: {username} | 计划时间: {target_time}")
|
||||
|
||||
3
test_results/上传失败的断点.txt
Normal file
3
test_results/上传失败的断点.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
CZSCZQ-13A-二工区-沙马大桥-7#墩-山区
|
||||
CZSCZQ-13A-二工区-沙马大桥-2#墩身-山区
|
||||
CZSCZQ-13A-二工区-沙马大桥-1#墩身-山区
|
||||
0
test_results/上传成功的断点.txt
Normal file
0
test_results/上传成功的断点.txt
Normal file
@@ -1,25 +1,30 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['scheduler.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hiddenimports=['main', 'globals.apis', 'globals.ids', 'globals.global_variable', 'permissions', 'globals.driver_utils', 'page_objects.login_page', 'page_objects.download_tabbar_page', 'page_objects.screenshot_page', 'page_objects.upload_config_page', 'page_objects.more_download_page'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='沉降观测自动上传',
|
||||
|
||||
Reference in New Issue
Block a user