Add cross-flow automation test and tone down log/comment wording
- main_router: add run_automation_test that runs each site's download flows in a cross sequence (expected/actual) plus a pass/fail report with timings; expose it as menu [8] - Site download functions now return False on their exception paths so the test harness can record failures (baishi also flags config-read failure) - Replace overblown log/comment phrasing across all modules with plain statements; trim docstrings Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
174
main_router.py
174
main_router.py
@@ -1,6 +1,7 @@
|
||||
# main_router.py
|
||||
|
||||
import os
|
||||
import time
|
||||
import yaml
|
||||
import pandas as pd
|
||||
from playwright.sync_api import sync_playwright
|
||||
@@ -22,7 +23,7 @@ SITES_CONFIG = {
|
||||
}
|
||||
|
||||
# ====================================================================
|
||||
# 🛡️ 网点特征注册表:定义每个网点登录成功、成功进入工作台的标志性控件
|
||||
# 站点就绪特征:每个站点登录成功进入工作台后的标志性控件
|
||||
# ====================================================================
|
||||
READY_SELECTORS = {
|
||||
"顺心": 'h1:has-text("盟商门户网")',
|
||||
@@ -33,7 +34,7 @@ READY_SELECTORS = {
|
||||
|
||||
|
||||
def task_process_undelivered_data(site_name="顺心"):
|
||||
"""全局模块:应到未到异常件比对引擎 (支持动态网点前缀)"""
|
||||
"""应到未到比对:找出应到但未实到的运单 (按站点前缀)"""
|
||||
print(f"\n▶ 开始执行【{site_name} - 应到未到数据处理】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
@@ -56,10 +57,10 @@ def task_process_undelivered_data(site_name="顺心"):
|
||||
df_actual = pd.read_excel(actual_path, dtype=str, keep_default_na=False)
|
||||
|
||||
if "运单号" not in df_expected.columns or "运单号" not in df_actual.columns:
|
||||
print("❌ 核心资产校验失败:数据源中缺失【运单号】字段,请检查导出配置。")
|
||||
print("❌ 校验失败:数据源缺少【运单号】字段,请检查导出配置。")
|
||||
return
|
||||
|
||||
print(">> 正在启动多维数据集比对引擎...")
|
||||
print(">> 正在比对应到与实到数据...")
|
||||
|
||||
# 统一运单号为去空白字符串,消除 int/float 与 str 混读导致 isin 永不命中的隐患
|
||||
df_expected["运单号"] = df_expected["运单号"].astype(str).str.strip()
|
||||
@@ -78,20 +79,120 @@ def task_process_undelivered_data(site_name="顺心"):
|
||||
]
|
||||
df_output = df_undelivered[available_columns]
|
||||
|
||||
print(f">> 筛选完毕!共捕获到异常【应到未到】货物数据: {len(df_output)} 条。")
|
||||
print(f">> 比对完成,共筛选出【应到未到】运单: {len(df_output)} 条。")
|
||||
|
||||
df_output.to_excel(output_path, index=False)
|
||||
print(f"====================================================")
|
||||
print(f" 🎉 异常比对流完成!独立数据已安全输出。")
|
||||
print(f" 📁 成果归档路径: {output_path}")
|
||||
print(f" 比对完成,结果已输出。")
|
||||
print(f" 📁 输出路径: {output_path}")
|
||||
print(f"====================================================")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 数据处理引擎在执行连接和输出时发生致命异常: {e}")
|
||||
print(f"❌ 比对过程中发生异常: {e}")
|
||||
|
||||
|
||||
# ====================================================================
|
||||
# 自动化测试入口
|
||||
# ====================================================================
|
||||
# 每个(非百世)站点的交叉测试序列:覆盖两种下载流程之间的全部 4 种相邻转换,
|
||||
# 用于验证无论上一个流程把页面留在什么状态,下一个流程都能正常运行:
|
||||
# 应到->实到、实到->应到、应到->应到、实到->实到
|
||||
CROSS_TEST_SEQUENCE = ["expected", "actual", "expected", "expected", "actual", "actual"]
|
||||
|
||||
|
||||
def run_automation_test(pages_map):
|
||||
"""自动化测试入口:按交叉序列逐个跑通各站点的下载流程,结束后打印统计报告。
|
||||
|
||||
判定规则:流程函数返回 False 或抛出异常记为 FAIL,其余记为 PASS。
|
||||
"""
|
||||
# 站点 -> {流程键: (中文名, 流程函数)};百世为单流程,单独处理
|
||||
flow_table = {
|
||||
"顺心": {
|
||||
"expected": ("应到", site_shunxin.shunxin_expected_download),
|
||||
"actual": ("实到", site_shunxin.shunxin_actual_download),
|
||||
},
|
||||
"中通": {
|
||||
"expected": ("应到", site_zto.zto_expected_download),
|
||||
"actual": ("实到", site_zto.zto_actual_download),
|
||||
},
|
||||
"韵达": {
|
||||
"expected": ("应到", site_yunda.yunda_expected_download),
|
||||
"actual": ("实到", site_yunda.yunda_actual_download),
|
||||
},
|
||||
}
|
||||
|
||||
# 构建测试计划:[(站点, 流程中文名, 流程函数), ...]
|
||||
plan = []
|
||||
for site_name, flows in flow_table.items():
|
||||
if site_name not in pages_map:
|
||||
continue
|
||||
for flow_key in CROSS_TEST_SEQUENCE:
|
||||
label, func = flows[flow_key]
|
||||
plan.append((site_name, label, func))
|
||||
# 百世:单流程,跑一次即可
|
||||
if "百世" in pages_map:
|
||||
plan.append(("百世", "应到未到", site_baishi.baishi_download_undelivered_data))
|
||||
|
||||
if not plan:
|
||||
print("\n⚠️ 当前没有已就绪的站点,无法执行自动化测试。")
|
||||
return
|
||||
|
||||
total = len(plan)
|
||||
print("\n====================================================")
|
||||
print(f"自动化测试开始,共 {total} 个步骤。")
|
||||
print("(双流程站点按应到/实到交叉序列执行,覆盖全部相邻转换)")
|
||||
print("====================================================")
|
||||
|
||||
results = [] # [(站点, 流程, 状态, 耗时秒, 错误信息)]
|
||||
for idx, (site_name, label, func) in enumerate(plan, start=1):
|
||||
print("\n----------------------------------------------------")
|
||||
print(f"[步骤 {idx}/{total}] 站点【{site_name}】流程【{label}】")
|
||||
print("----------------------------------------------------")
|
||||
page = pages_map[site_name]
|
||||
start = time.time()
|
||||
status = "PASS"
|
||||
err = ""
|
||||
try:
|
||||
page.bring_to_front()
|
||||
ret = func(page)
|
||||
if ret is False:
|
||||
status = "FAIL"
|
||||
err = "流程返回失败状态"
|
||||
except Exception as e:
|
||||
status = "FAIL"
|
||||
err = str(e)
|
||||
elapsed = time.time() - start
|
||||
results.append((site_name, label, status, elapsed, err))
|
||||
print(f">> 步骤结果: {status} (耗时 {elapsed:.1f}s)")
|
||||
|
||||
_print_test_report(results)
|
||||
|
||||
|
||||
def _print_test_report(results):
|
||||
"""打印自动化测试统计报告。"""
|
||||
passed = sum(1 for r in results if r[2] == "PASS")
|
||||
failed = len(results) - passed
|
||||
|
||||
print("\n====================================================")
|
||||
print("自动化测试统计报告")
|
||||
print("====================================================")
|
||||
for i, (site_name, label, status, elapsed, err) in enumerate(results, start=1):
|
||||
mark = "✅" if status == "PASS" else "❌"
|
||||
print(f" {i:>2}. {mark} {status} {site_name} - {label} (耗时 {elapsed:.1f}s)")
|
||||
if err:
|
||||
note = err if len(err) <= 60 else err[:57] + "..."
|
||||
print(f" 说明: {note}")
|
||||
print("----------------------------------------------------")
|
||||
print(f" 合计 {len(results)} 步:通过 {passed},失败 {failed}")
|
||||
if failed == 0:
|
||||
print(" ✅ 全部流程跑通。")
|
||||
else:
|
||||
print(" ❌ 存在失败流程,请结合上方说明与运行日志排查。")
|
||||
print("====================================================")
|
||||
|
||||
|
||||
def run_multi_site_daemon():
|
||||
"""多网点自动化主控引擎 (状态机卫语句驱动版)"""
|
||||
"""多站点自动化主控流程"""
|
||||
|
||||
# 1. 读取配置文件
|
||||
debug_mode = False
|
||||
@@ -108,7 +209,7 @@ def run_multi_site_daemon():
|
||||
# 动态确定需要挂载启动的网页
|
||||
active_sites = {}
|
||||
if debug_mode and debug_target in SITES_CONFIG:
|
||||
print(f"\n🛠️ 【调试模式激活】当前中控台仅挂载并开启目标站点: [{debug_target}]")
|
||||
print(f"\n🛠️ 【调试模式】仅加载目标站点: [{debug_target}]")
|
||||
active_sites = {debug_target: SITES_CONFIG[debug_target]}
|
||||
else:
|
||||
active_sites = SITES_CONFIG
|
||||
@@ -120,7 +221,7 @@ def run_multi_site_daemon():
|
||||
pages_map = {}
|
||||
|
||||
print("\n====================================================")
|
||||
print("【唤醒阶段】正在构建多网点并行运行环境...")
|
||||
print("【启动】正在打开各站点页面...")
|
||||
print("====================================================")
|
||||
|
||||
for site_name, url in active_sites.items():
|
||||
@@ -130,7 +231,7 @@ def run_multi_site_daemon():
|
||||
pages_map[site_name] = page
|
||||
|
||||
print("\n====================================================")
|
||||
print("【状态自检】启动前置智能接管机制...")
|
||||
print("【登录检测】正在准备各站点登录...")
|
||||
print("====================================================")
|
||||
|
||||
# 针对支持纯代码自动登录的站点,在此处前置注入登录事件
|
||||
@@ -142,37 +243,37 @@ def run_multi_site_daemon():
|
||||
print(f" ⚠️ 韵达前置自动登录模块发生波动: {e}")
|
||||
|
||||
# ====================================================================
|
||||
# 🛡️ 就绪卫语句轮询器 (Ready Guard Polling)
|
||||
# 无需死板的输入回车,全自动识别状态并无缝放行
|
||||
# 就绪轮询 (Ready Guard Polling)
|
||||
# 自动识别各站点登录完成状态,无需手动回车
|
||||
# ====================================================================
|
||||
ready_status = {site: False for site in active_sites.keys()}
|
||||
|
||||
print("\n>> 正在静默轮询全网点就绪状态 (自动免密/手工登录均可激活)...")
|
||||
print("\n>> 正在轮询各站点就绪状态 (自动登录或手动登录均可)...")
|
||||
while not all(ready_status.values()):
|
||||
for site_name, page in pages_map.items():
|
||||
if not ready_status[site_name]:
|
||||
try:
|
||||
# 0.5秒轻量级探针,避免阻塞主循环
|
||||
# 0.5 秒轻量探测,避免阻塞主循环
|
||||
if page.locator(READY_SELECTORS[site_name]).is_visible(
|
||||
timeout=500
|
||||
):
|
||||
ready_status[site_name] = True
|
||||
print(f" 🎉 【{site_name}】探测到主页特征,状态已就绪!")
|
||||
print(f" ✅ 【{site_name}】已检测到主页,登录就绪。")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pending_sites = [s for s, ready in ready_status.items() if not ready]
|
||||
if pending_sites:
|
||||
print(
|
||||
f" ⏳ 等待以下网点登录验证: [{', '.join(pending_sites)}] ... (请在浏览器中操作)"
|
||||
f" ⏳ 等待以下站点完成登录: [{', '.join(pending_sites)}] ... (请在浏览器中操作)"
|
||||
)
|
||||
page.wait_for_timeout(3000) # 挂起 3 秒后执行下一轮盘点
|
||||
page.wait_for_timeout(3000) # 等待 3 秒后进行下一轮检查
|
||||
|
||||
print("\n====================================================")
|
||||
print("【接管阶段】所有活跃网点均已就绪,正在执行环境净化...")
|
||||
print("【准备】所有站点已就绪,正在清理初始弹窗...")
|
||||
print("====================================================")
|
||||
|
||||
# 顺心环境净化
|
||||
# 顺心:处理初始弹窗
|
||||
if "顺心" in pages_map:
|
||||
try:
|
||||
sx_page = pages_map["顺心"]
|
||||
@@ -183,11 +284,11 @@ def run_multi_site_daemon():
|
||||
sx_page.get_by_role("button", name="Close").click(timeout=2000)
|
||||
sx_page.wait_for_timeout(500)
|
||||
sx_page.get_by_role("button", name="不再询问").click(timeout=2000)
|
||||
print(" ✅ 【顺心】环境准备就绪!")
|
||||
print(" ✅ 【顺心】初始弹窗处理完成。")
|
||||
except Exception:
|
||||
pass # 环境可能很干净没有弹窗,无视报错
|
||||
|
||||
# 百世环境打地鼠
|
||||
# 百世:循环关闭初始弹窗
|
||||
if "百世" in pages_map:
|
||||
try:
|
||||
bs_page = pages_map["百世"]
|
||||
@@ -215,27 +316,27 @@ def run_multi_site_daemon():
|
||||
if not handled_any:
|
||||
break
|
||||
bs_page.wait_for_timeout(800)
|
||||
print(" ✅ 【百世】界面净化完毕!")
|
||||
print(" ✅ 【百世】初始弹窗处理完成。")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "中通" in pages_map:
|
||||
print(" ✅ 【中通】状态就绪!")
|
||||
print(" ✅ 【中通】已就绪。")
|
||||
|
||||
if "韵达" in pages_map:
|
||||
print(" ✅ 【韵达】工作区状态就绪!")
|
||||
print(" ✅ 【韵达】已就绪。")
|
||||
|
||||
def is_site_ready(site_name):
|
||||
if site_name not in pages_map:
|
||||
print(f"\n🚫 【安全拦截】当前处于局部调试,[{site_name}] 未挂载加载!")
|
||||
print(f"\n🚫 站点 [{site_name}] 未加载(当前为调试模式),已跳过。")
|
||||
return False
|
||||
return True
|
||||
|
||||
while True:
|
||||
print("\n====================================================")
|
||||
print(" 物流数据多端提取总枢纽 ")
|
||||
print(" 物流数据下载主菜单 ")
|
||||
if debug_mode:
|
||||
print(f" [ 🛠️ 调试模式独立聚焦 : {debug_target} ]")
|
||||
print(f" [ 调试模式,仅加载: {debug_target} ]")
|
||||
print("====================================================")
|
||||
print(" 模块一:【顺心】数据处理流")
|
||||
print(" [1] 执行 - 应到货物数据下载")
|
||||
@@ -252,7 +353,10 @@ def run_multi_site_daemon():
|
||||
print(" [6] 执行 - 应到货物数据下载")
|
||||
print(" [7] 执行 - 实到货物数据下载")
|
||||
print("-" * 52)
|
||||
print(" 全局离线数据引擎")
|
||||
print(" 自动化测试")
|
||||
print(" [8] 执行 - 全站点下载流程自动化测试 (交叉跑通校验)")
|
||||
print("-" * 52)
|
||||
print(" 全局离线数据处理")
|
||||
print(" [9] 执行 - 异常数据清洗比对 (Left Anti-Join)")
|
||||
print("-" * 52)
|
||||
print(" [0] 退出系统")
|
||||
@@ -282,6 +386,8 @@ def run_multi_site_daemon():
|
||||
elif choice == "7" and is_site_ready("韵达"):
|
||||
pages_map["韵达"].bring_to_front()
|
||||
site_yunda.yunda_actual_download(pages_map["韵达"])
|
||||
elif choice == "8":
|
||||
run_automation_test(pages_map)
|
||||
elif choice == "9":
|
||||
site_name = input(
|
||||
"请输入要比对的网点名称 (如 顺心/中通/韵达): "
|
||||
@@ -290,16 +396,16 @@ def run_multi_site_daemon():
|
||||
site_name = "顺心"
|
||||
task_process_undelivered_data(site_name)
|
||||
elif choice == "0":
|
||||
print("\n正在释放浏览器并安全登出系统...")
|
||||
print("\n正在关闭浏览器并退出...")
|
||||
break
|
||||
else:
|
||||
if choice not in ["1", "2", "3", "4", "5", "6", "7", "9", "0"]:
|
||||
if choice not in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
|
||||
print("\n⚠️ 无效输入,请查证后回车。")
|
||||
except Exception as e:
|
||||
print(f"❌ 调度枢纽异常: {e}")
|
||||
print(f"❌ 任务调度异常: {e}")
|
||||
|
||||
browser.close()
|
||||
print("中控守护进程安全退出。")
|
||||
print("程序已退出。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user