Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,30 @@ abstract class BaseIntegrationTest {
}
.build()

IterableApi.initialize(context, BuildConfig.ITERABLE_API_KEY, config)
// Use background initialization to prevent ANRs on slow CI emulators
Log.d("BaseIntegrationTest", "Starting background SDK initialization...")
val initLatch = CountDownLatch(1)
IterableApi.initializeInBackground(context, BuildConfig.ITERABLE_API_KEY, config) {
Log.d("BaseIntegrationTest", "SDK initialization completed in background")
initLatch.countDown()
}

// Wait for initialization to complete (with generous timeout for CI)
val initialized = initLatch.await(30, TimeUnit.SECONDS)
if (!initialized) {
Log.e("BaseIntegrationTest", "SDK initialization timed out after 30 seconds!")
throw IllegalStateException("SDK initialization timed out")
}

// Set the user email for integration testing
val userEmail = TestConstants.TEST_USER_EMAIL
IterableApi.getInstance().setEmail(userEmail)
Log.d("BaseIntegrationTest", "User email set to: $userEmail")
Log.d("BaseIntegrationTest", "Iterable SDK initialized with email: $userEmail")

// Add stabilization delay for CI environments
Thread.sleep(1000)
Log.d("BaseIntegrationTest", "SDK initialization stabilization complete")
}

private fun setupTestEnvironment() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,25 @@ package com.iterable.integration.tests

import android.content.Intent
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.By
import com.iterable.iterableapi.IterableApi
import com.iterable.iterableapi.IterableEmbeddedMessage
import androidx.test.uiautomator.Until
import com.iterable.integration.tests.activities.EmbeddedMessageTestActivity
import com.iterable.iterableapi.IterableApi
import com.iterable.iterableapi.ui.embedded.IterableEmbeddedView
import com.iterable.iterableapi.ui.embedded.IterableEmbeddedViewType
import org.awaitility.Awaitility
import org.json.JSONObject
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit

