From b453634b11783e54500b7b0b7effd579694a7850 Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Sat, 29 Aug 2026 10:51:42 +0800 Subject: [PATCH] fix(android): retry copy-link click after share rerender (#133) --- android/app/build.gradle.kts | 4 +- .../agent/automation/CurrentPageCollection.kt | 39 ++++++++++++++++--- .../goauto/agent/CurrentPageCollectionTest.kt | 25 ++++++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5f8903c..1e69498 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "cn.ilapage.goauto.agent" minSdk = 23 targetSdk = 34 - versionCode = 32 - versionName = "0.9.19" + versionCode = 33 + versionName = "0.9.20" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/CurrentPageCollection.kt b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/CurrentPageCollection.kt index d8cee1e..f7c3046 100644 --- a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/CurrentPageCollection.kt +++ b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/CurrentPageCollection.kt @@ -146,13 +146,10 @@ class CurrentPageIdentityRunner( AgentDiagnosticReason.PANEL_FOUND, elapsedMs = now() - startedAt, ) - val copy = uniqueAction(panel, config.copyLinkAliases, topOnly = false) - ?: return failure("PDD_COPY_LINK_UNAVAILABLE", "复制链接入口不唯一") val copiedAt = now() - when (driver.clickFresh(copy)) { - FreshActionResult.SUCCESS -> Unit - FreshActionResult.AMBIGUOUS -> return failure("PDD_COPY_LINK_UNAVAILABLE", "复制链接入口不唯一") - else -> return failure("PDD_COPY_LINK_UNAVAILABLE", "复制商品链接失败") + val copyResult = clickCopyLinkWithRetry(config.copyLinkAliases) + if (!copyResult.successful) { + return failure("PDD_COPY_LINK_UNAVAILABLE", copyResult.message) } val clipboard = readFreshClipboard(copiedAt, config.clipboardTimeoutMs) diagnose( @@ -354,6 +351,35 @@ class CurrentPageIdentityRunner( } return ShareClickResult(false, message) } + private fun clickCopyLinkWithRetry(aliases: List): ShareClickResult { + val deadline = now() + COPY_LINK_CLICK_RETRY_MILLIS + var lastReason = FreshClickReason.TARGET_NOT_FOUND + do { + val snapshot = driver.capture() + detailProblem(snapshot)?.let { return ShareClickResult(false, it.message) } + val candidates = actionCandidates(snapshot, aliases, topOnly = false) + if (candidates.size > 1) return ShareClickResult(false, "复制链接入口不唯一") + val target = candidates.singleOrNull() + if (target != null) { + val outcome = driver.clickFreshDetailed(target) + lastReason = outcome.reason + when (outcome.result) { + FreshActionResult.SUCCESS -> return ShareClickResult(true, "") + FreshActionResult.AMBIGUOUS -> return ShareClickResult(false, "复制链接入口不唯一") + FreshActionResult.NOT_FOUND, FreshActionResult.FAILED -> Unit + } + } + pause(POLL_MILLIS) + } while (now() <= deadline) + return ShareClickResult( + false, + when (lastReason) { + FreshClickReason.NO_CLICKABLE_ANCESTOR -> "复制链接入口暂时无法点击" + else -> "复制商品链接失败" + }, + ) + } + private fun waitForAction(aliases: List, timeoutMillis: Long): UiSnapshot? { val deadline = now() + timeoutMillis @@ -517,6 +543,7 @@ class CurrentPageIdentityRunner( const val POLL_MILLIS = 100L const val SHARE_STABLE_MILLIS = 300L const val SHARE_CLICK_RETRY_MILLIS = 1_000L + const val COPY_LINK_CLICK_RETRY_MILLIS = 1_000L const val PDD_PACKAGE = "com.xunmeng.pinduoduo" } } diff --git a/android/app/src/test/java/cn/ilapage/goauto/agent/CurrentPageCollectionTest.kt b/android/app/src/test/java/cn/ilapage/goauto/agent/CurrentPageCollectionTest.kt index 5addf06..53e2de1 100644 --- a/android/app/src/test/java/cn/ilapage/goauto/agent/CurrentPageCollectionTest.kt +++ b/android/app/src/test/java/cn/ilapage/goauto/agent/CurrentPageCollectionTest.kt @@ -294,6 +294,31 @@ class CurrentPageCollectionTest { assertTrue(events.any { it.reason == AgentDiagnosticReason.LONG_LINK_DIRECT }) } + @Test + fun retriesCopyLinkWhenSharePanelRerendersBeforeFreshClick() { + var currentTime = 0L + val driver = FakeDriver( + snapshots = List(6) { detailSnapshot() } + List(4) { sharePanelSnapshot() }, + freshOutcomes = ArrayDeque( + listOf( + FreshClickOutcome(FreshActionResult.SUCCESS, FreshClickReason.SUCCESS, candidateCount = 1), + FreshClickOutcome(FreshActionResult.NOT_FOUND, FreshClickReason.TARGET_NOT_FOUND), + FreshClickOutcome(FreshActionResult.SUCCESS, FreshClickReason.SUCCESS, candidateCount = 1), + ), + ), + ) + + val result = CurrentPageIdentityRunner( + driver = driver, + readFreshClipboard = { _, _ -> ClipboardReadOutcome("https://mobile.yangkeduo.com/goods.html?goods_id=12345") }, + now = { currentTime }, + pause = { currentTime += it }, + ).identify(rule()) + + assertTrue(result.successful) + assertEquals(listOf("分享", "复制链接", "复制链接"), driver.clicked) + } + private class FakeDriver( private val snapshots: List, private val freshOutcomes: ArrayDeque = ArrayDeque(),