fix(android): recognize single-heading panel after safe purchase entry #260
This commit is contained in:
+10
-1
@@ -186,7 +186,7 @@ object PddScreenParser {
|
||||
// purchase/order/payment controls must never become collection click targets.
|
||||
private val nonConfigurableClickDenylist = listOf("提交订单", "确认订单", "支付", "付款")
|
||||
|
||||
fun parse(snapshot: UiSnapshot, config: PddCollectorConfig, goodsId: String, evidence: PageEvidence?, purchaseContext: PurchasePanelContext? = null, collectionContext: Boolean = false): ParsedPddScreen {
|
||||
fun parse(snapshot: UiSnapshot, config: PddCollectorConfig, goodsId: String, evidence: PageEvidence?, purchaseContext: PurchasePanelContext? = null, collectionContext: Boolean = false, purchaseEntryContext: Boolean = false): ParsedPddScreen {
|
||||
val visibleNodes = snapshot.nodes.filter { it.visible }
|
||||
val visible = visibleNodes.mapNotNull { node ->
|
||||
val descendants = descendants(node, visibleNodes)
|
||||
@@ -333,6 +333,14 @@ object PddScreenParser {
|
||||
boundedScrollables.size == 1 && headedPanelScrollable != null && headings.size == 1 &&
|
||||
dimensions.size == 1 && dimensions.single().values.isNotEmpty() &&
|
||||
hasClose && hasQuantityControls && hasPaymentArea && hasOrderSubmitAction
|
||||
// Only the executor that just activated a safe entry on this same
|
||||
// product Activity can enable this first-view purchase exception.
|
||||
val singleDimensionPurchasePanel = purchaseEntryContext && !collectionContext &&
|
||||
snapshot.packageName == PDD_PACKAGE && !snapshot.activityName.isNullOrBlank() &&
|
||||
goodsId.isNotBlank() && problem == null && !hasSelectionSummary &&
|
||||
boundedScrollables.size == 1 && headedPanelScrollable != null && headings.size == 1 &&
|
||||
dimensions.size == 1 && dimensions.single().values.isNotEmpty() &&
|
||||
hasClose && hasQuantityControls && hasPaymentArea && hasOrderSubmitAction
|
||||
// Some selected-spec panels omit both the "已选" prefix and a confirm
|
||||
// button. Keep the same structural evidence required for checkout.
|
||||
val specPanelType = when {
|
||||
@@ -340,6 +348,7 @@ object PddScreenParser {
|
||||
orderConfirmationEvidence -> SpecPanelType.ORDER_CONFIRMATION
|
||||
structuredSelectionPanel -> SpecPanelType.NORMAL_SCROLLABLE
|
||||
singleDimensionCollectionPanel -> SpecPanelType.NORMAL_SCROLLABLE
|
||||
singleDimensionPurchasePanel -> SpecPanelType.NORMAL_SCROLLABLE
|
||||
panelScrollable != null && (hasSelectionSummary || hasSubmitHint || (hasPanelTitle && hasPanelAction)) -> SpecPanelType.NORMAL_SCROLLABLE
|
||||
hasSelectionSummary && hasPanelTitle && hasPanelAction -> SpecPanelType.NON_SCROLLABLE_CONFIRMATION
|
||||
// Some PDD builds expose the complete selector as non-scrollable
|
||||
|
||||
+6
-1
@@ -96,10 +96,12 @@ class PurchaseRehearsalExecutor(
|
||||
) {
|
||||
private var purchasePanelContext: PurchasePanelContext? = null
|
||||
private var pageIdentity: Pair<String?, String?> = null to null
|
||||
private var purchaseEntryIdentity: Pair<String?, String?>? = null
|
||||
private var entryRecoveryReason = "not_checked"
|
||||
|
||||
fun execute(input: PurchaseExecutionInput, rule: PurchaseRule, supportedCapabilities: Set<String>): PurchaseExecutionOutcome {
|
||||
purchasePanelContext = null
|
||||
purchaseEntryIdentity = null
|
||||
validateBeforeDeviceAction(input, rule, supportedCapabilities)?.let { return it }
|
||||
var observedPrice: Long? = null
|
||||
var addressProof: ShippingAddressProof? = null
|
||||
@@ -380,6 +382,7 @@ class PurchaseRehearsalExecutor(
|
||||
val beforeSignature = specActionSignature(screen)
|
||||
val beforeIdentity = pageIdentity
|
||||
val click = driver.clickFreshDetailed(requireNotNull(target))
|
||||
purchaseEntryIdentity = if (click.result == FreshActionResult.SUCCESS) beforeIdentity else null
|
||||
panelDiagnostic("entrySource=${screen.specEntrySource ?: "unknown"};entryClick=${click.result};entryReason=${click.reason}")
|
||||
when (click.result) {
|
||||
// The parser already narrowed to a single semantic candidate; the
|
||||
@@ -1205,7 +1208,9 @@ class PurchaseRehearsalExecutor(
|
||||
private fun currentScreen(input: PurchaseExecutionInput): ParsedPddScreen {
|
||||
val snapshot = driver.capture()
|
||||
pageIdentity = snapshot.packageName to snapshot.activityName
|
||||
val screen = PddScreenParser.parse(snapshot, DEFAULT_COLLECTOR, input.goodsId, null, purchasePanelContext)
|
||||
val screen = PddScreenParser.parse(snapshot, DEFAULT_COLLECTOR, input.goodsId, null, purchasePanelContext,
|
||||
purchaseEntryContext = purchaseEntryIdentity != null && purchaseEntryIdentity == pageIdentity)
|
||||
if (purchaseEntryIdentity != pageIdentity) purchaseEntryIdentity = null
|
||||
purchasePanelContext = if (screen.isPddPackage && screen.problem == null && screen.specPanelOpen) {
|
||||
screen.specPanelContainer?.let {
|
||||
PurchasePanelContext(it, normalizedTarget("color", input.mappedColor).orEmpty())
|
||||
|
||||
@@ -27,6 +27,20 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class PddProductDetailCollectorTest {
|
||||
@Test fun `purchase entry can recognize one heading only with combined structure`() {
|
||||
val snapshot = singleDimensionPanel()
|
||||
assertFalse(PddScreenParser.parse(snapshot, config(), GOODS_ID, null).specPanelOpen)
|
||||
assertEquals(SpecPanelType.NORMAL_SCROLLABLE,
|
||||
PddScreenParser.parse(snapshot, config(), GOODS_ID, null, purchaseEntryContext = true).specPanelType)
|
||||
for (missing in listOf("close", "plus", "minus", "info/quantity", "payment", "submit", "scroll/color-title", "scroll/color")) {
|
||||
val changed = snapshot.copy(nodes = snapshot.nodes.filterNot { it.path == missing })
|
||||
assertFalse(missing, PddScreenParser.parse(changed, config(), GOODS_ID, null, purchaseEntryContext = true).specPanelOpen)
|
||||
}
|
||||
assertFalse(PddScreenParser.parse(snapshot.copy(packageName = "other"), config(), GOODS_ID, null, purchaseEntryContext = true).specPanelOpen)
|
||||
assertFalse(PddScreenParser.parse(snapshot.copy(activityName = null), config(), GOODS_ID, null, purchaseEntryContext = true).specPanelOpen)
|
||||
assertFalse(PddScreenParser.parse(snapshot, config(), "", null, purchaseEntryContext = true).specPanelOpen)
|
||||
}
|
||||
|
||||
private fun singleDimensionPanel(): UiSnapshot {
|
||||
val snapshot = prefixlessPanel()
|
||||
return snapshot.copy(nodes = snapshot.nodes.filterNot {
|
||||
|
||||
Reference in New Issue
Block a user