fix(android): reject address editor hint values (#366)
This commit is contained in:
+2
@@ -248,6 +248,8 @@ class GoAutoAccessibilityService : AccessibilityService(), UiDriver, PddCollecto
|
||||
checked = node.isChecked,
|
||||
enabled = node.isEnabled,
|
||||
visible = node.isVisibleToUser,
|
||||
hintText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) node.hintText?.toString() else null,
|
||||
showingHintText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) node.isShowingHintText else null,
|
||||
)
|
||||
for (index in 0 until node.childCount) {
|
||||
node.getChild(index)?.let { snapshot(it, "$path/$index", path) }
|
||||
|
||||
+3
@@ -27,6 +27,9 @@ data class SnapshotNode(
|
||||
val checked: Boolean,
|
||||
val enabled: Boolean,
|
||||
val visible: Boolean,
|
||||
// In-memory accessibility metadata; not part of result or diagnostic payloads.
|
||||
val hintText: String? = null,
|
||||
val showingHintText: Boolean? = null,
|
||||
) {
|
||||
val label: String get() = text?.trim().takeUnless { it.isNullOrEmpty() }
|
||||
?: contentDescription?.trim().orEmpty()
|
||||
|
||||
+8
-3
@@ -510,7 +510,7 @@ class PurchaseLiveAutomation(
|
||||
if (modify.size != 1) fail("PURCHASE_ADDRESS_EDIT_AMBIGUOUS", "没有找到唯一的地址修改按钮,未创建订单")
|
||||
click(modify.single(), "修改地址")
|
||||
val editor = waitForInitialAddressEditor()
|
||||
val current = editor.label.trim()
|
||||
val current = editor.text.orEmpty().trim()
|
||||
val body = current.split(Regex("[-_]"), limit = 2).firstOrNull()?.trim().orEmpty()
|
||||
if (body.isBlank()) fail("PURCHASE_ADDRESS_UPDATE_FAILED", "当前详细地址无效,未创建订单")
|
||||
val expected = body + suffix
|
||||
@@ -537,6 +537,10 @@ class PurchaseLiveAutomation(
|
||||
|
||||
/** Initial readiness only; return the same sampled node for the original value and fresh input anchor. */
|
||||
private fun waitForInitialAddressEditor(): SnapshotNode {
|
||||
fun isHint(editor: SnapshotNode): Boolean {
|
||||
val hint = editor.hintText?.trim().orEmpty()
|
||||
return editor.showingHintText == true || (hint.isNotEmpty() && editor.text?.trim() == hint)
|
||||
}
|
||||
var titleSeen = false
|
||||
var structural = emptyList<SnapshotNode>()
|
||||
var readyCount = 0
|
||||
@@ -555,7 +559,7 @@ class PurchaseLiveAutomation(
|
||||
editor.bounds.left >= label.bounds.left
|
||||
}
|
||||
}.distinctBy { it.path }
|
||||
readyCount = structural.count { it.enabled && it.label.isNotBlank() }
|
||||
readyCount = structural.count { it.enabled && !it.text.isNullOrBlank() && !isHint(it) }
|
||||
if (structural.size == 1 && readyCount == 1) return structural.single()
|
||||
pause(200)
|
||||
}
|
||||
@@ -564,7 +568,8 @@ class PurchaseLiveAutomation(
|
||||
structural.isEmpty() -> "editor_missing"
|
||||
structural.size > 1 -> "editor_ambiguous"
|
||||
!structural.single().enabled -> "editor_disabled"
|
||||
else -> "editor_empty"
|
||||
structural.single().text.isNullOrBlank() -> "editor_empty"
|
||||
else -> "editor_hint"
|
||||
}
|
||||
val diagnostic = "stage=address_editor_ready;reason=$reason;titleSeen=$titleSeen;structural=${structural.size};ready=$readyCount"
|
||||
if (!titleSeen) fail("PURCHASE_ADDRESS_EDIT_TIMEOUT", "地址编辑页面打开超时,未创建订单 [$diagnostic]")
|
||||
|
||||
@@ -233,6 +233,101 @@ class PurchaseLiveAutomationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor description cannot substitute for empty text`() {
|
||||
for (text in listOf(null, "", " ")) {
|
||||
assertAddressEditorTimeout("editor_empty", 1, 0) { snapshot, _ ->
|
||||
snapshot.changeAddressEditor { it.copy(text = text, contentDescription = "合成说明占位标记") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor waits until hint is replaced by real text`() {
|
||||
assertDelayedAddressEditor { snapshot, sample ->
|
||||
snapshot.changeAddressEditor {
|
||||
it.copy(
|
||||
text = if (sample <= 4) "合成提示占位标记" else it.text,
|
||||
hintText = "合成提示占位标记",
|
||||
showingHintText = sample <= 4,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor persistent hint text fails after the full budget`() {
|
||||
assertAddressEditorTimeout("editor_hint", 1, 0) { snapshot, _ ->
|
||||
snapshot.changeAddressEditor {
|
||||
it.copy(text = "合成提示占位标记", hintText = "合成提示占位标记", showingHintText = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor rejects text equal to trimmed hint despite false or absent showing flag`() {
|
||||
for (showing in listOf(false, null)) {
|
||||
assertAddressEditorTimeout("editor_hint", 1, 0) { snapshot, _ ->
|
||||
snapshot.changeAddressEditor {
|
||||
it.copy(text = " 合成提示占位标记 ", hintText = " 合成提示占位标记 ", showingHintText = showing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor showing hint flag blocks nonmatching or missing hint text`() {
|
||||
for (hint in listOf(null, "", "合成提示占位标记")) {
|
||||
assertAddressEditorTimeout("editor_hint", 1, 0) { snapshot, _ ->
|
||||
snapshot.changeAddressEditor { it.copy(hintText = hint, showingHintText = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor empty text remains empty even when hint and description are present`() {
|
||||
assertAddressEditorTimeout("editor_empty", 1, 0) { snapshot, _ ->
|
||||
snapshot.changeAddressEditor {
|
||||
it.copy(text = " ", contentDescription = "合成说明占位标记", hintText = "合成提示占位标记", showingHintText = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor missing hint metadata remains compatible and uses actual text`() {
|
||||
val driver = LiveDriver(initialEditFrame = { snapshot, _ ->
|
||||
snapshot.changeAddressEditor {
|
||||
assertEquals(null, it.hintText)
|
||||
assertEquals(null, it.showingHintText)
|
||||
it.copy(text = " 合成真实地址-old ", contentDescription = "合成说明占位标记")
|
||||
}
|
||||
})
|
||||
|
||||
val proof = PurchaseLiveAutomation(driver, pause = {}).updateShippingAddress("_cg366")
|
||||
|
||||
assertEquals("合成真实地址_cg366", proof.expectedAddress)
|
||||
assertEquals(1, driver.initialEditCaptures)
|
||||
assertTrue(driver.lastInputTarget === driver.lastInitialEditSnapshot?.nodes?.single { it.path == "editor-address" })
|
||||
assertAddressEditorActions(driver, saved = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor accepts actual text with absent false or blank hint metadata`() {
|
||||
for (showing in listOf(null, false)) {
|
||||
for (hint in listOf(null, "", " ", "合成提示占位标记")) {
|
||||
val driver = LiveDriver(initialEditFrame = { snapshot, _ ->
|
||||
snapshot.changeAddressEditor { it.copy(hintText = hint, showingHintText = showing) }
|
||||
})
|
||||
|
||||
val proof = PurchaseLiveAutomation(driver, pause = {}).updateShippingAddress("_cg366")
|
||||
|
||||
assertTrue(proof.expectedAddress.endsWith("_cg366"))
|
||||
assertEquals(1, driver.initialEditCaptures)
|
||||
assertAddressEditorActions(driver, saved = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `address editor waits for an enabled input`() {
|
||||
assertDelayedAddressEditor { snapshot, sample ->
|
||||
@@ -436,7 +531,7 @@ class PurchaseLiveAutomationTest {
|
||||
assertEquals(50, driver.initialEditCaptures)
|
||||
assertEquals(listOf(500L) + List(50) { 200L }, editPauses)
|
||||
val emitted = error.message.orEmpty() + diagnostics.joinToString()
|
||||
listOf("广东", "骏景", "测试收货人", "13800000000", "editor-address", "_cg366").forEach {
|
||||
listOf("广东", "骏景", "测试收货人", "13800000000", "editor-address", "_cg366", "合成提示占位标记", "合成说明占位标记").forEach {
|
||||
assertFalse("Sensitive fixture value in readiness diagnostics", emitted.contains(it))
|
||||
}
|
||||
assertAddressEditorActions(driver, saved = false)
|
||||
|
||||
Reference in New Issue
Block a user