From 8b8591afdc1810985d4e734eecd2113e469775aa Mon Sep 17 00:00:00 2001 From: currycan Date: Sat, 11 Jul 2026 15:43:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20probe=20=E6=A8=A1=E5=BC=8F=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=8A=8A=E9=A2=84=E5=85=88=E5=AD=98=E5=9C=A8=E7=9A=84?= =?UTF-8?q?=E6=9C=AA=E6=94=AF=E4=BB=98=E8=AE=A2=E5=8D=95=E8=AF=AF=E6=8A=A5?= =?UTF-8?q?=E4=B8=BA=E3=80=8C=E6=8A=A2=E7=A5=A8=E6=88=90=E5=8A=9F=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机测试(设备 c6c4eb67,--probe 安全探测)捕获:大麦启动瞬时弹出账号里 一个预先存在的未支付订单弹窗,probe_only 模式却输出「快速重试成功:抢票 成功:检测到未支付订单」,outcome=order_pending_payment。 安全探测从不提交任何订单,该弹窗必然是账号预先存在的占单(非本次探测 产生);报「抢票成功」会让安全探测用户误以为 probe 下了单——与圆桌评审 U-10「禁止虚报成功」同源的信任缺陷。 修复:新增 _report_pending_order_dialog() 按模式据实分流—— - probe_only → 新 outcome preexisting_pending_order +「探测提醒:账号存在 未支付订单(非本次安全探测产生)」,不称成功; - 正式/开发模式 → 保持 order_pending_payment 原语义(本轮流程占单)。 三处 pending_order 上报点(orchestrator 主路径 ×2 + recovery 快速重试 ×1) 统一走此 helper,返回 True/exit 0 语义不变。 测试:全量 1376 passed / 1 xfailed,覆盖率 84.11%;+2 单测(probe 模式 outcome 断言 + 成功日志映射不含「抢票成功」)。旧行为已由真机日志实证。 Co-Authored-By: Claude Fable 5 --- mobile/damai_app/orchestrator.py | 36 ++++++++++++++++------- mobile/damai_app/recovery_strategies.py | 6 +--- tests/unit/test_mobile_damai_app.py | 39 +++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/mobile/damai_app/orchestrator.py b/mobile/damai_app/orchestrator.py index 50be82b..95657f4 100755 --- a/mobile/damai_app/orchestrator.py +++ b/mobile/damai_app/orchestrator.py @@ -192,6 +192,29 @@ def _set_run_outcome(self, outcome): """Record the terminal outcome for the latest run attempt.""" self._last_run_outcome = outcome + def _report_pending_order_dialog(self): + """检测到「未支付订单弹窗」时的统一上报(真话优先,U-10 同源)。 + + probe_only 从不提交任何订单,此弹窗必然是账号里预先存在的占单(非本次 + 安全探测产生)。旧实现无论何种模式都记 ``order_pending_payment`` → + 「抢票成功:检测到未支付订单」,会误导安全探测用户以为 probe 下了单 + (2026-07-11 真机实测)。据实分流,返回 True 语义不变: + - probe_only:``preexisting_pending_order`` —— 只做探测提醒,不称成功; + - 正式/开发模式:``order_pending_payment`` —— 本轮流程占单,沿用原语义。 + """ + if self.config.probe_only: + self._set_run_outcome("preexisting_pending_order") + logger.info( + "探测提醒:账号存在未支付订单(非本次安全探测产生)," + "如需可自行前往订单页处理;本次探测未做任何提交" + ) + else: + self._set_run_outcome("order_pending_payment") + logger.info( + "检测到未支付订单弹窗(已占单待支付),请立即前往订单页完成支付" + ) + return True + def _execution_mode_key(self): """Return the current execution mode key.""" if self.config.probe_only: @@ -232,6 +255,7 @@ def _log_success_outcome(self, retry_prefix=""): "validation_ready": "开发验证成功:已到订单确认页,未提交订单", "order_submitted": "抢票成功:已提交订单", "order_pending_payment": "抢票成功:检测到未支付订单,请立即前往支付完成下单", + "preexisting_pending_order": "探测提醒:账号存在未支付订单(非本次探测产生),本次未做任何提交", "order_flow_completed": "抢票流程完成:已执行提交,等待后续结果确认", } logger.info( @@ -760,11 +784,7 @@ def run_ticket_grabbing(self, initial_page_probe=None): return False if page_probe["state"] == "pending_order_dialog": - self._set_run_outcome("order_pending_payment") - logger.info( - "检测到未支付订单弹窗(已占单待支付),请立即前往订单页完成支付" - ) - return True + return self._report_pending_order_dialog() if page_probe["state"] not in { "detail_page", @@ -779,11 +799,7 @@ def run_ticket_grabbing(self, initial_page_probe=None): return False page_probe = self.probe_current_page() if page_probe["state"] == "pending_order_dialog": - self._set_run_outcome("order_pending_payment") - logger.info( - "检测到未支付订单弹窗(已占单待支付),请立即前往订单页完成支付" - ) - return True + return self._report_pending_order_dialog() else: logger.warning("当前不在演出详情页,请先手动打开目标演出详情页") return False diff --git a/mobile/damai_app/recovery_strategies.py b/mobile/damai_app/recovery_strategies.py index 1b1372c..5bb918d 100644 --- a/mobile/damai_app/recovery_strategies.py +++ b/mobile/damai_app/recovery_strategies.py @@ -351,11 +351,7 @@ def _fast_retry_from_current_state(self): result = self._submit_order_fast(submit_selectors) return self._finalize_submit_result(result) elif state == "pending_order_dialog": - self._set_run_outcome("order_pending_payment") - logger.info( - "检测到未支付订单弹窗(已占单待支付),请立即前往订单页完成支付" - ) - return True + return self._report_pending_order_dialog() else: if self.config.auto_navigate: return ( diff --git a/tests/unit/test_mobile_damai_app.py b/tests/unit/test_mobile_damai_app.py index 85a1f7c..f7e5771 100644 --- a/tests/unit/test_mobile_damai_app.py +++ b/tests/unit/test_mobile_damai_app.py @@ -1588,6 +1588,45 @@ def test_run_ticket_grabbing_returns_success_when_pending_order_dialog_detected_ assert result is True assert bot._last_run_outcome == "order_pending_payment" + def test_run_ticket_grabbing_probe_mode_pending_order_is_not_grab_success(self, bot): + # 2026-07-11 真机回归:probe_only 从不提交,遇到预先存在的未支付订单弹窗 + # 必须据实上报(preexisting_pending_order),绝不能记「抢票成功」——否则 + # 安全探测用户会误以为 probe 下了单(信任问题,U-10 同源)。 + bot.config.probe_only = True + bot.config.if_commit_order = False + with patch.object(bot, "dismiss_startup_popups"): + with patch.object(bot, "check_session_valid", return_value=True): + with patch.object( + bot, + "probe_current_page", + return_value={ + "state": "pending_order_dialog", + "purchase_button": False, + "price_container": False, + "quantity_picker": False, + "submit_button": False, + "reservation_mode": False, + "pending_order_dialog": True, + }, + ): + result = bot.run_ticket_grabbing() + + assert result is True + assert bot._last_run_outcome == "preexisting_pending_order" + + def test_log_success_outcome_preexisting_pending_order_not_grab_success( + self, bot, caplog + ): + # 成功日志映射也不得对该 outcome 说「抢票成功」 + import logging as _logging + + bot._set_run_outcome("preexisting_pending_order") + with caplog.at_level(_logging.INFO): + bot._log_success_outcome("快速重试成功:") + joined = " ".join(r.message for r in caplog.records) + assert "抢票成功" not in joined + assert "未支付订单" in joined + def test_run_ticket_grabbing_rush_mode_skips_detail_prepare_and_reprobe_when_no_sell_time( self, bot ):