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:
@@ -60,12 +60,70 @@ 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()
|
||||||
self._wait_table_ready()
|
if order_no:
|
||||||
|
self._wait_search_applied(order_no)
|
||||||
|
else:
|
||||||
|
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():
|
||||||
|
|||||||
Reference in New Issue
Block a user