diff --git a/e2e/flows/checkout/assert-native-preload-cache-reuse.yaml b/e2e/flows/checkout/assert-native-preload-cache-reuse.yaml new file mode 100644 index 000000000..ec99b0bad --- /dev/null +++ b/e2e/flows/checkout/assert-native-preload-cache-reuse.yaml @@ -0,0 +1,13 @@ +appId: ${E2E_APP_ID} +--- +- runFlow: present.yaml +- runFlow: close.yaml + +# Preload completing is not proof that presentation reused it, so each sample republishes the +# SDK's own cache-hit diagnostic as this identifier. Swift installs a Logger and observes the +# message directly; Kotlin reads it from the app's own PID-scoped Logcat. Same assertion on +# both platforms. +- extendedWaitUntil: + visible: + id: preload-cache-hit-observed + timeout: 30000 diff --git a/e2e/flows/checkout/assert-native-preload-disabled.yaml b/e2e/flows/checkout/assert-native-preload-disabled.yaml new file mode 100644 index 000000000..18d256b12 --- /dev/null +++ b/e2e/flows/checkout/assert-native-preload-disabled.yaml @@ -0,0 +1,33 @@ +appId: ${E2E_APP_ID} +--- +# Runs after the shared readiness flow, so disabling below acts on a proven Ready preload +# instead of matching a not-ready state the app merely started in. +- tapOn: + id: settings-tab +- waitForAnimationToEnd + +- tapOn: + id: checkout-preloading-toggle + enabled: true +- assertVisible: + id: checkout-preloading-toggle + checked: false + +- tapOn: + id: cart-tab +- waitForAnimationToEnd + +- extendedWaitUntil: + visible: + id: preload-state-not-ready + timeout: 30000 + +- assertVisible: + id: preload-cache-hit-none + +- runFlow: present.yaml +- runFlow: close.yaml + +# Disabled preload presents a fresh checkout, so the cache-hit marker must stay none. +- assertVisible: + id: preload-cache-hit-none diff --git a/e2e/flows/checkout/assert-native-preload-ready.yaml b/e2e/flows/checkout/assert-native-preload-ready.yaml index d93bedced..dcbe5709a 100644 --- a/e2e/flows/checkout/assert-native-preload-ready.yaml +++ b/e2e/flows/checkout/assert-native-preload-ready.yaml @@ -5,18 +5,6 @@ appId: ${E2E_APP_ID} id: preload-state-ready timeout: 60000 -# Pins the starting value, so a marker stuck on "observed" cannot pass the assertion below. +# Pins the starting value, so a marker stuck on "observed" cannot pass later assertions. - assertVisible: id: preload-cache-hit-none - -- runFlow: present.yaml -- runFlow: close.yaml - -# Preload completing is not proof that presentation reused it, so each sample republishes the -# SDK's own cache-hit diagnostic as this identifier. Swift installs a Logger and observes the -# message directly; Kotlin reads it from the app's own PID-scoped Logcat. Same assertion on -# both platforms. -- extendedWaitUntil: - visible: - id: preload-cache-hit-observed - timeout: 30000 diff --git a/e2e/tests/shared/native-preload-disabled.yaml b/e2e/tests/shared/native-preload-disabled.yaml new file mode 100644 index 000000000..acd6ba3e0 --- /dev/null +++ b/e2e/tests/shared/native-preload-disabled.yaml @@ -0,0 +1,14 @@ +appId: ${E2E_APP_ID} +name: Native preload disabled avoids reuse +tags: + - preload + - smoke + +env: + # Preload caches a checkout per buyer, so the identity is pinned rather than left to + # whatever the sample was last configured with. + E2E_CART_PARAMS: "productIndex=0&quantity=1&buyerIdentityMode=guest" +--- +- runFlow: ../../flows/app/bootstrap-cart-from-link.yaml +- runFlow: ../../flows/checkout/assert-native-preload-ready.yaml +- runFlow: ../../flows/checkout/assert-native-preload-disabled.yaml diff --git a/e2e/tests/shared/native-preload-ready.yaml b/e2e/tests/shared/native-preload-ready.yaml index 0585d0b71..f3bda23c2 100644 --- a/e2e/tests/shared/native-preload-ready.yaml +++ b/e2e/tests/shared/native-preload-ready.yaml @@ -12,3 +12,4 @@ env: --- - runFlow: ../../flows/app/bootstrap-cart-from-link.yaml - runFlow: ../../flows/checkout/assert-native-preload-ready.yaml +- runFlow: ../../flows/checkout/assert-native-preload-cache-reuse.yaml diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiers.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiers.kt index d457b45bf..692c67b90 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiers.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiers.kt @@ -13,5 +13,10 @@ object AccessibilityIdentifiers { object Tabs { const val CART = "cart-tab" + const val SETTINGS = "settings-tab" + } + + object Settings { + const val CHECKOUT_PRELOADING_TOGGLE = "checkout-preloading-toggle" } } diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/common/navigation/BottomAppBarWithNavigation.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/common/navigation/BottomAppBarWithNavigation.kt index b0f185ac9..1b98cad4e 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/common/navigation/BottomAppBarWithNavigation.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/common/navigation/BottomAppBarWithNavigation.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.contentDescription @@ -20,6 +21,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.shopify.checkoutkit.androiddemo.R +import com.shopify.checkoutkit.androiddemo.accessibility.AccessibilityIdentifiers import com.shopify.checkoutkit.androiddemo.common.components.BodySmall @Composable @@ -54,6 +56,7 @@ fun BottomAppBarWithNavigation( ImageVector.vectorResource(R.drawable.profile), stringResource(id = R.string.navigation_log_in), currentScreen, + modifier = Modifier.testTag(AccessibilityIdentifiers.Tabs.SETTINGS), ) } } @@ -66,6 +69,7 @@ fun NavigationItem( icon: ImageVector, label: String, currentScreen: Screen, + modifier: Modifier = Modifier, ) { val isActiveScreen = currentScreen == screen val color = if (isActiveScreen) { @@ -77,7 +81,7 @@ fun NavigationItem( Column { IconButton( onClick = { navController.navigate(screen.route) }, - modifier = Modifier.semantics { + modifier = modifier.semantics { this.contentDescription = "$label icon" } ) { diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/BuyerIdentityDemoSwitch.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/BuyerIdentityDemoSwitch.kt index 64961c7a2..f2cf98f87 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/BuyerIdentityDemoSwitch.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/BuyerIdentityDemoSwitch.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import com.shopify.checkoutkit.androiddemo.common.components.BodyMedium @Composable @@ -28,6 +29,7 @@ fun SettingsSwitch( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier, + testTag: String? = null, ) { Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -37,7 +39,8 @@ fun SettingsSwitch( BodyMedium(label) Switch( checked = checked, - onCheckedChange = onCheckedChange + onCheckedChange = onCheckedChange, + modifier = if (testTag == null) Modifier else Modifier.testTag(testTag), ) } } diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/SettingsView.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/SettingsView.kt index 7713a9677..caf83facd 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/SettingsView.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/settings/SettingsView.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.shopify.checkoutkit.androiddemo.R +import com.shopify.checkoutkit.androiddemo.accessibility.AccessibilityIdentifiers import com.shopify.checkoutkit.androiddemo.common.components.BodyMedium import com.shopify.checkoutkit.androiddemo.common.components.Header2 import com.shopify.checkoutkit.androiddemo.common.components.ProgressIndicator @@ -101,7 +102,8 @@ fun SettingsView( onCheckedChange = settingsViewModel::setCheckoutPreloadingEnabled, modifier = Modifier .background(color = MaterialTheme.colorScheme.background) - .fillMaxWidth() + .fillMaxWidth(), + testTag = AccessibilityIdentifiers.Settings.CHECKOUT_PRELOADING_TOGGLE, ) } diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiersTest.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiersTest.kt index 1d650a1ce..cc9d0caaa 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiersTest.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/accessibility/AccessibilityIdentifiersTest.kt @@ -19,5 +19,12 @@ class AccessibilityIdentifiersTest { @Test fun `tab markers match the maestro flows`() { assertThat(AccessibilityIdentifiers.Tabs.CART).isEqualTo("cart-tab") + assertThat(AccessibilityIdentifiers.Tabs.SETTINGS).isEqualTo("settings-tab") + } + + @Test + fun `settings markers match the maestro flows`() { + assertThat(AccessibilityIdentifiers.Settings.CHECKOUT_PRELOADING_TOGGLE) + .isEqualTo("checkout-preloading-toggle") } } diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Accessibility/AccessibilityIdentifiers.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Accessibility/AccessibilityIdentifiers.swift index 56bd34c03..41127da66 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Accessibility/AccessibilityIdentifiers.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Accessibility/AccessibilityIdentifiers.swift @@ -11,5 +11,10 @@ enum AccessibilityIdentifiers { enum Tabs { static let cart = "cart-tab" + static let settings = "settings-tab" + } + + enum Settings { + static let checkoutPreloadingToggle = "checkout-preloading-toggle" } } diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/App/SceneDelegate.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/App/SceneDelegate.swift index e5ffbfb19..4536c5d51 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/App/SceneDelegate.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/App/SceneDelegate.swift @@ -112,6 +112,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Settings settingsController.tabBarItem.image = UIImage(systemName: "gearshape.2") settingsController.tabBarItem.title = "Settings" + settingsController.tabBarItem.accessibilityIdentifier = AccessibilityIdentifiers.Tabs.settings } private func subscribeToAuthStateChanges() { diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/SettingsView.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/SettingsView.swift index 27c7e2d3b..e9bc0889e 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/SettingsView.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/SettingsView.swift @@ -53,12 +53,22 @@ struct SettingsView: View { NavigationView { List { Section(header: Text("Features")) { - Toggle("Checkout preloading", isOn: $checkoutPreloadingEnabled) - .onChange(of: checkoutPreloadingEnabled) { _ in - ShopifyCheckoutKit.configure { - $0.preloading.enabled = checkoutPreloadingEnabled + // A whole-row Toggle exposes one full-width element whose center tap misses + // the switch, so the label sits outside and the identifier tags the control. + HStack { + Text("Checkout preloading") + Spacer() + Toggle("Checkout preloading", isOn: $checkoutPreloadingEnabled) + .labelsHidden() + .accessibilityIdentifier( + AccessibilityIdentifiers.Settings.checkoutPreloadingToggle + ) + .onChange(of: checkoutPreloadingEnabled) { _ in + ShopifyCheckoutKit.configure { + $0.preloading.enabled = checkoutPreloadingEnabled + } } - } + } Picker("Window open handler", selection: $windowOpenHandler) { ForEach(WindowOpenHandlerOption.allCases, id: \.self) { option in diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Accessibility/AccessibilityIdentifiersTests.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Accessibility/AccessibilityIdentifiersTests.swift index 6fc5b5cf4..201176972 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Accessibility/AccessibilityIdentifiersTests.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Accessibility/AccessibilityIdentifiersTests.swift @@ -14,5 +14,10 @@ class AccessibilityIdentifiersTests: XCTestCase { func testTabMarkersMatchTheMaestroFlows() { XCTAssertEqual(AccessibilityIdentifiers.Tabs.cart, "cart-tab") + XCTAssertEqual(AccessibilityIdentifiers.Tabs.settings, "settings-tab") + } + + func testSettingsMarkersMatchTheMaestroFlows() { + XCTAssertEqual(AccessibilityIdentifiers.Settings.checkoutPreloadingToggle, "checkout-preloading-toggle") } }