|
| 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.runner.lifecycle.ActivityLifecycleMonitorRegistry |
| 10 | +import androidx.test.runner.lifecycle.Stage |
| 11 | +import androidx.test.uiautomator.UiDevice |
| 12 | +import androidx.test.uiautomator.UiSelector |
| 13 | +import androidx.test.uiautomator.By |
| 14 | +import com.iterable.iterableapi.IterableApi |
| 15 | +import com.iterable.iterableapi.IterableEmbeddedMessage |
| 16 | +import com.iterable.integration.tests.activities.EmbeddedMessageTestActivity |
| 17 | +import com.iterable.iterableapi.ui.embedded.IterableEmbeddedView |
| 18 | +import com.iterable.iterableapi.ui.embedded.IterableEmbeddedViewType |
| 19 | +import org.awaitility.Awaitility |
| 20 | +import org.json.JSONObject |
| 21 | +import org.junit.After |
| 22 | +import org.junit.Assert |
| 23 | +import org.junit.Before |
| 24 | +import org.junit.Test |
| 25 | +import org.junit.runner.RunWith |
| 26 | +import java.util.concurrent.TimeUnit |
| 27 | + |
| 28 | +@RunWith(AndroidJUnit4::class) |
| 29 | +class EmbeddedMessageIntegrationTest : BaseIntegrationTest() { |
| 30 | + |
| 31 | + companion object { |
| 32 | + private const val TAG = "EmbeddedMessageIntegrationTest" |
| 33 | + private const val TEST_PLACEMENT_ID = TestConstants.TEST_EMBEDDED_PLACEMENT_ID |
| 34 | + } |
| 35 | + |
| 36 | + private lateinit var uiDevice: UiDevice |
| 37 | + private lateinit var mainActivityScenario: ActivityScenario<MainActivity> |
| 38 | + |
| 39 | + @Before |
| 40 | + override fun setUp() { |
| 41 | + Log.d(TAG, "🔧 Test setup starting...") |
| 42 | + |
| 43 | + uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) |
| 44 | + |
| 45 | + // Call super.setUp() to initialize SDK with BaseIntegrationTest's config |
| 46 | + // This sets test mode flag and initializes SDK with test handlers (including urlHandler) |
| 47 | + super.setUp() |
| 48 | + |
| 49 | + Log.d(TAG, "🔧 Base setup complete, SDK initialized with test handlers") |
| 50 | + |
| 51 | + // Disable in-app auto display and clear existing messages BEFORE launching app |
| 52 | + // This prevents in-app messages from obscuring the embedded message test screen |
| 53 | + Log.d(TAG, "🔧 Disabling in-app auto display and clearing existing messages...") |
| 54 | + IterableApi.getInstance().inAppManager.setAutoDisplayPaused(true) |
| 55 | + Log.d(TAG, "✅ In-app auto display paused") |
| 56 | + |
| 57 | + // Clear all existing in-app messages |
| 58 | + IterableApi.getInstance().inAppManager.messages.forEach { |
| 59 | + Log.d(TAG, "Clearing existing in-app message: ${it.messageId}") |
| 60 | + IterableApi.getInstance().inAppManager.removeMessage(it) |
| 61 | + } |
| 62 | + Log.d(TAG, "✅ All in-app messages cleared") |
| 63 | + |
| 64 | + Log.d(TAG, "🔧 MainActivity will skip initialization due to test mode flag") |
| 65 | + |
| 66 | + // Now launch the app flow with custom handlers already configured |
| 67 | + launchAppAndNavigateToEmbeddedTesting() |
| 68 | + } |
| 69 | + |
| 70 | + @After |
| 71 | + override fun tearDown() { |
| 72 | + super.tearDown() |
| 73 | + } |
| 74 | + |
| 75 | + private fun launchAppAndNavigateToEmbeddedTesting() { |
| 76 | + // Step 1: Launch MainActivity (the home page) |
| 77 | + Log.d(TAG, "🔧 Step 1: Launching MainActivity...") |
| 78 | + val mainIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, MainActivity::class.java) |
| 79 | + mainActivityScenario = ActivityScenario.launch(mainIntent) |
| 80 | + |
| 81 | + // Wait for MainActivity to be ready |
| 82 | + Awaitility.await() |
| 83 | + .atMost(5, TimeUnit.SECONDS) |
| 84 | + .pollInterval(500, TimeUnit.MILLISECONDS) |
| 85 | + .until { |
| 86 | + val state = mainActivityScenario.state |
| 87 | + Log.d(TAG, "🔧 MainActivity state: $state") |
| 88 | + state == Lifecycle.State.RESUMED |
| 89 | + } |
| 90 | + |
| 91 | + Log.d(TAG, "🔧 MainActivity is ready!") |
| 92 | + |
| 93 | + // Step 2: Click the "Embedded Messages" button to navigate to EmbeddedMessageTestActivity |
| 94 | + Log.d(TAG, "🔧 Step 2: Clicking 'Embedded Messages' button...") |
| 95 | + val embeddedButton = uiDevice.findObject(UiSelector().resourceId("com.iterable.integration.tests:id/btnEmbeddedMessages")) |
| 96 | + if (embeddedButton.exists()) { |
| 97 | + embeddedButton.click() |
| 98 | + Log.d(TAG, "🔧 Clicked Embedded Messages button successfully") |
| 99 | + } else { |
| 100 | + Log.e(TAG, "❌ Embedded Messages button not found!") |
| 101 | + Assert.fail("Embedded Messages button not found in MainActivity") |
| 102 | + } |
| 103 | + |
| 104 | + // Step 3: Wait for EmbeddedMessageTestActivity to load |
| 105 | + Log.d(TAG, "🔧 Step 3: Waiting for EmbeddedMessageTestActivity to load...") |
| 106 | + Thread.sleep(2000) // Give time for navigation |
| 107 | + |
| 108 | + Log.d(TAG, "🔧 App navigation complete: Now on EmbeddedMessageTestActivity!") |
| 109 | + } |
| 110 | + |
| 111 | + @Test |
| 112 | + fun testEmbeddedMessageMVP() { |
| 113 | + // Step 1: Ensure user is signed in |
| 114 | + Log.d(TAG, "📧 Step 1: Ensuring user is signed in...") |
| 115 | + val userSignedIn = testUtils.ensureUserSignedIn(TestConstants.TEST_USER_EMAIL) |
| 116 | + Assert.assertTrue("User should be signed in", userSignedIn) |
| 117 | + Log.d(TAG, "✅ User signed in successfully: ${TestConstants.TEST_USER_EMAIL}") |
| 118 | + |
| 119 | + // Step 2: Preliminary check - verify view is ready with placement ID |
| 120 | + Log.d(TAG, "🔍 Step 2: Checking view readiness with placement ID...") |
| 121 | + var viewReady = false |
| 122 | + InstrumentationRegistry.getInstrumentation().runOnMainSync { |
| 123 | + val activity = ActivityLifecycleMonitorRegistry.getInstance() |
| 124 | + .getActivitiesInStage(Stage.RESUMED) |
| 125 | + .firstOrNull() as? EmbeddedMessageTestActivity |
| 126 | + |
| 127 | + activity?.let { |
| 128 | + val fragmentContainer = it.findViewById<androidx.fragment.app.FragmentContainerView>(R.id.embedded_message_container) |
| 129 | + viewReady = fragmentContainer != null |
| 130 | + if (viewReady) { |
| 131 | + Log.d(TAG, "✅ View is ready with placementID - $TEST_PLACEMENT_ID") |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + Assert.assertTrue("FragmentContainerView should exist in EmbeddedMessageTestActivity", viewReady) |
| 136 | + |
| 137 | + // Step 3: Update user properties to make user eligible |
| 138 | + Log.d(TAG, "📝 Step 3: Updating user properties (isPremium = true)...") |
| 139 | + val dataFields = JSONObject().apply { |
| 140 | + put("isPremium", true) |
| 141 | + } |
| 142 | + IterableApi.getInstance().updateUser(dataFields) |
| 143 | + Log.d(TAG, "✅ User properties updated") |
| 144 | + |
| 145 | + // Step 4: Wait 5 seconds for backend to process and make user eligible |
| 146 | + Log.d(TAG, "⏳ Step 4: Waiting 5 seconds for backend to process user update...") |
| 147 | + Thread.sleep(3000) |
| 148 | + |
| 149 | + // Step 5: Manually sync embedded messages |
| 150 | + Log.d(TAG, "🔄 Step 5: Syncing embedded messages...") |
| 151 | + IterableApi.getInstance().embeddedManager.syncMessages() |
| 152 | + |
| 153 | + // Wait for sync to complete |
| 154 | + Thread.sleep(2000) |
| 155 | + |
| 156 | + // Step 6: Get placement IDs and verify expected placement ID exists |
| 157 | + Log.d(TAG, "🔍 Step 6: Getting placement IDs...") |
| 158 | + val placementIds = IterableApi.getInstance().embeddedManager.getPlacementIds() |
| 159 | + Log.d(TAG, "📋 Found placement IDs: $placementIds") |
| 160 | + |
| 161 | + Assert.assertTrue( |
| 162 | + "Placement ID $TEST_PLACEMENT_ID should exist, but found: $placementIds", |
| 163 | + placementIds.contains(TEST_PLACEMENT_ID) |
| 164 | + ) |
| 165 | + Log.d(TAG, "✅ Placement ID $TEST_PLACEMENT_ID found") |
| 166 | + |
| 167 | + // Step 7: Get messages for the placement ID |
| 168 | + Log.d(TAG, "📨 Step 7: Getting messages for placement ID $TEST_PLACEMENT_ID...") |
| 169 | + val messages = IterableApi.getInstance().embeddedManager.getMessages(TEST_PLACEMENT_ID) |
| 170 | + Assert.assertTrue("Should have at least 1 message for placement $TEST_PLACEMENT_ID", messages!!.isNotEmpty()) |
| 171 | + |
| 172 | + val message = messages.first() |
| 173 | + Log.d(TAG, "✅ Found message: ${message.metadata.messageId}") |
| 174 | + |
| 175 | + // Step 8: Display message using IterableEmbeddedView |
| 176 | + Log.d(TAG, "🎨 Step 8: Displaying message using IterableEmbeddedView...") |
| 177 | + |
| 178 | + InstrumentationRegistry.getInstrumentation().runOnMainSync { |
| 179 | + val activity = ActivityLifecycleMonitorRegistry.getInstance() |
| 180 | + .getActivitiesInStage(Stage.RESUMED) |
| 181 | + .firstOrNull() as? EmbeddedMessageTestActivity |
| 182 | + |
| 183 | + if (activity != null) { |
| 184 | + val fragment = IterableEmbeddedView(IterableEmbeddedViewType.BANNER, message, null) |
| 185 | + activity.supportFragmentManager.beginTransaction() |
| 186 | + .replace(R.id.embedded_message_container, fragment) |
| 187 | + .commitNow() |
| 188 | + Log.d(TAG, "✅ Fragment added to FragmentManager") |
| 189 | + } else { |
| 190 | + Assert.fail("EmbeddedMessageTestActivity not found in RESUMED stage") |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + // Wait for fragment to be displayed |
| 195 | + Thread.sleep(1000) |
| 196 | + |
| 197 | + // Step 9: Verify display - check fragment exists |
| 198 | + Log.d(TAG, "✅ Step 9: Verifying embedded message is displayed...") |
| 199 | + var isEmbeddedFragmentDisplayed = false |
| 200 | + |
| 201 | + InstrumentationRegistry.getInstrumentation().runOnMainSync { |
| 202 | + val activity = ActivityLifecycleMonitorRegistry.getInstance() |
| 203 | + .getActivitiesInStage(Stage.RESUMED) |
| 204 | + .firstOrNull() as? EmbeddedMessageTestActivity |
| 205 | + |
| 206 | + activity?.let { act -> |
| 207 | + val fragmentManager = act.supportFragmentManager |
| 208 | + fragmentManager.fragments.forEach { fragment -> |
| 209 | + if (fragment is IterableEmbeddedView) { |
| 210 | + isEmbeddedFragmentDisplayed = true |
| 211 | + Log.d(TAG, "✅ Found IterableEmbeddedView fragment") |
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + Assert.assertTrue( |
| 218 | + "IterableEmbeddedView fragment should be displayed", |
| 219 | + isEmbeddedFragmentDisplayed |
| 220 | + ) |
| 221 | + |
| 222 | + Log.d(TAG, "✅ Embedded message is displayed, now interacting with button...") |
| 223 | + |
| 224 | + // Step 10: Interact with button - find and click first button |
| 225 | + Log.d(TAG, "🎯 Step 10: Clicking button in the embedded message...") |
| 226 | + |
| 227 | + // Try to find button by resource ID first |
| 228 | + val button = uiDevice.findObject(UiSelector().resourceId("com.iterable.iterableapi.ui:id/embedded_message_first_button")) |
| 229 | + |
| 230 | + if (button.exists()) { |
| 231 | + button.click() |
| 232 | + Log.d(TAG, "✅ Clicked embedded message button") |
| 233 | + } else { |
| 234 | + // Try to find by button text if available |
| 235 | + val buttonText = message.elements?.buttons?.firstOrNull()?.title |
| 236 | + if (buttonText != null) { |
| 237 | + val buttonByText = uiDevice.findObject(By.text(buttonText)) |
| 238 | + if (buttonByText != null) { |
| 239 | + buttonByText.click() |
| 240 | + Log.d(TAG, "✅ Clicked embedded message button by text: $buttonText") |
| 241 | + } else { |
| 242 | + Assert.fail("Button not found in the embedded message (tried resource ID and text: $buttonText)") |
| 243 | + } |
| 244 | + } else { |
| 245 | + Assert.fail("Button not found in the embedded message (tried resource ID, but no button text available)") |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + // Step 11: Verify URL handler was called |
| 250 | + Log.d(TAG, "🎯 Step 11: Verifying URL handler was called after button click...") |
| 251 | + |
| 252 | + val urlHandlerCalled = waitForUrlHandler(timeoutSeconds = 3) |
| 253 | + Assert.assertTrue( |
| 254 | + "URL handler should have been called after clicking the button", |
| 255 | + urlHandlerCalled |
| 256 | + ) |
| 257 | + |
| 258 | + // Step 12: Verify the correct URL was handled |
| 259 | + val handledUrl = getLastHandledUrl() |
| 260 | + Log.d(TAG, "🎯 URL handler received: $handledUrl") |
| 261 | + |
| 262 | + Assert.assertNotNull("Handled URL should not be null", handledUrl) |
| 263 | + Log.d(TAG, "✅ URL handler was called with URL: $handledUrl") |
| 264 | + |
| 265 | + Log.d(TAG, "✅✅✅ Test completed successfully! All steps passed.") |
| 266 | + } |
| 267 | +} |
| 268 | + |
0 commit comments