From 2b4b2a4050026676f9d8ce06ce87b6fc21d93489 Mon Sep 17 00:00:00 2001 From: xst Date: Wed, 2 Sep 2026 14:32:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(pages):=20=E6=90=9C=E7=B4=A2=E5=90=8E?= =?UTF-8?q?=E7=AD=89=E5=BE=85=E7=BB=93=E6=9E=9C=E7=94=9F=E6=95=88=20+=20?= =?UTF-8?q?=E7=82=B9=E8=A1=8C=E5=89=8D=E6=A0=A1=E9=AA=8C=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E8=BF=90=E5=8D=95=EF=BC=8C=E6=9D=9C=E7=BB=9D=E7=82=B9=E9=94=99?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: 点击查询是异步请求, 旧代码不等响应就点固定列第一行—— 点的是过滤前的旧列表。未过滤列表第一行可能是个 6 月的老单, 导致审核/打款打在无关运单上, 测试假通过(车队长单实际未处理)。 - _click_search(order_no): 等待第一行包含单号或出现稳定空态 (空态需连续两次轮询确认, 规避 vxe-table 加载瞬时空块) - _click_table_action(order_no): 点击前校验主表第一行包含目标单号 - 四个流程方法(核算/审核/财务打款/货主打款)全部穿透 order_no 验收: 假单号探测被正确拒绝; TC-D2S-001 通过且 DB 终态确认真通; TC-D2S-002 如实失败暴露系统缺陷(核算点击未生效, 审核列表无此单)。 --- tests/pages/admin/freight_accounting_page.py | 87 +++++++++++++++++--- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/tests/pages/admin/freight_accounting_page.py b/tests/pages/admin/freight_accounting_page.py index d6dd5e3..4078d2e 100644 --- a/tests/pages/admin/freight_accounting_page.py +++ b/tests/pages/admin/freight_accounting_page.py @@ -60,12 +60,70 @@ class FreightAccountingPage(BasePage): input_el.clear() 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 self.wait_visible(btn) 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): """等待查询结果渲染稳定: 出现可见数据行或"暂无数据"空态。 @@ -92,11 +150,12 @@ class FreightAccountingPage(BasePage): except Exception: return False - def _click_table_action(self, button_index: int): + def _click_table_action(self, button_index: int, order_no: str | None = None): """点击表格固定列操作按钮(纯图标 按钮,selector 文档路径 + fallback)。 vxe-table 操作列按钮结构: td > div > button:nth-child(N) > span > i 点击后显式等待操作弹窗出现。 + 传入 order_no 时先校验第一行就是目标运单,杜绝点错行。 """ # 精确路径(selector 文档原始定位) sel = ( @@ -116,6 +175,8 @@ class FreightAccountingPage(BasePage): try: self.page.wait_for_selector("table tbody tr td:visible", timeout=8_000) self.page.wait_for_timeout(300) # 行渲染后按钮绑定有小延迟, 短暂 settle + if order_no: + self._verify_row1_is_order(order_no) btn = self.page.locator(sel).first if btn.count() == 0: btn = self.page.locator(sel_fallback).first @@ -182,7 +243,7 @@ class FreightAccountingPage(BasePage): """ self._go("tms/Carrier/freight") self._fill_order_no(order_no) - self._click_search() + self._click_search(order_no) # 无数据 → 执行 SQL 重试 if not self._table_has_data(): @@ -192,13 +253,13 @@ class FreightAccountingPage(BasePage): except Exception as e: raise RuntimeError(f"运费核算 SQL 重试失败!单号: {order_no}, 错误: {e}") self._fill_order_no(order_no) - self._click_search() + self._click_search(order_no) if not self._table_has_data(): raise RuntimeError(f"运费核算查询无数据!单号: {order_no}") # 点击编辑 (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: @@ -280,10 +341,10 @@ class FreightAccountingPage(BasePage): """运单审核:导航→搜索→审核→滚动弹窗→审核通过。""" self._go("tms/Carrier/OrderDetailAudit") self._fill_order_no(order_no) - self._click_search() + self._click_search(order_no) # 点击审核 (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) self._scroll_modal() @@ -303,10 +364,10 @@ class FreightAccountingPage(BasePage): """垫资-财务打款:导航→搜索→在线支付→滚动→勾选光大/手机→验证码→确认。""" self._go("tms/Carrier/dzFinancialapproval") self._fill_order_no(order_no) - self._click_search() + self._click_search(order_no) # 在线支付 (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() # 勾选光大银行 (selector 文档 Step 20) @@ -361,10 +422,10 @@ class FreightAccountingPage(BasePage): """垫资-货主打款:导航→搜索→打款→勾选光大→确认打款→确认弹窗。""" self._go("tms/Carrier/dzApplicationForPayment") self._fill_order_no(order_no) - self._click_search() + self._click_search(order_no) # 打款 (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) def _click_guangda_label():