From c45785fe4b1e39ae7d50a4fc5bc93a2f4e3c8ad7 Mon Sep 17 00:00:00 2001 From: saranyanataraja Date: Mon, 16 Mar 2026 15:56:32 +0100 Subject: [PATCH] feat(health-sdk): Add smoke UI test case for Health SDK and Bank app integration - from invoices list HEAL-143 --- gradle/libs.versions.toml | 1 + health-sdk/example-app/build.gradle.kts | 24 +++ .../sdk/exampleapp/test/pages/BankScreen.kt | 146 ++++++++++++++ .../exampleapp/test/pages/InvoicesScreen.kt | 125 ++++++++++++ .../sdk/exampleapp/test/pages/MainScreen.kt | 24 +++ .../test/pages/PaymentReviewScreen.kt | 178 ++++++++++++++++++ .../sdk/exampleapp/test/testdata/TestData.kt | 51 +++++ .../exampleapp/test/uitest/SmokeTestHealth.kt | 58 ++++++ 8 files changed, 607 insertions(+) create mode 100644 health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/BankScreen.kt create mode 100644 health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/InvoicesScreen.kt create mode 100644 health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/MainScreen.kt create mode 100644 health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/PaymentReviewScreen.kt create mode 100644 health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/testdata/TestData.kt create mode 100644 health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/uitest/SmokeTestHealth.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 44a7c631a4..0160e35702 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -112,6 +112,7 @@ androidx-test-orchestrator = { module = "androidx.test:orchestrator", version.re androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" } androidx-test-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-junit" } androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } +androidx-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espresso" } androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" } androidx-test-espresso-idlingresource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" } androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" } diff --git a/health-sdk/example-app/build.gradle.kts b/health-sdk/example-app/build.gradle.kts index bac0224313..f7d3194900 100644 --- a/health-sdk/example-app/build.gradle.kts +++ b/health-sdk/example-app/build.gradle.kts @@ -124,10 +124,34 @@ dependencies { androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.androidx.test.espresso.core) + androidTestImplementation(libs.androidx.test.espresso.contrib) + androidTestImplementation(libs.androidx.test.uiautomator) } apply() +/** + * Installs the bank-sdk example app (devPaymentProvider3Debug variant) on the connected + * device/emulator before any connectedAndroidTest task runs. + * + * The bank app package (net.gini.android.bank.insurance.mock) must be present on the device + * so the Health SDK can resolve the payment intent and launch the Bank app during UI tests. + * + * Run manually: ./gradlew :health-sdk:example-app:installBankApp + * Runs automatically before: connectedDevDebugAndroidTest (and all other connected test variants) + */ +val installBankApp by tasks.registering { + group = "verification" + description = "Installs bank-sdk example app (devPaymentProvider3Debug) before UI tests" + dependsOn(":bank-sdk:example-app:installDevPaymentProvider3Debug") +} + +tasks.configureEach { + if (name.startsWith("connected") && name.endsWith("AndroidTest")) { + dependsOn(installBankApp) + } +} + tasks.register("injectClientCredentials") { val propertiesMap = mutableMapOf() diff --git a/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/BankScreen.kt b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/BankScreen.kt new file mode 100644 index 0000000000..1f91371723 --- /dev/null +++ b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/BankScreen.kt @@ -0,0 +1,146 @@ +package net.gini.android.health.sdk.exampleapp.test.pages + +import android.content.Intent +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import net.gini.android.health.sdk.exampleapp.test.testdata.TestData +import org.junit.Assert.assertEquals + +/** + * Page Object Model for the bank-sdk example app payment screen. + * + * Hybrid Espresso + UiAutomator approach: + * 1. Espresso drives the Health app up to and including tapping "Pay" + * 2. [switchToBankApp] switches context via launchIntentForPackage + * (bank app is pre-installed by Gradle before the tests run) + * 3. UiAutomator verifies Bank app fields and taps Pay + * 4. [tapPayAndReturnToBusiness] waits for the deep-link return to Health app + * 5. Espresso regains control of the Health app + */ +class BankScreen { + + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private val device: UiDevice = UiDevice.getInstance(instrumentation) + + companion object { + private const val LAUNCH_TIMEOUT_MS = 5_000L + private const val FIELD_TIMEOUT_MS = 3_000L + } + + /** + * Switches context from the Health app to the Bank app. + * + * The Health SDK already fires the payment intent when "Pay" is tapped, + * so the Bank app may already be in the foreground. If not, we explicitly + * launch it via [android.content.pm.PackageManager.getLaunchIntentForPackage]. + */ + fun switchToBankApp(): BankScreen { + + val bankAppVisible = device.wait( + Until.hasObject(By.pkg(TestData.AppPackages.BANK_SDK_EXAMPLE_APP).depth(0)), + LAUNCH_TIMEOUT_MS + ) + + if (!bankAppVisible) { + // Fallback: explicitly launch the bank app + val launchIntent = instrumentation.targetContext.packageManager + .getLaunchIntentForPackage(TestData.AppPackages.BANK_SDK_EXAMPLE_APP) + ?.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) } + + requireNotNull(launchIntent) { + "Bank app not installed: ${TestData.AppPackages.BANK_SDK_EXAMPLE_APP}" + } + + instrumentation.targetContext.startActivity(launchIntent) + + device.wait( + Until.hasObject(By.pkg(TestData.AppPackages.BANK_SDK_EXAMPLE_APP).depth(0)), + LAUNCH_TIMEOUT_MS + ) + } + + return this + } + + /** + * Uses UiAutomator to verify all 4 extracted payment fields in the Bank app. + * Must be called after [switchToBankApp]. + */ + fun verifyPaymentDetails( + expectedRecipient: String, + expectedIban: String, + expectedAmount: String, + expectedPurpose: String + ): BankScreen { + // Verify Recipient + val recipientField = device.wait( + Until.findObject(By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "recipient")), + FIELD_TIMEOUT_MS + ) + val actualRecipient = recipientField?.text ?: "" + println("✅ Bank App Recipient: $actualRecipient") + assertEquals("Recipient mismatch in bank app", expectedRecipient, actualRecipient) + + // Verify IBAN + val actualIban = device + .findObject(By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "iban")) + ?.text ?: "" + println("✅ Bank App IBAN: $actualIban") + assertEquals("IBAN mismatch in bank app", expectedIban, actualIban) + + // Verify Amount + val actualAmount = device + .findObject(By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "amount")) + ?.text ?: "" + println("✅ Bank App Amount: $actualAmount") + assertEquals("Amount mismatch in bank app", expectedAmount, actualAmount) + + // Verify Purpose + val actualPurpose = device + .findObject(By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "purpose")) + ?.text ?: "" + println("✅ Bank App Purpose: $actualPurpose") + if (expectedPurpose.isNotEmpty()) { + assertEquals("Purpose mismatch in bank app", expectedPurpose, actualPurpose) + } + + return this + } + + /** + * Taps the Pay (resolve_payment) button in the Bank app, + * waits for the "Return to Business" button to confirm payment resolved, + * taps it to trigger the deep-link back to the Health app, + * then waits for the Health app to return to the foreground. + * After this call, Espresso regains control of the Health app. + */ + fun tapPayAndReturnToBusiness(): BankScreen { + // Tap resolve_payment (UiAutomator — cross-app) + device.findObject( + By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "resolve_payment") + )?.click() + + // Wait for "Return to Business" button — confirms payment was processed + device.wait( + Until.findObject( + By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "return_to_payment_initiator_app") + ), + FIELD_TIMEOUT_MS + ) + + // Tap it — triggers deep-link back to Health app + device.findObject( + By.res(TestData.AppPackages.BANK_SDK_EXAMPLE_APP, "return_to_payment_initiator_app") + )?.click() + + // Wait for the Health app to return to the foreground — Espresso takes over from here + device.wait( + Until.hasObject(By.pkg(TestData.AppPackages.HEALTH_SDK_EXAMPLE_APP).depth(0)), + LAUNCH_TIMEOUT_MS + ) + + return this + } +} diff --git a/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/InvoicesScreen.kt b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/InvoicesScreen.kt new file mode 100644 index 0000000000..1471f8dbe1 --- /dev/null +++ b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/InvoicesScreen.kt @@ -0,0 +1,125 @@ +package net.gini.android.health.sdk.exampleapp.test.pages + +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu +import androidx.test.espresso.PerformException +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.hasMinimumChildCount +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.platform.app.InstrumentationRegistry +import net.gini.android.health.sdk.exampleapp.R +import org.hamcrest.Matcher + +/** + * Page Object Model for InvoicesActivity. + * Contains all UI interactions and assertions for the invoices list screen. + */ +class InvoicesScreen { + + /** + * Opens the overflow menu, taps "Upload Invoices" and waits for + * the invoices list to fully populate. + */ + fun loadInvoices(): InvoicesScreen { + // Open the overflow (ellipse) menu in the toolbar + openActionBarOverflowOrOptionsMenu( + InstrumentationRegistry.getInstrumentation().targetContext + ) + + // Tap "Upload Invoices" from the overflow menu + onView(withText("Upload Invoices")) + .perform(click()) + + // Wait for invoices to be uploaded and the list to populate + Thread.sleep(10000) + + // Verify the RecyclerView is displayed with at least one invoice item + onView(withId(R.id.invoices_list)) + .check(matches(isDisplayed())) + .check(matches(hasMinimumChildCount(1))) + + return this + } + + /** + * Scrolls through the invoices list to find the item matching [recipientName] + * and taps its pay button. + */ + fun tapInvoiceByRecipient(recipientName: String): InvoicesScreen { + onView(withId(R.id.invoices_list)).perform(object : ViewAction { + override fun getConstraints(): Matcher = isDisplayed() + override fun getDescription() = "scroll to and click pay_invoice_button for recipient: $recipientName" + override fun perform(uiController: UiController, view: View) { + val recyclerView = view as RecyclerView + val adapter = recyclerView.adapter + ?: throw PerformException.Builder() + .withActionDescription(description) + .withViewDescription("RecyclerView has no adapter") + .build() + + // Find the adapter position of the item matching recipientName + var targetPosition = -1 + for (pos in 0 until adapter.itemCount) { + recyclerView.post { + (recyclerView.layoutManager as? LinearLayoutManager) + ?.scrollToPositionWithOffset(pos, 0) + } + uiController.loopMainThreadUntilIdle() + Thread.sleep(100) + uiController.loopMainThreadUntilIdle() + + val vh = recyclerView.findViewHolderForAdapterPosition(pos) ?: continue + val recipientView = vh.itemView + .findViewById(R.id.recipient) + if (recipientView?.text?.toString() == recipientName) { + targetPosition = pos + break + } + } + + if (targetPosition == -1) { + throw PerformException.Builder() + .withActionDescription(description) + .withViewDescription( + "No item with recipient '$recipientName' " + + "found in adapter (${adapter.itemCount} items)" + ) + .build() + } + + // Scroll to the found position and wait for it to settle + recyclerView.post { + (recyclerView.layoutManager as? LinearLayoutManager) + ?.scrollToPositionWithOffset(targetPosition, 0) + } + uiController.loopMainThreadUntilIdle() + Thread.sleep(300) + uiController.loopMainThreadUntilIdle() + + // Tap the pay button inside the matched item + val targetVh = recyclerView.findViewHolderForAdapterPosition(targetPosition) + ?: throw PerformException.Builder() + .withActionDescription(description) + .withViewDescription( + "ViewHolder at position $targetPosition not found after scroll" + ) + .build() + + val payBtn = targetVh.itemView.findViewById(R.id.pay_invoice_button) + payBtn.callOnClick() + uiController.loopMainThreadUntilIdle() + Thread.sleep(500) + uiController.loopMainThreadUntilIdle() + } + }) + return this + } +} diff --git a/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/MainScreen.kt b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/MainScreen.kt new file mode 100644 index 0000000000..43a9ebee15 --- /dev/null +++ b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/MainScreen.kt @@ -0,0 +1,24 @@ +package net.gini.android.health.sdk.exampleapp.test.pages + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.matcher.ViewMatchers.withId +import net.gini.android.health.sdk.exampleapp.R + +/** + * Page Object Model for MainActivity. + * Contains all UI interactions and assertions for the main screen. + */ +class MainScreen { + + /** + * Taps the "Invoices list (Material 3 Theme)" button and waits for InvoicesActivity to open. + */ + fun tapInvoicesListButton(): MainScreen { + onView(withId(R.id.invoices_screen)) + .perform(scrollTo(), click()) + Thread.sleep(2000) + return this + } +} diff --git a/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/PaymentReviewScreen.kt b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/PaymentReviewScreen.kt new file mode 100644 index 0000000000..67838aef2a --- /dev/null +++ b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/pages/PaymentReviewScreen.kt @@ -0,0 +1,178 @@ +package net.gini.android.health.sdk.exampleapp.test.pages + +import android.view.View +import android.widget.EditText +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import org.hamcrest.Matcher +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.emptyString +import org.hamcrest.Matchers.not +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse + +/** + * Page Object Model for the Payment Review screen. + * Contains all UI interactions and assertions for the payment review screen. + */ +class PaymentReviewScreen { + + /** + * Waits for the Payment Review screen to load, opens the bank selection bottom sheet, + * selects the bank matching [bankName], then taps "Continue to overview". + */ + fun selectBankAndContinue(bankName: String = "Bank"): PaymentReviewScreen { + // Wait for the Payment Review screen to load + Thread.sleep(3000) + + // Tap the bank picker container to open the bank selection bottom sheet + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.gps_two_rows_container), + isDisplayed() + ) + ).perform(click()) + + // Wait for the bank selection bottom sheet to appear + Thread.sleep(1500) + + // Choose the bank matching bankName from the list + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.gps_select_bank_button), + withText(bankName), + isDescendantOfA(withId(net.gini.android.internal.payment.R.id.gps_payment_provider_apps_list)), + isDisplayed() + ) + ).perform(click()) + + // Wait for the selection to register and button to activate + Thread.sleep(1000) + + // Tap "Continue to overview" button + onView(withText("Continue to overview")) + .perform(click()) + + // Wait for the overview / payment review screen to fully render + Thread.sleep(2000) + + return this + } + + /** + * Taps the "Pay" button on the payment overview screen to launch the bank app. + * The button is identified by [net.gini.android.internal.payment.R.id.gps_pay_button]. + */ + fun tapPayButton(): PaymentReviewScreen { + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.payment), + isDisplayed() + ) + ).perform(click()) + + // Wait for the bank app to launch + Thread.sleep(3000) + + return this + } + + /** + * Captures all 4 extracted payment fields (Recipient, IBAN, Amount, Reference) from the + * payment details view, prints them to logcat and asserts them against the expected values. + * + * @param expectedRecipient expected recipient name + * @param expectedIban expected IBAN string + * @param expectedAmount expected amount string + * @param expectedReference expected reference/purpose string; pass empty string to skip assertion + */ + fun verifyExtractionFields( + expectedRecipient: String, + expectedIban: String, + expectedAmount: String, + expectedReference: String + ): PaymentReviewScreen { + val paymentDetailsId = net.gini.android.internal.payment.R.id.gps_payment_details + + // Capture and verify Recipient + val recipientText = Array(1) { "" } + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.recipient), + isDescendantOfA(withId(paymentDetailsId)), + isDisplayed() + ) + ) + .perform(captureFieldText(recipientText)) + .check(matches(not(withText(emptyString())))) + assertFalse("Recipient should not be empty", recipientText[0].isEmpty()) + println("✅ Recipient: ${recipientText[0]}") + assertEquals("Recipient extraction mismatch", expectedRecipient, recipientText[0]) + + // Capture and verify IBAN + val ibanText = Array(1) { "" } + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.iban), + isDescendantOfA(withId(paymentDetailsId)), + isDisplayed() + ) + ) + .perform(captureFieldText(ibanText)) + .check(matches(not(withText(emptyString())))) + assertFalse("IBAN should not be empty", ibanText[0].isEmpty()) + println("✅ IBAN: ${ibanText[0]}") + assertEquals("IBAN extraction mismatch", expectedIban, ibanText[0]) + + // Capture and verify Amount + val amountText = Array(1) { "" } + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.amount), + isDescendantOfA(withId(paymentDetailsId)), + isDisplayed() + ) + ) + .perform(captureFieldText(amountText)) + .check(matches(not(withText(emptyString())))) + assertFalse("Amount should not be empty", amountText[0].isEmpty()) + println("✅ Amount: ${amountText[0]}") + assertEquals("Amount extraction mismatch", expectedAmount, amountText[0]) + + // Capture and verify Reference (purpose) + val purposeText = Array(1) { "" } + onView( + allOf( + withId(net.gini.android.internal.payment.R.id.purpose), + isDescendantOfA(withId(paymentDetailsId)), + isDisplayed() + ) + ) + .perform(captureFieldText(purposeText)) + println("✅ Reference: ${purposeText[0]}") + if (expectedReference.isNotEmpty()) { + assertEquals("Reference extraction mismatch", expectedReference, purposeText[0]) + } + + return this + } + + // ── private helpers ────────────────────────────────────────────────────── + + private fun captureFieldText(container: Array): ViewAction = object : ViewAction { + override fun getConstraints(): Matcher = isDisplayed() + override fun getDescription() = "capture text from view" + override fun perform(uiController: UiController, view: View) { + container[0] = (view as? EditText)?.text?.toString() + ?: view.contentDescription?.toString() + ?: "" + } + } +} diff --git a/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/testdata/TestData.kt b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/testdata/TestData.kt new file mode 100644 index 0000000000..f5272de6c2 --- /dev/null +++ b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/testdata/TestData.kt @@ -0,0 +1,51 @@ +package net.gini.android.health.sdk.exampleapp.test.testdata + +/** + * Central store for all test data used across UI tests. + * + * POM best practice: + * - Page classes → UI interactions & assertions only + * - Test classes → test flow orchestration only + * - TestData → all constants, input values & expected extraction results + */ +object TestData { + + /** + * Invoice data for the PVS RHEIN-RUHR GMBH invoice. + * Used in [SmokeTestHealth.testInvoiceFromInvoiceList]. + */ + object PvsRheinRuhrInvoice { + /** Recipient name shown in the invoices list — used to locate and tap the correct item. */ + const val RECIPIENT = "PVS RHEIN-RUHR GMBH" + + /** Expected extracted IBAN on the payment review screen. */ + const val IBAN = "DE62300700100056910300" + + /** Expected extracted amount on the payment review screen. */ + const val AMOUNT = "18,23" + const val AMOUNT1 = "18.23" + + /** Expected extracted reference/purpose on the payment review screen. */ + const val REFERENCE = "Rechnungsdatum 01.08.2013" + } + + /** + * Bank names available in the bank selection bottom sheet. + * Add new bank entries here as more banks are supported. + */ + object BankNames { + const val BANK = "Bank" + } + + /** + * Package identifiers for apps used during end-to-end testing. + */ + object AppPackages { + /** Package name of the health-sdk example app (the app under test). */ + const val HEALTH_SDK_EXAMPLE_APP = "net.gini.android.health.sdk.exampleapp" + + /** Package name of the bank-sdk example app (devPaymentProvider3Debug variant). */ + const val BANK_SDK_EXAMPLE_APP = "net.gini.android.bank.insurance.mock" + } +} + diff --git a/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/uitest/SmokeTestHealth.kt b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/uitest/SmokeTestHealth.kt new file mode 100644 index 0000000000..fdbb0d00f8 --- /dev/null +++ b/health-sdk/example-app/src/androidTest/java/net/gini/android/health/sdk/exampleapp/test/uitest/SmokeTestHealth.kt @@ -0,0 +1,58 @@ +package net.gini.android.health.sdk.exampleapp.test.uitest + +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import net.gini.android.health.sdk.exampleapp.MainActivity +import net.gini.android.health.sdk.exampleapp.test.pages.BankScreen +import net.gini.android.health.sdk.exampleapp.test.pages.InvoicesScreen +import net.gini.android.health.sdk.exampleapp.test.pages.MainScreen +import net.gini.android.health.sdk.exampleapp.test.pages.PaymentReviewScreen +import net.gini.android.health.sdk.exampleapp.test.testdata.TestData +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class SmokeTestHealth { + + @get:Rule + val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun testInvoiceFromInvoiceList() { + // Step 1 & 2: Tap "Invoices list (Material 3 Theme)" and wait for InvoicesActivity to open + MainScreen().tapInvoicesListButton() + + // Step 3-6: Open overflow menu, upload invoices and wait for the list to populate + InvoicesScreen().loadInvoices() + + // Step 7: Scroll to the invoice matching the recipient name and tap its pay button + InvoicesScreen().tapInvoiceByRecipient(TestData.PvsRheinRuhrInvoice.RECIPIENT) + + // Step 8-18: Select bank, continue to overview and verify all 4 extracted payment fields + PaymentReviewScreen() + .selectBankAndContinue(bankName = TestData.BankNames.BANK) + .verifyExtractionFields( + expectedRecipient = TestData.PvsRheinRuhrInvoice.RECIPIENT, + expectedIban = TestData.PvsRheinRuhrInvoice.IBAN, + expectedAmount = TestData.PvsRheinRuhrInvoice.AMOUNT, + expectedReference = TestData.PvsRheinRuhrInvoice.REFERENCE + ) + // Step 19: Tap the Pay button — Health SDK fires the payment intent to the Bank app + .tapPayButton() + + // Step 20: Switch context from Health app → Bank app via launchIntentForPackage + // Step 21-23: UiAutomator verifies fields, taps Pay, waits for deep-link return + BankScreen() + .switchToBankApp() + .verifyPaymentDetails( + expectedRecipient = TestData.PvsRheinRuhrInvoice.RECIPIENT, + expectedIban = TestData.PvsRheinRuhrInvoice.IBAN, + expectedAmount = TestData.PvsRheinRuhrInvoice.AMOUNT1, + expectedPurpose = TestData.PvsRheinRuhrInvoice.REFERENCE + ) + .tapPayAndReturnToBusiness() + } +}