|
| 1 | +package com.iterable.integration.tests |
| 2 | + |
| 3 | +import android.content.Intent |
| 4 | +import android.util.Log |
| 5 | +import androidx.lifecycle.Lifecycle |
| 6 | +import androidx.test.core.app.ActivityScenario |
| 7 | +import androidx.test.ext.junit.runners.AndroidJUnit4 |
| 8 | +import androidx.test.platform.app.InstrumentationRegistry |
| 9 | +import androidx.test.uiautomator.UiDevice |
| 10 | +import androidx.test.uiautomator.UiSelector |
| 11 | +import androidx.test.uiautomator.By |
| 12 | +import com.iterable.iterableapi.IterableApi |
| 13 | +import com.iterable.integration.tests.activities.PushNotificationTestActivity |
| 14 | +import org.awaitility.Awaitility |
| 15 | +import org.junit.After |
| 16 | +import org.junit.Assert |
| 17 | +import org.junit.Before |
| 18 | +import org.junit.Test |
| 19 | +import org.junit.runner.RunWith |
| 20 | +import java.util.concurrent.TimeUnit |
| 21 | + |
| 22 | +@RunWith(AndroidJUnit4::class) |
| 23 | +class PushNotificationIntegrationTest : BaseIntegrationTest() { |
| 24 | + |
| 25 | + companion object { |
| 26 | + private const val TAG = "PushNotificationIntegrationTest" |
| 27 | + private const val TEST_PUSH_CAMPAIGN_ID = TestConstants.TEST_PUSH_CAMPAIGN_ID |
| 28 | + } |
| 29 | + |
| 30 | + private lateinit var uiDevice: UiDevice |
| 31 | + private lateinit var mainActivityScenario: ActivityScenario<MainActivity> |
| 32 | + |
| 33 | + @Before |
| 34 | + override fun setUp() { |
| 35 | + Log.d(TAG, "🔧 Test setup starting...") |
| 36 | + |
| 37 | + uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) |
| 38 | + |
| 39 | + // Call super.setUp() to initialize SDK with BaseIntegrationTest's config |
| 40 | + // This sets test mode flag and initializes SDK with test handlers (including urlHandler) |
| 41 | + super.setUp() |
| 42 | + |
| 43 | + Log.d(TAG, "🔧 Base setup complete, SDK initialized with test handlers") |
| 44 | + |
| 45 | + // Disable in-app auto display and clear existing messages BEFORE launching app |
| 46 | + // This prevents in-app messages from obscuring the push notification test screen |
| 47 | + Log.d(TAG, "🔧 Disabling in-app auto display and clearing existing messages...") |
| 48 | + IterableApi.getInstance().inAppManager.setAutoDisplayPaused(true) |
| 49 | + Log.d(TAG, "✅ In-app auto display paused") |
| 50 | + |
| 51 | + // Clear all existing in-app messages |
| 52 | + IterableApi.getInstance().inAppManager.messages.forEach { |
| 53 | + Log.d(TAG, "Clearing existing in-app message: ${it.messageId}") |
| 54 | + IterableApi.getInstance().inAppManager.removeMessage(it) |
| 55 | + } |
| 56 | + Log.d(TAG, "✅ All in-app messages cleared") |
| 57 | + |
| 58 | + Log.d(TAG, "🔧 MainActivity will skip initialization due to test mode flag") |
| 59 | + |
| 60 | + // Now launch the app flow with custom handlers already configured |
| 61 | + launchAppAndNavigateToPushNotificationTesting() |
| 62 | + } |
| 63 | + |
| 64 | + @After |
| 65 | + override fun tearDown() { |
| 66 | + super.tearDown() |
| 67 | + } |
| 68 | + |
| 69 | + private fun launchAppAndNavigateToPushNotificationTesting() { |
| 70 | + // Step 1: Launch MainActivity (the home page) |
| 71 | + Log.d(TAG, "🔧 Step 1: Launching MainActivity...") |
| 72 | + val mainIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, MainActivity::class.java) |
| 73 | + mainActivityScenario = ActivityScenario.launch(mainIntent) |
| 74 | + |
| 75 | + // Wait for MainActivity to be ready |
| 76 | + Awaitility.await() |
| 77 | + .atMost(5, TimeUnit.SECONDS) |
| 78 | + .pollInterval(500, TimeUnit.MILLISECONDS) |
| 79 | + .until { |
| 80 | + val state = mainActivityScenario.state |
| 81 | + Log.d(TAG, "🔧 MainActivity state: $state") |
| 82 | + state == Lifecycle.State.RESUMED |
| 83 | + } |
| 84 | + |
| 85 | + Log.d(TAG, "🔧 MainActivity is ready!") |
| 86 | + |
| 87 | + // Step 2: Click the "Push Notifications" button to navigate to PushNotificationTestActivity |
| 88 | + Log.d(TAG, "🔧 Step 2: Clicking 'Push Notifications' button...") |
| 89 | + val pushButton = uiDevice.findObject(UiSelector().resourceId("com.iterable.integration.tests:id/btnPushNotifications")) |
| 90 | + if (pushButton.exists()) { |
| 91 | + pushButton.click() |
| 92 | + Log.d(TAG, "🔧 Clicked Push Notifications button successfully") |
| 93 | + } else { |
| 94 | + Log.e(TAG, "❌ Push Notifications button not found!") |
| 95 | + Assert.fail("Push Notifications button not found in MainActivity") |
| 96 | + } |
| 97 | + |
| 98 | + // Step 3: Wait for PushNotificationTestActivity to load |
| 99 | + Log.d(TAG, "🔧 Step 3: Waiting for PushNotificationTestActivity to load...") |
| 100 | + Thread.sleep(2000) // Give time for navigation |
| 101 | + |
| 102 | + Log.d(TAG, "🔧 App navigation complete: Now on PushNotificationTestActivity!") |
| 103 | + } |
| 104 | + |
| 105 | + @Test |
| 106 | + fun testPushNotificationMVP() { |
| 107 | + // Step 1: Ensure user is signed in |
| 108 | + Log.d(TAG, "📧 Step 1: Ensuring user is signed in...") |
| 109 | + val userSignedIn = testUtils.ensureUserSignedIn(TestConstants.TEST_USER_EMAIL) |
| 110 | + Assert.assertTrue("User should be signed in", userSignedIn) |
| 111 | + Log.d(TAG, "✅ User signed in successfully: ${TestConstants.TEST_USER_EMAIL}") |
| 112 | + |
| 113 | + // Step 2: Trigger push notification campaign via API |
| 114 | + Log.d(TAG, "🎯 Step 2: Triggering push notification campaign via API...") |
| 115 | + Log.d(TAG, "Campaign ID: $TEST_PUSH_CAMPAIGN_ID") |
| 116 | + Log.d(TAG, "User Email: ${TestConstants.TEST_USER_EMAIL}") |
| 117 | + |
| 118 | + var campaignTriggered = false |
| 119 | + val latch = java.util.concurrent.CountDownLatch(1) |
| 120 | + |
| 121 | + triggerPushCampaignViaAPI(TEST_PUSH_CAMPAIGN_ID, TestConstants.TEST_USER_EMAIL, null) { success -> |
| 122 | + campaignTriggered = success |
| 123 | + Log.d(TAG, "🎯 Push campaign trigger result: $success") |
| 124 | + if (!success) { |
| 125 | + val errorMessage = testUtils.getLastErrorMessage() |
| 126 | + Log.w(TAG, "⚠️ Push campaign trigger failed: $errorMessage") |
| 127 | + } |
| 128 | + latch.countDown() |
| 129 | + } |
| 130 | + |
| 131 | + // Wait for API call to complete (up to 10 seconds for CI) |
| 132 | + val apiCallCompleted = latch.await(10, java.util.concurrent.TimeUnit.SECONDS) |
| 133 | + Log.d(TAG, "🎯 API call completed: $apiCallCompleted, success: $campaignTriggered") |
| 134 | + |
| 135 | + if (!apiCallCompleted) { |
| 136 | + Log.e(TAG, "❌ API call did not complete in time") |
| 137 | + Assert.fail("Push campaign trigger API call did not complete in time") |
| 138 | + return |
| 139 | + } |
| 140 | + |
| 141 | + if (!campaignTriggered) { |
| 142 | + val errorMessage = testUtils.getLastErrorMessage() |
| 143 | + Log.e(TAG, "❌ Push campaign trigger FAILED: $errorMessage") |
| 144 | + Log.e(TAG, "❌ Cannot proceed with test - no push notification will be available") |
| 145 | + Assert.fail("Push campaign trigger failed: $errorMessage. Check API key and campaign configuration.") |
| 146 | + return |
| 147 | + } |
| 148 | + |
| 149 | + Log.d(TAG, "✅ Push campaign triggered successfully, waiting for notification...") |
| 150 | + |
| 151 | + // Step 3: Wait for push notification to arrive (give time for FCM delivery) |
| 152 | + Log.d(TAG, "⏳ Step 3: Waiting for push notification to arrive...") |
| 153 | + Thread.sleep(5000) // Give time for FCM to deliver the notification |
| 154 | + |
| 155 | + // Step 4: Open notification drawer and verify notification is present |
| 156 | + Log.d(TAG, "📱 Step 4: Opening notification drawer...") |
| 157 | + uiDevice.openNotification() |
| 158 | + Thread.sleep(2000) // Wait for notification drawer to open |
| 159 | + |
| 160 | + // Step 5: Find and interact with the notification |
| 161 | + Log.d(TAG, "🔍 Step 5: Looking for push notification in notification drawer...") |
| 162 | + |
| 163 | + // Try to find notification by text (common notification text patterns) |
| 164 | + var notificationFound = false |
| 165 | + var notification = uiDevice.findObject(By.textContains("Iterable")) |
| 166 | + ?: uiDevice.findObject(By.textContains("iterable")) |
| 167 | + ?: uiDevice.findObject(By.textContains("Test")) |
| 168 | + |
| 169 | + if (notification == null) { |
| 170 | + // Try to find any notification that might be from our app |
| 171 | + val notifications = uiDevice.findObjects(By.res("com.android.systemui:id/notification_stack_scroller")) |
| 172 | + if (notifications.isNotEmpty()) { |
| 173 | + notification = notifications.first() |
| 174 | + notificationFound = true |
| 175 | + } |
| 176 | + } else { |
| 177 | + notificationFound = true |
| 178 | + } |
| 179 | + |
| 180 | + if (!notificationFound || notification == null) { |
| 181 | + Log.e(TAG, "❌ Push notification not found in notification drawer") |
| 182 | + uiDevice.pressBack() // Close notification drawer |
| 183 | + Assert.fail("Push notification not found in notification drawer. Check FCM configuration and campaign setup.") |
| 184 | + return |
| 185 | + } |
| 186 | + |
| 187 | + Log.d(TAG, "✅ Push notification found in notification drawer") |
| 188 | + |
| 189 | + // Step 6: Click on the notification to open it |
| 190 | + Log.d(TAG, "🎯 Step 6: Clicking on push notification...") |
| 191 | + notification.click() |
| 192 | + Thread.sleep(2000) // Wait for app to open |
| 193 | + |
| 194 | + // Step 7: Verify URL handler was called (if notification has action) |
| 195 | + Log.d(TAG, "🎯 Step 7: Verifying URL handler was called after notification click...") |
| 196 | + |
| 197 | + val urlHandlerCalled = waitForUrlHandler(timeoutSeconds = 5) |
| 198 | + if (urlHandlerCalled) { |
| 199 | + // Step 8: Verify the correct URL was handled |
| 200 | + val handledUrl = getLastHandledUrl() |
| 201 | + Log.d(TAG, "🎯 URL handler received: $handledUrl") |
| 202 | + |
| 203 | + Assert.assertNotNull("Handled URL should not be null", handledUrl) |
| 204 | + Log.d(TAG, "✅ URL handler was called with URL: $handledUrl") |
| 205 | + } else { |
| 206 | + Log.d(TAG, "ℹ️ URL handler was not called - notification may not have an action URL") |
| 207 | + // This is acceptable if the notification doesn't have a deep link action |
| 208 | + } |
| 209 | + |
| 210 | + Log.d(TAG, "✅✅✅ Test completed successfully! All steps passed.") |
| 211 | + } |
| 212 | +} |
| 213 | + |
0 commit comments