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:
Misaka
2026-06-20 23:33:22 +08:00
parent ab3f25a10e
commit 229e474b58
5 changed files with 298 additions and 191 deletions

View File

@@ -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__":