diff --git a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt index 9133065..b55e9ea 100644 --- a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt +++ b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt @@ -162,24 +162,28 @@ class PurchaseLiveAutomation( return unknown("PURCHASE_ORDER_UNEXPECTED_APP", "核单期间出现未授权应用") } if (restoredFromWechat) pddObservedAfterWechatRestore = true - val paymentVisible = isKnownPddPaymentActivity(snapshot) || - currentLabels.any { label -> PAYMENT_MARKERS.any(label::contains) } + val paymentVisible = isKnownPddPaymentActivity(snapshot) val orderContextVisible = currentLabels.any { label -> ORDER_CONTEXT_MARKERS.any(label::contains) } - if (!paymentVisible && !orderContextVisible) { + val unpaidContextVisible = currentLabels.any { label -> UNPAID_MARKERS.any(label::contains) } + if (paymentVisible) { + if (!backedOutOfPayment) { + backedOutOfPayment = true + if (!driver.backPurchase()) { + return unknown("PURCHASE_ORDER_PAYMENT_BACK_FAILED", "支付页无法安全返回订单详情") + } + pause(500) + } else { + return unknown("PURCHASE_ORDER_PAYMENT_REPEATED", "支付页重复出现,已停止自动核单") + } + return@repeat + } + if (!orderContextVisible && !unpaidContextVisible) { pause(ORDER_RESULT_SAMPLE_INTERVAL_MS) return@repeat } currentLabels.forEach(labels::add) parseOrderEvidence(labels)?.let { return it } - if (paymentVisible && !backedOutOfPayment) { - backedOutOfPayment = true - if (!driver.backPurchase()) { - return unknown("PURCHASE_ORDER_PAYMENT_BACK_FAILED", "支付页无法安全返回订单详情") - } - pause(500) - } else if (paymentVisible) { - return unknown("PURCHASE_ORDER_PAYMENT_REPEATED", "支付页重复出现,已停止自动核单") - } else if (index > 0 && index % 15 == 0) { + if (index > 0 && index % ORDER_RESULT_SCROLL_SAMPLE_INTERVAL == 0) { driver.swipePurchase(SwipeDirection.UP, 400) } pause(ORDER_RESULT_SAMPLE_INTERVAL_MS) @@ -435,6 +439,7 @@ class PurchaseLiveAutomation( const val ORDER_RESULT_MAX_SAMPLES = 60 const val ORDER_RESULT_MAX_EMPTY_SAMPLES = 15 const val ORDER_RESULT_WECHAT_RESTORE_MAX_SAMPLES = 15 + const val ORDER_RESULT_SCROLL_SAMPLE_INTERVAL = 15 const val ORDER_RESULT_SAMPLE_INTERVAL_MS = 200L } } diff --git a/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt b/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt index fd002e8..1f21c1a 100644 --- a/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt +++ b/android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt @@ -120,6 +120,37 @@ class PurchaseLiveAutomationTest { assertFalse(driver.clicked.any { it.startsWith("微信") || it.contains("支付") }) } + @Test + fun `payment page returns to folded unpaid order detail and scrolls to read evidence`() { + val driver = LiveDriver(chooserAfterSubmit = true, orderEvidenceBelowFold = true) + val automation = PurchaseLiveAutomation(driver, pause = {}) + val address = automation.updateShippingAddress("_cg54") + automation.finalConfirmation(input().copy(addressSuffix = "_cg54"), address) + automation.submitOrderOnce() + + val order = automation.readOrderResult() + + assertEquals("PDD-202608210001", order?.orderNo) + assertEquals(2, driver.postSubmitBackCount) + assertEquals(1, driver.genericSwipes) + assertFalse(driver.clicked.any { it.contains("支付") }) + } + + @Test + fun `repeated known payment activity still stops without scrolling or payment clicks`() { + val driver = LiveDriver(postSubmitCaptureSequence = listOf("payment", "payment")) + val automation = PurchaseLiveAutomation(driver, pause = {}) + val address = automation.updateShippingAddress("_cg55") + automation.finalConfirmation(input().copy(addressSuffix = "_cg55"), address) + automation.submitOrderOnce() + + assertEquals(null, automation.readOrderResult()) + assertEquals("PURCHASE_ORDER_PAYMENT_REPEATED", automation.lastOrderReadFailure?.code) + assertEquals(1, driver.postSubmitBackCount) + assertEquals(0, driver.genericSwipes) + assertFalse(driver.clicked.any { it.contains("支付") }) + } + @Test fun `WeChat login is not touched and PDD is restored once before reading order detail`() { val driver = LiveDriver(wechatLoginAfterSubmit = true) @@ -310,6 +341,7 @@ class PurchaseLiveAutomationTest { private val savedTransitionHidesSuffix: Boolean = false, private val wechatLoginAfterSubmit: Boolean = false, private val wechatRestoreStuck: Boolean = false, + private val orderEvidenceBelowFold: Boolean = false, postSubmitCaptureSequence: List = emptyList(), ) : PurchaseUiDriver { private var page = "confirmation" @@ -359,6 +391,7 @@ class PurchaseLiveAutomationTest { node("address-summary", if (savedTransitionHidesSuffix) "已保存的收货信息" else address.substring(address.lastIndexOf("_cg"))), )) "order" -> snapshot(listOf(node("status", "待付款"), node("order", "订单号:PDD-202608210001"), node("time", "下单时间:2026-08-21 10:30:00"), node("pay", "立即支付", clickable = true))) + "order-folded" -> snapshot(listOf(node("status", "待付款"), node("pay", "立即支付", clickable = true))) "order-no-time" -> snapshot(listOf(node("status", "待付款"), node("order", "订单号:PDD-202608210001"), node("pay", "去支付", clickable = true))) "chooser" -> UiSnapshot(if (trustedChooser) "android" else "example.untrusted", "com.android.internal.app.ChooserActivity", listOf( node("chooser-title", "选择要使用的应用"), node("wechat-1", "微信"), node("wechat-2", "微信分身"), @@ -428,7 +461,11 @@ class PurchaseLiveAutomationTest { address = value return FreshActionResult.SUCCESS } - override fun swipePurchase(direction: SwipeDirection, durationMs: Long): Boolean { genericSwipes++; return true } + override fun swipePurchase(direction: SwipeDirection, durationMs: Long): Boolean { + genericSwipes++ + if (page == "order-folded" && direction == SwipeDirection.UP) page = "order" + return true + } override fun swipePurchaseIn(target: SnapshotNode, direction: SwipeDirection, durationMs: Long): Boolean { scopedSwipes++ if (target.path == "panel" && direction == SwipeDirection.DOWN) addressVisible = true @@ -439,7 +476,7 @@ class PurchaseLiveAutomationTest { if (page == "chooser" || page == "payment") postSubmitBackCount++ page = when (page) { "chooser" -> "payment" - "payment" -> "order" + "payment" -> if (orderEvidenceBelowFold) "order-folded" else "order" else -> "confirmation" } return true