fix(pages): 搜索后等待结果生效 + 点行前校验目标运单,杜绝点错行

根因: 点击查询是异步请求, 旧代码不等响应就点固定列第一行——
点的是过滤前的旧列表。未过滤列表第一行可能是个 6 月的老单,
导致审核/打款打在无关运单上, 测试假通过(车队长单实际未处理)。

- _click_search(order_no): 等待第一行包含单号或出现稳定空态
  (空态需连续两次轮询确认, 规避 vxe-table 加载瞬时空块)
- _click_table_action(order_no): 点击前校验主表第一行包含目标单号
- 四个流程方法(核算/审核/财务打款/货主打款)全部穿透 order_no

验收: 假单号探测被正确拒绝; TC-D2S-001 通过且 DB 终态确认真通;
TC-D2S-002 如实失败暴露系统缺陷(核算点击未生效, 审核列表无此单)。
This commit is contained in:
xst
2026-09-02 14:32:42 +08:00
parent 2aca405267
commit 2b4b2a4050
+73 -12
View File
@@ -60,13 +60,71 @@ class FreightAccountingPage(BasePage):
input_el.clear() input_el.clear()
input_el.fill(order_no) input_el.fill(order_no)
def _click_search(self): def _click_search(self, order_no: str | None = None):
"""点击查询按钮,并等待列表进入稳定态(数据行或空态)。""" """点击查询按钮,并等待列表刷新为本次查询的结果。
传入 order_no 时等待"第一行包含该单号或出现空态",
避免点到查询响应返回前的旧表格行(未过滤列表的第一行
可能是完全无关的运单)。
"""
btn = self.page.locator("button:has-text('查询')").first btn = self.page.locator("button:has-text('查询')").first
self.wait_visible(btn) self.wait_visible(btn)
btn.click() btn.click()
if order_no:
self._wait_search_applied(order_no)
else:
self._wait_table_ready() self._wait_table_ready()
def _wait_search_applied(self, order_no: str, timeout_ms: int = 15_000):
"""等待查询结果生效: 第一行文本包含单号, 或出现稳定空态。
查询是异步请求, 响应返回前表格仍显示上一次内容——若不等待,
后续行操作会点在旧列表的行上。
空态判定要求连续两次轮询均为空(规避 vxe-table 加载中的瞬时空块),
且行匹配优先于空态判定。
"""
import time
row1 = self.page.locator(
".vxe-table--main-wrapper table > tbody > tr:nth-child(1)"
)
empty = self.page.locator(
".vxe-table--empty-block, .vxe-table--empty-placeholder-wrapper"
)
deadline = time.monotonic() + timeout_ms / 1000
last_text = ""
empty_streak = 0
while time.monotonic() < deadline:
if row1.count() > 0:
empty_streak = 0
last_text = row1.first.inner_text().replace("\n", " ")
if order_no in last_text:
return
elif empty.count() > 0:
empty_streak += 1
if empty_streak >= 2:
return
else:
empty_streak = 0
self.page.wait_for_timeout(200)
raise RuntimeError(
f"查询结果未包含单号 {order_no}(等待 {timeout_ms}ms)。"
f"当前第一行: {last_text[:120] or '无数据行'}"
)
def _verify_row1_is_order(self, order_no: str):
"""点击行操作前校验: 主表第一行必须包含目标单号(杜绝点错行)。"""
row1 = self.page.locator(
".vxe-table--main-wrapper table > tbody > tr:nth-child(1)"
)
if row1.count() == 0:
raise RuntimeError(f"列表无数据行,无法操作单号 {order_no}")
text = row1.first.inner_text().replace("\n", " ")
if order_no not in text:
raise RuntimeError(
f"列表第一行不是目标运单!期望 {order_no},实际第一行: {text[:120]}"
)
def _wait_table_ready(self, timeout_ms: int = 10_000): def _wait_table_ready(self, timeout_ms: int = 10_000):
"""等待查询结果渲染稳定: 出现可见数据行或"暂无数据"空态。 """等待查询结果渲染稳定: 出现可见数据行或"暂无数据"空态。
@@ -92,11 +150,12 @@ class FreightAccountingPage(BasePage):
except Exception: except Exception:
return False return False
def _click_table_action(self, button_index: int): def _click_table_action(self, button_index: int, order_no: str | None = None):
"""点击表格固定列操作按钮(纯图标 <i> 按钮,selector 文档路径 + fallback)。 """点击表格固定列操作按钮(纯图标 <i> 按钮,selector 文档路径 + fallback)。
vxe-table 操作列按钮结构: td > div > button:nth-child(N) > span > i vxe-table 操作列按钮结构: td > div > button:nth-child(N) > span > i
点击后显式等待操作弹窗出现。 点击后显式等待操作弹窗出现。
传入 order_no 时先校验第一行就是目标运单,杜绝点错行。
""" """
# 精确路径(selector 文档原始定位) # 精确路径(selector 文档原始定位)
sel = ( sel = (
@@ -116,6 +175,8 @@ class FreightAccountingPage(BasePage):
try: try:
self.page.wait_for_selector("table tbody tr td:visible", timeout=8_000) self.page.wait_for_selector("table tbody tr td:visible", timeout=8_000)
self.page.wait_for_timeout(300) # 行渲染后按钮绑定有小延迟, 短暂 settle self.page.wait_for_timeout(300) # 行渲染后按钮绑定有小延迟, 短暂 settle
if order_no:
self._verify_row1_is_order(order_no)
btn = self.page.locator(sel).first btn = self.page.locator(sel).first
if btn.count() == 0: if btn.count() == 0:
btn = self.page.locator(sel_fallback).first btn = self.page.locator(sel_fallback).first
@@ -182,7 +243,7 @@ class FreightAccountingPage(BasePage):
""" """
self._go("tms/Carrier/freight") self._go("tms/Carrier/freight")
self._fill_order_no(order_no) self._fill_order_no(order_no)
self._click_search() self._click_search(order_no)
# 无数据 → 执行 SQL 重试 # 无数据 → 执行 SQL 重试
if not self._table_has_data(): if not self._table_has_data():
@@ -192,13 +253,13 @@ class FreightAccountingPage(BasePage):
except Exception as e: except Exception as e:
raise RuntimeError(f"运费核算 SQL 重试失败!单号: {order_no}, 错误: {e}") raise RuntimeError(f"运费核算 SQL 重试失败!单号: {order_no}, 错误: {e}")
self._fill_order_no(order_no) self._fill_order_no(order_no)
self._click_search() self._click_search(order_no)
if not self._table_has_data(): if not self._table_has_data():
raise RuntimeError(f"运费核算查询无数据!单号: {order_no}") raise RuntimeError(f"运费核算查询无数据!单号: {order_no}")
# 点击编辑 (button_index=4, 来自 selector 文档 Step 3) # 点击编辑 (button_index=4, 来自 selector 文档 Step 3)
self._click_table_action(button_index=4) self._click_table_action(button_index=4, order_no=order_no)
# ── 车队长全额收款模式:核算前选择车队长 + 收款模式 ── # ── 车队长全额收款模式:核算前选择车队长 + 收款模式 ──
if team_leader_payment: if team_leader_payment:
@@ -280,10 +341,10 @@ class FreightAccountingPage(BasePage):
"""运单审核:导航→搜索→审核→滚动弹窗→审核通过。""" """运单审核:导航→搜索→审核→滚动弹窗→审核通过。"""
self._go("tms/Carrier/OrderDetailAudit") self._go("tms/Carrier/OrderDetailAudit")
self._fill_order_no(order_no) self._fill_order_no(order_no)
self._click_search() self._click_search(order_no)
# 点击审核 (button_index=2, selector 文档 Step 10) # 点击审核 (button_index=2, selector 文档 Step 10)
self._click_table_action(button_index=2) self._click_table_action(button_index=2, order_no=order_no)
# 滚动弹窗 (selector 文档 Step 11) # 滚动弹窗 (selector 文档 Step 11)
self._scroll_modal() self._scroll_modal()
@@ -303,10 +364,10 @@ class FreightAccountingPage(BasePage):
"""垫资-财务打款:导航→搜索→在线支付→滚动→勾选光大/手机→验证码→确认。""" """垫资-财务打款:导航→搜索→在线支付→滚动→勾选光大/手机→验证码→确认。"""
self._go("tms/Carrier/dzFinancialapproval") self._go("tms/Carrier/dzFinancialapproval")
self._fill_order_no(order_no) self._fill_order_no(order_no)
self._click_search() self._click_search(order_no)
# 在线支付 (button_index=2, selector 文档 Step 18) # 在线支付 (button_index=2, selector 文档 Step 18)
self._click_table_action(button_index=2) self._click_table_action(button_index=2, order_no=order_no)
self._scroll_modal() self._scroll_modal()
# 勾选光大银行 (selector 文档 Step 20) # 勾选光大银行 (selector 文档 Step 20)
@@ -361,10 +422,10 @@ class FreightAccountingPage(BasePage):
"""垫资-货主打款:导航→搜索→打款→勾选光大→确认打款→确认弹窗。""" """垫资-货主打款:导航→搜索→打款→勾选光大→确认打款→确认弹窗。"""
self._go("tms/Carrier/dzApplicationForPayment") self._go("tms/Carrier/dzApplicationForPayment")
self._fill_order_no(order_no) self._fill_order_no(order_no)
self._click_search() self._click_search(order_no)
# 打款 (button_index=2, selector 文档 Step 30) # 打款 (button_index=2, selector 文档 Step 30)
self._click_table_action(button_index=2) self._click_table_action(button_index=2, order_no=order_no)
# 勾选光大 (selector 文档 Step 31) # 勾选光大 (selector 文档 Step 31)
def _click_guangda_label(): def _click_guangda_label():