@RunWith(AndroidJUnit4::class)
class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {
Expand All @@ -48,6 +45,9 @@ class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {

Log.d(TAG, "🔧 Base setup complete, SDK initialized with test handlers")

// Add small delay to ensure SDK is fully ready after background initialization
Thread.sleep(500)

// Disable in-app auto display and clear existing messages BEFORE launching app
// This prevents in-app messages from obscuring the embedded message test screen
Log.d(TAG, "🔧 Disabling in-app auto display and clearing existing messages...")
Expand Down Expand Up @@ -78,28 +78,25 @@ class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {
val mainIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, MainActivity::class.java)
mainActivityScenario = ActivityScenario.launch(mainIntent)

// Wait for MainActivity to be ready
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.pollInterval(500, TimeUnit.MILLISECONDS)
.until {
val state = mainActivityScenario.state
Log.d(TAG, "🔧 MainActivity state: $state")
state == Lifecycle.State.RESUMED
}

Log.d(TAG, "🔧 MainActivity is ready!")
Log.d(TAG, "🔧 MainActivity launched")

// Step 2: Click the "Embedded Messages" button to navigate to EmbeddedMessageTestActivity
Log.d(TAG, "🔧 Step 2: Clicking 'Embedded Messages' button...")
val embeddedButton = uiDevice.findObject(UiSelector().resourceId("com.iterable.integration.tests:id/btnEmbeddedMessages"))
if (embeddedButton.exists()) {
embeddedButton.click()
Log.d(TAG, "🔧 Clicked Embedded Messages button successfully")
} else {
Log.e(TAG, "❌ Embedded Messages button not found!")
Assert.fail("Embedded Messages button not found in MainActivity")
Log.d(TAG, "🔧 Step 2: Waiting for and clicking 'Embedded Messages' button...")

// Use UiDevice.wait() with generous timeout for slow emulators
val embeddedButton = uiDevice.wait(
Until.findObject(By.res("com.iterable.integration.tests", "btnEmbeddedMessages")),
10000 // 10 second timeout for slow CI
)

if (embeddedButton == null) {
Log.e(TAG, "❌ Embedded Messages button not found after waiting 10 seconds!")
Log.e(TAG, "Current activity: " + uiDevice.currentPackageName)
}
Assert.assertNotNull("Embedded Messages button should be found", embeddedButton)
embeddedButton.click()

Log.d(TAG, "🔧 Clicked Embedded Messages button successfully")

// Step 3: Wait for EmbeddedMessageTestActivity to load
Log.d(TAG, "🔧 Step 3: Waiting for EmbeddedMessageTestActivity to load...")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.test.runner.lifecycle.Stage
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.iterable.iterableapi.IterableApi
import com.iterable.iterableapi.IterableInAppMessage
import com.iterable.iterableapi.IterableInAppLocation
Expand Down Expand Up @@ -55,6 +56,10 @@ class InAppMessageIntegrationTest : BaseIntegrationTest() {
super.setUp()

Log.d(TAG, "🔧 Base setup complete, SDK initialized with test handlers")

// Add small delay to ensure SDK is fully ready after background initialization
Thread.sleep(500)

Log.d(TAG, "🔧 MainActivity will skip initialization due to test mode flag")

// Now launch the app flow with custom handlers already configured
Expand Down Expand Up @@ -87,15 +92,20 @@ class InAppMessageIntegrationTest : BaseIntegrationTest() {
Log.d(TAG, "🔧 MainActivity is ready!")

// Step 2: Click the "In-App Messages" button to navigate to InAppMessageTestActivity
Log.d(TAG, "🔧 Step 2: Clicking 'In-App Messages' button...")
val inAppButton = uiDevice.findObject(UiSelector().resourceId("com.iterable.integration.tests:id/btnInAppMessages"))
if (inAppButton.exists()) {
Log.d(TAG, "🔧 Step 2: Waiting for and clicking 'In-App Messages' button...")

// Use UiDevice.wait() with generous timeout for slow emulators
val inAppButton = uiDevice.wait(
Until.findObject(By.res("com.iterable.integration.tests", "btnInAppMessages")),
10000 // 10 second timeout for slow CI
)

if (inAppButton != null) {
inAppButton.click()
Log.d(TAG, "🔧 Clicked In-App Messages button successfully")
} else {
//Take screenshot for debugging
// uiDevice.takeScreenshot(File("/sdcard/Download/InAppButtonNotFound.png"))
Log.e(TAG, "❌ In-App Messages button not found!")
Log.e(TAG, "❌ In-App Messages button not found after waiting 10 seconds!")
Log.e(TAG, "Current activity: " + uiDevice.currentPackageName)
Assert.fail("In-App Messages button not found in MainActivity")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.iterable.iterableapi.IterableApi
import com.iterable.integration.tests.activities.PushNotificationTestActivity
import org.awaitility.Awaitility
Expand All @@ -36,6 +37,9 @@ class PushNotificationIntegrationTest : BaseIntegrationTest() {
uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
super.setUp()

// Add small delay to ensure SDK is fully ready after background initialization
Thread.sleep(500)

IterableApi.getInstance().inAppManager.setAutoDisplayPaused(true)
IterableApi.getInstance().inAppManager.messages.forEach {
IterableApi.getInstance().inAppManager.removeMessage(it)
Expand All @@ -61,11 +65,20 @@ class PushNotificationIntegrationTest : BaseIntegrationTest() {
mainActivityScenario.state == Lifecycle.State.RESUMED
}

val pushButton = uiDevice.findObject(UiSelector().resourceId("com.iterable.integration.tests:id/btnPushNotifications"))
if (!pushButton.exists()) {
Log.d(TAG, "Waiting for Push Notifications button to appear...")
val pushButton = uiDevice.wait(
Until.findObject(By.res("com.iterable.integration.tests", "btnPushNotifications")),
10000 // 10 second timeout for slow CI
)

if (pushButton == null) {
Log.e(TAG, "Push Notifications button not found after waiting 10 seconds!")
Log.e(TAG, "Current activity: " + uiDevice.currentPackageName)
Assert.fail("Push Notifications button not found in MainActivity")
}

pushButton.click()
Log.d(TAG, "Clicked Push Notifications button successfully")
Thread.sleep(2000)
}

Expand Down Expand Up @@ -184,12 +197,19 @@ class PushNotificationIntegrationTest : BaseIntegrationTest() {
Thread.sleep(1000)

// Try to find and click the Push Notifications button in MainActivity
val pushButton = uiDevice.findObject(UiSelector().resourceId("com.iterable.integration.tests:id/btnPushNotifications"))
if (pushButton.exists()) {
Log.d(TAG, "Navigating back to Push Notification Test Activity...")
val pushButton = uiDevice.wait(
Until.findObject(By.res("com.iterable.integration.tests", "btnPushNotifications")),
5000 // 5 second timeout
)

if (pushButton != null) {
pushButton.click()
Log.d(TAG, "Clicked Push Notifications button")
Thread.sleep(2000) // Wait for navigation
} else {
// If button not found, try launching the activity directly
Log.d(TAG, "Button not found, launching activity directly")
val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, PushNotificationTestActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
InstrumentationRegistry.getInstrumentation().targetContext.startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ object TestConstants {
// Test placement IDs
const val TEST_EMBEDDED_PLACEMENT_ID = 2157L

// Test timeouts
const val TIMEOUT_SECONDS = 5L
const val POLL_INTERVAL_SECONDS = 1L
// Test timeouts (increased for CI stability)
const val TIMEOUT_SECONDS = 15L // Increased from 5s for slower CI emulators
const val POLL_INTERVAL_SECONDS = 2L // Increased from 1s for better stability
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.espresso.intent.Intents.intended;
Expand All @@ -22,11 +21,8 @@
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static org.hamcrest.CoreMatchers.allOf;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
public class IterableActionRunnerTest {
Expand Down Expand Up @@ -57,9 +53,14 @@ public void testOpenUrlAction() throws Exception {

@Test
public void testUrlHandlingOverride() throws Exception {
IterableUrlHandler urlHandlerMock = mock(IterableUrlHandler.class);
when(urlHandlerMock.handleIterableURL(any(Uri.class), any(IterableActionContext.class))).thenReturn(true);
IterableTestUtils.initIterableApi(new IterableConfig.Builder().setUrlHandler(urlHandlerMock).build());
// Use a simple implementation instead of mock for API 36+ compatibility
IterableUrlHandler urlHandler = new IterableUrlHandler() {
@Override
public boolean handleIterableURL(Uri uri, IterableActionContext context) {
return true;
}
};
IterableTestUtils.initIterableApi(new IterableConfig.Builder().setUrlHandler(urlHandler).build());

JSONObject actionData = new JSONObject();
actionData.put("type", "openUrl");
Expand All @@ -73,17 +74,31 @@ public void testUrlHandlingOverride() throws Exception {

@Test
public void testCustomAction() throws Exception {
IterableCustomActionHandler customActionHandlerMock = mock(IterableCustomActionHandler.class);
IterableTestUtils.initIterableApi(new IterableConfig.Builder().setCustomActionHandler(customActionHandlerMock).build());
// Track if the custom action handler was called (for API 36+ compatibility)
final boolean[] handlerCalled = {false};
final IterableAction[] capturedAction = {null};
final IterableActionContext[] capturedContext = {null};

IterableCustomActionHandler customActionHandler = (action, actionContext) -> {
handlerCalled[0] = true;
capturedAction[0] = action;
capturedContext[0] = actionContext;
return false;
};

IterableTestUtils.initIterableApi(new IterableConfig.Builder().setCustomActionHandler(customActionHandler).build());

JSONObject actionData = new JSONObject();
actionData.put("type", "customActionName");
IterableAction action = IterableAction.from(actionData);
IterableActionRunner.executeAction(getApplicationContext(), action, IterableActionSource.PUSH);

ArgumentCaptor<IterableActionContext> contextCaptor = ArgumentCaptor.forClass(IterableActionContext.class);
verify(customActionHandlerMock).handleIterableCustomAction(eq(action), contextCaptor.capture());
assertEquals(IterableActionSource.PUSH, contextCaptor.getValue().source);
// Verify the handler was called with correct parameters
assertTrue("Custom action handler should have been called", handlerCalled[0]);
assertNotNull("Action should not be null", capturedAction[0]);
assertEquals("Action type should match", "customActionName", capturedAction[0].getType());
assertNotNull("Context should not be null", capturedContext[0]);
assertEquals("Source should be PUSH", IterableActionSource.PUSH, capturedContext[0].source);
IterableTestUtils.initIterableApi(null);
}
}
Loading
Loading