diff --git a/.github/workflows/espresso-test.yml b/.github/workflows/espresso-test.yml new file mode 100644 index 00000000..e283da46 --- /dev/null +++ b/.github/workflows/espresso-test.yml @@ -0,0 +1,92 @@ +name: Espresso Test + +on: + push: + branches: + - feature-testing-tool + pull_request: + branches: + - feature-testing-tool + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Add Client ID + env: + CLIENT_ID: ${{ secrets.CLIENT_ID }} + run: ./gradlew updateLocalsXmlFile + + - name: Build APKs + run: | + ./gradlew :demo:assembleDebug :demo:assembleAndroidTest + + - name: Upload App to BrowserStack + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + run: | + APP_APK_PATH=./demo/build/outputs/apk/debug/demo-debug.apk + TEST_APK_PATH=./demo/build/outputs/apk/androidTest/debug/demo-debug-androidTest.apk + + echo "App APK path: $APP_APK_PATH" + echo "Test APK path: $TEST_APK_PATH" + + if [[ -z "$APP_APK_PATH" || -z "$TEST_APK_PATH" ]]; then + echo "Error: APK file not found." + exit 1 + fi + + # Upload app APK and capture response + APP_UPLOAD_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/app" \ + -F "file=@$APP_APK_PATH") + + echo "App upload response: $APP_UPLOAD_RESPONSE" + + APP_URL=$(echo $APP_UPLOAD_RESPONSE | jq -r '.app_url') + + if [[ -z "$APP_URL" ]]; then + echo "Error: App URL not found in response." + exit 1 + fi + + # Upload test suite APK and capture response + TEST_SUITE_UPLOAD_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/test-suite" \ + -F "file=@$TEST_APK_PATH") + + echo "Test suite upload response: $TEST_SUITE_UPLOAD_RESPONSE" + + TEST_SUITE_URL=$(echo $TEST_SUITE_UPLOAD_RESPONSE | jq -r '.test_suite_url') + + if [[ -z "$TEST_SUITE_URL" ]]; then + echo "Error: Test suite URL not found in response." + exit 1 + fi + + # Use the app_url and test_suite_url in another cURL request + FINAL_RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/espresso/v2/build" \ + -d "{\"app\": \"$APP_URL\", \"testSuite\": \"$TEST_SUITE_URL\", \"devices\": [\"Samsung Galaxy S23-13.0\"], \"project\": \"Paypal_Messages_Android\"}" \ + -H "Content-Type: application/json") + + echo "Final response: $FINAL_RESPONSE" diff --git a/build.gradle b/build.gradle index d7827a1c..98ed7087 100644 --- a/build.gradle +++ b/build.gradle @@ -61,9 +61,36 @@ tasks.register('changeReleaseVersion') { def topLevelGradleFileText = topLevelGradleFile.getText('UTF-8') def useSnapshot = System.getenv('USE_SNAPSHOT') def snapshotParam = useSnapshot == "true" || useSnapshot == true ? "-SNAPSHOT" : "" - + def updatedScript = topLevelGradleFileText.replaceFirst(/("sdkVersionName"\s*: )".*",/, '$1"' + versionParam + snapshotParam + '",') topLevelGradleFile.write(updatedScript, 'UTF-8') } } + + + +task updateLocalsXmlFile(type: ReplaceStringTask) { + replacementString = System.getenv('CLIENT_ID') ?: "REPLACE_WITH_CLIENT_ID_IN_LOCALS_XML" +} + +class ReplaceStringTask extends DefaultTask { + @Input + String replacementString + + @TaskAction + void updateLocalsXml() { + def filePath = "demo/src/main/res/values/locals.xml" + def file = project.file(filePath) // Use project's file method + + // Read the current contents of locals.xml + def currentContent = file.text + + // Perform replacement + def modifiedContent = currentContent.replaceAll("REPLACE_WITH_CLIENT_ID_IN_LOCALS_XML", replacementString) + + // Write the modified content back to locals.xml + file.text = modifiedContent + + } +} diff --git a/demo/build.gradle b/demo/build.gradle index 232cae9b..aafe3eb6 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -57,6 +57,15 @@ android { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } + sourceSets { + androidTest { + java { + // Exclude tests in specific directories + exclude '**/src/androidTest/**' + } + } + } + } dependencies { @@ -74,9 +83,12 @@ dependencies { implementation project(':library') implementation 'com.google.android.material:material:1.10.0' - testImplementation 'junit:junit:4.13.2' + implementation 'junit:junit:4.13.2' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation platform('androidx.compose:compose-bom:2023.09.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.5.1' debugImplementation 'androidx.compose.ui:ui-tooling:1.5.1' diff --git a/demo/src/androidTest/java/com/paypal/messagesdemo/InlineXmlTest.kt b/demo/src/androidTest/java/com/paypal/messagesdemo/InlineXmlTest.kt new file mode 100644 index 00000000..26e9ca39 --- /dev/null +++ b/demo/src/androidTest/java/com/paypal/messagesdemo/InlineXmlTest.kt @@ -0,0 +1,58 @@ +package com.paypal.messagesdemo + +import android.view.Gravity +import androidx.core.content.ContextCompat +import androidx.test.espresso.Espresso +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.paypal.messages.R +import com.paypal.messages.config.message.style.PayPalMessageColor +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +public class InlineXmlTest { + var expectedColor: Int? = null + + @get:Rule + val activityScenarioRule = ActivityScenarioRule(BasicXmlActivity::class.java) + + @Test + fun testGenericMessage() { + waitForApp(500) + + // Check if SecondActivity is displayed by verifying a TextView in SecondActivity + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(Gravity.LEFT))) + + // Get the actual color value from the resource ID + activityScenarioRule.scenario.onActivity { activity -> + expectedColor = ContextCompat.getColor(activity, PayPalMessageColor.BLACK.colorResId) + } + + // Use the custom matcher to check the text color of the TextView + onView(withId(R.id.content)) + .check(matches(ColorMatcher.withTextColor(expectedColor!!))) + } + + @Test + fun testGenericModalCloseWithBackButton() { + waitForApp(500) + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + + clickMessage() + waitForApp(1000) + modalContent("Get more info") + + Espresso.pressBack() + waitForApp(500) + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + clickMessage() + waitForApp(1000) + modalContent("Get more info") + } +} diff --git a/demo/src/androidTest/java/com/paypal/messagesdemo/JetPackTest.kt b/demo/src/androidTest/java/com/paypal/messagesdemo/JetPackTest.kt new file mode 100644 index 00000000..cf8794f2 --- /dev/null +++ b/demo/src/androidTest/java/com/paypal/messagesdemo/JetPackTest.kt @@ -0,0 +1,55 @@ +package com.paypal.messagesdemo + +// @RunWith(AndroidJUnit4ClassRunner::class) +// public class JetPackTest { +// var expectedColor: Int? = null +// +// @Rule +// @JvmField +// val activityScenarioRule = ActivityScenarioRule( +// Intent(ApplicationProvider.getApplicationContext(), JetpackActivity::class.java).apply { +// putExtra("TEST_ENV", "LIVE") +// }, +// ) +// +// fun submit() { +// onView(withId(Demo.id.submit)).perform(scrollTo()) +// onView(withId(Demo.id.submit)).perform(click()) +// waitForApp(500) +// } +// +// @Test +// fun testGenericMessage() { +// // Perform a delay +// waitForApp(1000) +// +// // Check if SecondActivity is displayed by verifying a TextView in SecondActivity +// checkMessage("%paypal_logo% Buy now, pay later. Learn more") +// onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(Gravity.LEFT))) +// +// // Get the actual color value from the resource ID +// activityScenarioRule.scenario.onActivity { activity -> +// expectedColor = ContextCompat.getColor(activity, PayPalMessageColor.BLACK.colorResId) +// } +// +// // Use the custom matcher to check the text color of the TextView +// onView(withId(R.id.content)) +// .check(matches(ColorMatcher.withTextColor(expectedColor!!))) +// } +// +// @Test +// fun testGenericMessageAndModal() { +// waitForApp(2000) +// +// // Check if SecondActivity is displayed by verifying a TextView in SecondActivity +// checkMessage("%paypal_logo% Buy now, pay later. Learn more") +// clickMessage() +// +// onView(withId(R.id.ModalWebView)).check( +// matches(ViewMatchers.isDisplayed()), +// ) +// +// modalContent("Pay Later options") +// waitForApp(2000) +// } +// } diff --git a/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt b/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt new file mode 100644 index 00000000..ea1e2f8f --- /dev/null +++ b/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt @@ -0,0 +1,544 @@ +package com.paypal.messagesdemo + +import android.content.Intent +import android.view.Gravity +import androidx.core.content.ContextCompat +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.paypal.messages.R +import com.paypal.messages.config.message.style.PayPalMessageAlignment +import com.paypal.messages.config.message.style.PayPalMessageColor +import org.hamcrest.CoreMatchers.containsString +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import com.paypal.messagesdemo.R as Demo + +@RunWith(AndroidJUnit4ClassRunner::class) +abstract class XmlDemoSetup { + + var expectedColor: Int? = null + + @Rule + @JvmField + val activityScenarioRule = ActivityScenarioRule( + Intent(ApplicationProvider.getApplicationContext(), XmlActivity::class.java).apply { + putExtra("TEST_ENV", "LIVE") + }, + ) +} + +@RunWith(AndroidJUnit4ClassRunner::class) +public class XmlDemoGenericTest : XmlDemoSetup() { + + @Test + fun testGenericMessage() { + // Perform a delay + + // Check if SecondActivity is displayed by verifying a TextView in SecondActivity + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(Gravity.LEFT))) + + // Get the actual color value from the resource ID + activityScenarioRule.scenario.onActivity { activity -> + expectedColor = ContextCompat.getColor(activity, PayPalMessageColor.BLACK.colorResId) + } + + // Use the custom matcher to check the text color of the TextView + onView(withId(R.id.content)) + .check(matches(ColorMatcher.withTextColor(expectedColor!!))) + } + + @Test + fun testGenericMessageAndModal() { + waitForApp(2000) + + // Check if SecondActivity is displayed by verifying a TextView in SecondActivity + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + clickMessage() + + onView(withId(R.id.ModalWebView)).check( + matches(ViewMatchers.isDisplayed()), + ) + + modalContent("Pay Later options") + } + + @Test + fun testGenericModalNavigatingTiles() { + waitForApp(2000) + + clickMessage() + + onView(withId(R.id.ModalWebView)).check( + matches(ViewMatchers.isDisplayed()), + ) + + closeButtonPresent() + checkPi4TilePresent() + checkPayMonthlyTilePresent() + checkNiTilePresent() + + clickNiTile() + checkNIContent() + clickSeeOtherModalOptions() + + clickPi4Tile() + checkPi4ModalContent() + clickSeeOtherModalOptions() + + clickPayMonthlyTile() + checkPayMonthlyContent() + clickSeeOtherModalOptions() + + closeModal() + } + + @Test + fun testGenericModalClose() { + waitForApp(5000) + + clickMessage() + + onView(withId(R.id.ModalWebView)).check( + matches(ViewMatchers.isDisplayed()), + ) + + closeModal() + } + + @Test + fun testGenericModalCloseAndOpenSameMessage() { + waitForApp(2000) + + clickMessage() + modalContent("Pay Later") + + clickPi4Tile() + checkPi4ModalContent() + closeModal() + waitForApp(500) + + clickMessage() + waitForApp(500) + checkPi4ModalContent() + } +} + +@RunWith(AndroidJUnit4ClassRunner::class) +public class XmlDemoShortTermTest : XmlDemoSetup() { + + @Test + fun testShorTermMessage() { + waitForApp(500) + clickShortTermOffer() + submit() + waitForApp(500) + } + + @Test + fun testShortTermNonQualifyingMessage() { + waitForApp(500) + clickShortTermOffer() + + typeAmount("15") + submit() + waitForApp(500) + onView(withId(R.id.content)).check(matches(withText(containsString("payments on purchases of ")))) + waitForApp(500) + clearAmount() + typeAmount("2000") + submit() + onView(withId(R.id.content)).check(matches(withText(containsString("payments on purchases of ")))) + + waitForApp(500) + } + + @Test + fun testShortTermQualifyingMessage() { + waitForApp(500) + clickShortTermOffer() + + typeAmount("1000") + submit() + waitForApp(500) + onView(withId(R.id.content)).check(matches(withText(containsString("250")))) +// checkMessage("250") +// waitForApp(5000) + } + + @Test + fun testShortTermMessageAndModal() { + waitForApp(500) + clickShortTermOffer() + submit() + waitForApp(500) + clickMessage() + checkPi4ModalContent() + closeModal() + } + + @Test + fun testShortTermOpenAndSwitchModal() { + waitForApp(500) + clickShortTermOffer() + submit() + waitForApp(500) + clickMessage() + checkPi4ModalContent() + clickSeeOtherModalOptions() + waitForApp(200) + clickPi4Tile() + checkPi4ModalContent() + } + + @Test + fun testShortTermQualifyingModal() { + waitForApp(500) + clickShortTermOffer() + typeAmount("1000") + submit() + waitForApp(500) + clickMessage() + checkPi4ModalContent() + } +} + +@RunWith(AndroidJUnit4ClassRunner::class) +public class XmlDemoLongTermTest : XmlDemoSetup() { + + @Test + fun testLongTermNonQualifyingMessage() { + waitForApp(500) + clickLongTermOffer() + typeAmount("15") + submit() + waitForApp(500) + checkMessage("%paypal_logo% Pay monthly") + clearAmount() + typeAmount("20000") + submit() + checkMessage("%paypal_logo% Pay monthly") + } + + @Test + fun testLongTermQualifyingMessage() { + waitForApp(500) + clickLongTermOffer() + typeAmount("1000") + submit() + waitForApp(2000) + checkMessage("$95") + } + + @Test + fun testLongTermNoAmountMessage() { + waitForApp(500) + clickLongTermOffer() + submit() + waitForApp(500) + clickMessage() + checkPayMonthlyContent() + modalContent("Enter an amount of") + } + + @Test + fun testLongTermQualifyingAmountMessage() { + waitForApp(500) + clickLongTermOffer() + typeAmount("1000") + submit() + waitForApp(500) + clickMessage() + checkPayMonthlyContent() + modalContent("12 months") + } + + @Test + fun testLongTermRangeMessage() { + waitForApp(500) + clickLongTermOffer() + submit() + waitForApp(500) + clickMessage() + checkPayMonthlyContent() + + typeCalculatorAmount("1000") + waitForApp(5000) + modalContent("for 12") + + clearCalculatorAmount() + waitForApp(500) + typeCalculatorAmount("100") + waitForApp(5000) + modalContent("Enter an amount") + + clearCalculatorAmount() + typeCalculatorAmount("20000") + waitForApp(5000) + modalContent("Enter an amount no larger") + waitForApp(500) + } + + @Test + fun testLongTermModalNavigation() { + waitForApp(500) + clickLongTermOffer() + submit() + waitForApp(500) + clickMessage() + checkPayMonthlyContent() + + clickSeeOtherModalOptions() + clickPayMonthlyTile() + checkPayMonthlyContent() + } + + @Test + fun testLongTermModalDisclosure() { + waitForApp(500) + clickLongTermOffer() + submit() + waitForApp(2000) + clickMessage() + checkPayMonthlyContent() + waitForApp(2000) + + modalContent("Find more disclosures") + +// Iframe clicks not working +// clickDisclosure() +// Espresso.pressBack() +// waitForApp(500) +// checkPayMonthlyContent() + } +} + +@RunWith(AndroidJUnit4ClassRunner::class) +public class XmlDemoNiTest : XmlDemoSetup() { + + @Test + fun testNiMessage() { + waitForApp(500) + clickNIOffer() + submit() + waitForApp(1000) + checkMessage("No Interest") + } + + @Test + fun testNonQualifyingNiMessage() { + waitForApp(500) + clickNIOffer() + typeAmount("15") + submit() + waitForApp(1000) + checkMessage("No Interest") + } + + @Test + fun testQualifyingNiMessage() { + waitForApp(500) + clickNIOffer() + typeAmount("100") + submit() + waitForApp(1000) + checkMessage("No Interest") + } + + @Test + fun testNiMessageAndModal() { + waitForApp(500) + clickNIOffer() + submit() + waitForApp(1000) + checkMessage("%paypal_logo% No Interest") + clickMessage() + waitForApp(1000) + modalContent("Apply now") + } + + @Test + fun testNiModalNavigateOtherAndBack() { + waitForApp(500) + clickNIOffer() + submit() + waitForApp(1000) + checkMessage("%paypal_logo% No Interest") + clickMessage() + waitForApp(1000) + modalContent("Apply now") + clickSeeOtherModalOptions() + clickNiTile() + modalContent("Apply now") + } + + @Test + fun testNiModalApplyNowCloses() { + waitForApp(500) + clickNIOffer() + submit() + waitForApp(1000) + checkMessage("%paypal_logo% No Interest") + clickMessage() + waitForApp(2000) + modalContent("Apply now") +// Clicking iframe button not working +// waitForApp(1000) +// clickApplyNow() +// Espresso.pressBack() +// waitForApp(1000) +// modalContent("Apply now") + } + + @Test + fun testNiModalTermsCloses() { + waitForApp(500) + clickNIOffer() + submit() + waitForApp(1000) + checkMessage("%paypal_logo% No Interest") + clickMessage() + waitForApp(1000) + modalContent("Apply now") +// clickApplyNow() +// waitForApp(20000) +// Espresso.pressBack() +// waitForApp(1000) +// modalContent("Apply now") + } +} + +@RunWith(AndroidJUnit4ClassRunner::class) +public class XmlDemoStyleOptionsTest : XmlDemoSetup() { + + @Test + fun testAlignment() { + waitForApp(500) + onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(Gravity.LEFT))) + + onView(withId(Demo.id.styleCenter)).perform(click()) + submit() + waitForApp(500) + onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(PayPalMessageAlignment.CENTER.value))) + + onView(withId(Demo.id.styleRight)).perform(click()) + submit() + waitForApp(500) + onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(Gravity.RIGHT))) + } + + @Test + fun testMessageColors() { + waitForApp(500) + checkMessageColor(activityScenarioRule, PayPalMessageColor.BLACK) + + onView(withId(Demo.id.styleWhite)).perform(click()) + submit() + waitForApp(500) + checkMessageColor(activityScenarioRule, PayPalMessageColor.WHITE) + + onView(withId(Demo.id.styleMonochrome)).perform(click()) + submit() + waitForApp(500) + checkMessageColor(activityScenarioRule, PayPalMessageColor.MONOCHROME) + + onView(withId(Demo.id.styleGrayscale)).perform(click()) + submit() + waitForApp(500) + checkMessageColor(activityScenarioRule, PayPalMessageColor.GRAYSCALE) + + onView(withId(Demo.id.styleBlack)).perform(click()) + submit() + waitForApp(500) + checkMessageColor(activityScenarioRule, PayPalMessageColor.BLACK) + } + + @Test + fun testLogoAlignment() { + waitForApp(2000) + onView(withId(Demo.id.styleInline)).perform(click()) + submit() + waitForApp(2000) + checkMessage("Buy now, pay later with %paypal_logo%. Learn more") + + onView(withId(Demo.id.styleAlternative)).perform(click()) + submit() + waitForApp(2000) + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + + onView(withId(Demo.id.styleNone)).perform(click()) + submit() + waitForApp(2000) + checkMessage("Buy now, pay later with PayPal. Learn more") + + onView(withId(Demo.id.stylePrimary)).perform(click()) + submit() + waitForApp(2000) + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + waitForApp(1000) + } +} + +// TODO - Need to change client id via secrets +// @RunWith(AndroidJUnit4ClassRunner::class) +// public class XmlDemoCrossBorderTest : XmlDemoSetup() { +// +// // @Test +// // fun testCrossBorder(){ +// +// // } +// } + +// @RunWith(AndroidJUnit4ClassRunner::class) +// public class XmlDemoEligibilityTest : XmlDemoSetup() { +// +// @Test +// fun testStandardConfig() { +// waitForApp(500) +// +// checkMessage("Buy now") +// typeAmount("20") +// submit() +// waitForApp(1000) +// checkMessage("-") +// clearAmount() +// +// typeAmount("50") +// submit() +// waitForApp(1000) +// checkMessage("12.50") +// clearAmount() +// +// typeAmount("2000") +// submit() +// waitForApp(1000) +// checkMessage("107.73") +// clearAmount() +// +// typeAmount("100000") +// submit() +// waitForApp(1000) +// checkMessage("-") +// +// clickMessage() +// waitForApp(1000) +// clickSeeOtherModalOptions() +// clickPi4Tile() +// waitForApp(500) +// clickSeeOtherModalOptions() +// clickNiTile() +// waitForApp(500) +// clickSeeOtherModalOptions() +// clickPayMonthlyTile() +// } +// +// // TODO - Need to change client ids for other tests +// } diff --git a/demo/src/androidTest/java/common/EsspressoUi.kt b/demo/src/androidTest/java/common/EsspressoUi.kt new file mode 100644 index 00000000..462c9498 --- /dev/null +++ b/demo/src/androidTest/java/common/EsspressoUi.kt @@ -0,0 +1,249 @@ +package com.paypal.messagesdemo + +import android.view.Gravity +import android.view.View +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.action.ViewActions.clearText +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.web.assertion.WebViewAssertions +import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches +import androidx.test.espresso.web.sugar.Web.onWebView +import androidx.test.espresso.web.webdriver.DriverAtoms +import androidx.test.espresso.web.webdriver.Locator +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.paypal.messages.R +import com.paypal.messages.config.message.style.PayPalMessageColor +import org.hamcrest.CoreMatchers.containsString +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher +import com.paypal.messagesdemo.R as Demo + +// Check Text Color +object ColorMatcher { + fun withTextColor( + @ColorInt expectedColor: Int, + ): TypeSafeMatcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("with text color: ") + description.appendValue(expectedColor) + } + + override fun matchesSafely(view: View): Boolean { + if (view is TextView) { + return view.currentTextColor == expectedColor + } + return false + } + } + } +} + +// Check Positioning +object GravityMatcher { + fun withGravity(expectedGravity: Int): TypeSafeMatcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("with gravity: ") + description.appendValue(expectedGravity) + } + + override fun matchesSafely(view: View): Boolean { + if (view is TextView) { + return (view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) == expectedGravity + } + return false + } + } + } +} + +// Custom ViewAction to wait +fun waitFor(millis: Long): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher { + return isRoot() + } + + override fun getDescription(): String { + return "wait for $millis milliseconds" + } + + override fun perform(uiController: UiController, view: View) { + uiController.loopMainThreadForAtLeast(millis) + } + } +} + +fun waitForApp(millis: Long) { + onView(isRoot()).perform(waitFor(millis)) +} + +fun clickMessage() { + onView(withId(R.id.content)).perform(click()) + waitFor(500) +} + +fun submit() { + onView(withId(Demo.id.submit)).perform(scrollTo()) + onView(withId(Demo.id.submit)).perform(click()) + waitFor(500) +} + +fun checkPi4TilePresent() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Interest-free payments every 2 weeks, starting today.")), + ) +} + +fun checkPayMonthlyTilePresent() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Split your purchase into equal monthly payments.")), + ) +} + +fun checkNiTilePresent() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("No Interest if paid in full in 6 months for purchases of \$99+.")), + ) +} + +fun closeButtonPresent() { + onView(withId(R.id.ModalCloseButton)).check( + matches(ViewMatchers.isDisplayed()), + ) +} + +fun clickTileByIndex(index: Int) { + onWebView(ViewMatchers.withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".tile:nth-of-type($index)")) + .perform(DriverAtoms.webClick()) +} + +fun clickPi4Tile() { + clickTileByIndex(1) +} + +fun clickPayMonthlyTile() { + clickTileByIndex(2) +} + +fun clickNiTile() { + clickTileByIndex(3) +} + +fun testDisclosure() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Find more disclosures")), + ) +} + +fun clickSeeOtherModalOptions() { + onWebView( + ViewMatchers.withId(R.id.ModalWebView), + ).withElement(DriverAtoms.findElement(Locator.ID, "productListLink")).perform(DriverAtoms.webClick()) +} + +fun clickDisclosure() { + onWebView( + ViewMatchers.withId(R.id.ModalWebView), + ).withElement(DriverAtoms.findElement(Locator.CLASS_NAME, "inline-link ")).perform(DriverAtoms.webClick()) +} + +fun modalContent(expectedText: String) { + onWebView(ViewMatchers.withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")) + .check(WebViewAssertions.webMatches(DriverAtoms.getText(), containsString(expectedText))) +} + +fun typeCalculatorAmount(amount: String) { + onWebView(withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".input ")) + .perform(DriverAtoms.webKeys(amount)) +} + +fun clearCalculatorAmount() { + onWebView(withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".input ")) + .perform(DriverAtoms.clearElement()) +} + +fun clickApplyNow() { + onWebView( + withId(R.id.ModalWebView), + ).withElement(DriverAtoms.findElement(Locator.CLASS_NAME, ".button.content__row")).perform(DriverAtoms.webClick()) +} + +fun checkPi4ModalContent() { + modalContent("Pay in 4") +} + +fun checkPayMonthlyContent() { + modalContent("Pay Monthly") +} + +fun checkNIContent() { + modalContent("Credit") +} + +fun closeModal() { + onView(withId(R.id.ModalCloseButton)).perform(click()) +} + +fun clickOffer(offerId: Int) { + onView(withId(offerId)).perform(click()) +} + +fun clickShortTermOffer() { + clickOffer(com.paypal.messagesdemo.R.id.offerShortTerm) +} + +fun clickLongTermOffer() { + clickOffer(com.paypal.messagesdemo.R.id.offerLongTerm) +} + +fun clickPayIn1() { + clickOffer(com.paypal.messagesdemo.R.id.offerPayIn1) +} + +fun clickNIOffer() { + clickOffer(com.paypal.messagesdemo.R.id.offerCredit) +} + +fun typeAmount(text: String) { + onView(withId(com.paypal.messagesdemo.R.id.amount)).perform(typeText(text)) +} + +fun clearAmount() { + onView(withId(com.paypal.messagesdemo.R.id.amount)).perform(clearText()) +} + +fun checkMessage(text: String) { + onView(withId(R.id.content)).check(matches(withText(containsString(text)))) +} + +fun checkMessageColor(activityScenarioRule: ActivityScenarioRule<*>, color: PayPalMessageColor) { + var expectedColor: Int? = null + + // Get the actual color value from the resource ID + activityScenarioRule.scenario.onActivity { activity -> + expectedColor = ContextCompat.getColor(activity, color.colorResId) + } + + // Use the custom matcher to check the text color of the TextView + onView(withId(R.id.content)) + .check(matches(ColorMatcher.withTextColor(expectedColor!!))) +} diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 9ff8ef01..d3b3fa38 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -33,5 +33,15 @@ + + + + + + + diff --git a/demo/src/main/java/com/paypal/messagesdemo/BasicXmlActivity.kt b/demo/src/main/java/com/paypal/messagesdemo/BasicXmlActivity.kt new file mode 100644 index 00000000..8e9368cd --- /dev/null +++ b/demo/src/main/java/com/paypal/messagesdemo/BasicXmlActivity.kt @@ -0,0 +1,15 @@ +package com.paypal.messagesdemo + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.paypal.messagesdemo.databinding.ActivityBasicMessageBinding + +class BasicXmlActivity : AppCompatActivity() { + private lateinit var binding: ActivityBasicMessageBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityBasicMessageBinding.inflate(layoutInflater) + setContentView(binding.root) + } +} diff --git a/demo/src/main/java/com/paypal/messagesdemo/JetpackActivity.kt b/demo/src/main/java/com/paypal/messagesdemo/JetpackActivity.kt index b0992c9b..7bdf32f1 100644 --- a/demo/src/main/java/com/paypal/messagesdemo/JetpackActivity.kt +++ b/demo/src/main/java/com/paypal/messagesdemo/JetpackActivity.kt @@ -1,5 +1,6 @@ package com.paypal.messagesdemo +import android.content.Intent import android.os.Bundle import android.util.Log import android.widget.Toast @@ -49,9 +50,17 @@ fun toSentenceCase(input: String): String { return input.lowercase().replaceFirstChar { it.titlecase() } } +fun determineEnvironment(intent: Intent): PayPalEnvironment { + return when (intent.getStringExtra("TEST_ENV")) { + "LIVE" -> PayPalEnvironment.LIVE + "SANDBOX" -> PayPalEnvironment.SANDBOX + "DEVELOP" -> PayPalEnvironment.DEVELOP() + else -> PayPalEnvironment.SANDBOX // Default value if no match is found + } +} + class JetpackActivity : ComponentActivity() { private val TAG = "PPM:JetpackActivity" - private val environment = PayPalEnvironment.SANDBOX override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -60,6 +69,8 @@ class JetpackActivity : ComponentActivity() { BasicTheme { val context = LocalContext.current + var environment by remember { mutableStateOf(determineEnvironment(intent)) } + var clientId: String by remember { mutableStateOf(getString(R.string.client_id)) } // Style Color @@ -305,6 +316,7 @@ class JetpackActivity : ComponentActivity() { modifier = Modifier .padding(top = 16.dp, bottom = 32.dp, start = 8.dp, end = 8.dp) .background(color = backgroundColor) + .height(40.dp) .fillMaxWidth(), factory = { messageView diff --git a/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt b/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt index 5a44eca0..359b2233 100644 --- a/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt +++ b/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt @@ -28,13 +28,19 @@ class XmlActivity : AppCompatActivity() { private var logoType: PayPalMessageLogoType = PayPalMessageLogoType.PRIMARY private var textAlignment: PayPalMessageAlignment = PayPalMessageAlignment.LEFT private var offerType: PayPalMessageOfferType? = null - private val environment = PayPalEnvironment.SANDBOX + private var environment: PayPalEnvironment = PayPalEnvironment.SANDBOX override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMessageBinding.inflate(layoutInflater) setContentView(binding.root) + environment = when(intent.getStringExtra("TEST_ENV")) { + "LIVE" -> PayPalEnvironment.LIVE + "SANDBOX" -> PayPalEnvironment.SANDBOX + "DEVELOP" -> PayPalEnvironment.DEVELOP() + else -> environment + } val messageWrapper = binding.messageWrapper val progressBar = binding.progressBar val resetButton = binding.reset diff --git a/demo/src/main/res/layout/activity_basic_message.xml b/demo/src/main/res/layout/activity_basic_message.xml new file mode 100644 index 00000000..4fa1cc9d --- /dev/null +++ b/demo/src/main/res/layout/activity_basic_message.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/library/src/main/java/com/paypal/messages/PayPalMessageView.kt b/library/src/main/java/com/paypal/messages/PayPalMessageView.kt index 5041eff2..eab0e28a 100644 --- a/library/src/main/java/com/paypal/messages/PayPalMessageView.kt +++ b/library/src/main/java/com/paypal/messages/PayPalMessageView.kt @@ -322,8 +322,12 @@ class PayPalMessageView @JvmOverloads constructor( override fun onDetachedFromWindow() { super.onDetachedFromWindow() // The modal will not dismiss (destroy) itself, it will only hide/show when opening and closing - // so we need to cleanup the modal instance if the message is removed - this.modal?.dismiss() + // so we need to clean up the modal instance if the message is removed + this.modal?.let { + if (it.isAdded) { + it.dismiss() + } + } } /** @@ -406,6 +410,15 @@ class PayPalMessageView @JvmOverloads constructor( buyerCountry = typedArray.getString(R.styleable.PayPalMessageView_paypal_buyer_country) } + if (typedArray.hasValue(R.styleable.PayPalMessageView_paypal_environment)) { + environment = when(typedArray.getString(R.styleable.PayPalMessageView_paypal_environment)) { + "LIVE" -> PayPalEnvironment.LIVE + "SANDBOX" -> PayPalEnvironment.SANDBOX + "DEVELOP" -> PayPalEnvironment.DEVELOP() + else -> PayPalEnvironment.SANDBOX + } + } + /** * STYLE */ diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 7c6c588d..2848cdc6 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -43,5 +43,6 @@ +