Skip to content

Commit 749b6c0

Browse files
authored
Merge branch 'master' into SDK-324-push-async-fix
2 parents c385658 + 4321a42 commit 749b6c0

File tree

18 files changed

+1251
-49
lines changed

18 files changed

+1251
-49
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,5 @@ jacoco.exec
7373

7474
IDE
7575
integration-tests/.idea/
76+
.claude/
77+
.mcp.json

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [3.6.4]
6+
### Fixed
7+
- Updated `customPayload` of In-App Messages to be `@Nullable`
8+
9+
### Added
10+
- Made `isIterableDeepLink` method public
11+
512
## [3.6.3]
613
### Fixed
714
- Improved in-app message sizing and positioning calculations for better stability and performance, especially during device orientation changes

integration-tests/src/androidTest/java/com/iterable/integration/tests/BaseIntegrationTest.kt

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ abstract class BaseIntegrationTest {
3838
private val customActionHandlerCalled = AtomicBoolean(false)
3939
private val lastHandledAction = AtomicReference<com.iterable.iterableapi.IterableAction?>(null)
4040
private val lastHandledActionType = AtomicReference<String?>(null)
41+
42+
// Deep link tracking for tests
43+
private val deepLinkReceived = AtomicBoolean(false)
44+
private val lastDeepLinkUrl = AtomicReference<String?>(null)
45+
private val lastDeepLinkPath = AtomicReference<String?>(null)
4146

4247
@Before
4348
open fun setUp() {
@@ -327,4 +332,157 @@ abstract class BaseIntegrationTest {
327332

328333
return syncHappened
329334
}
335+
336+
// ==================== Deep Link Testing Utilities ====================
337+
338+
/**
339+
* Reset deep link tracking
340+
*/
341+
protected fun resetDeepLinkTracking() {
342+
deepLinkReceived.set(false)
343+
lastDeepLinkUrl.set(null)
344+
lastDeepLinkPath.set(null)
345+
}
346+
347+
/**
348+
* Record that a deep link was received
349+
*/
350+
protected fun setDeepLinkReceived(url: String?, path: String?) {
351+
deepLinkReceived.set(true)
352+
lastDeepLinkUrl.set(url)
353+
lastDeepLinkPath.set(path)
354+
Log.d("BaseIntegrationTest", "Deep link received - URL: $url, Path: $path")
355+
}
356+
357+
/**
358+
* Check if a deep link was received
359+
*/
360+
protected fun isDeepLinkReceived(): Boolean = deepLinkReceived.get()
361+
362+
/**
363+
* Get the last received deep link URL
364+
*/
365+
protected fun getLastDeepLinkUrl(): String? = lastDeepLinkUrl.get()
366+
367+
/**
368+
* Get the last received deep link path
369+
*/
370+
protected fun getLastDeepLinkPath(): String? = lastDeepLinkPath.get()
371+
372+
/**
373+
* Wait for deep link to be received
374+
*/
375+
protected fun waitForDeepLink(timeoutSeconds: Long = TIMEOUT_SECONDS): Boolean {
376+
return waitForCondition({
377+
deepLinkReceived.get()
378+
}, timeoutSeconds)
379+
}
380+
381+
/**
382+
* Open a deep link URL using adb shell command.
383+
* This is the Android equivalent of iOS's `xcrun simctl openurl booted <url>`.
384+
*
385+
* The adb command `am start -a android.intent.action.VIEW -d <url>` simulates
386+
* opening a URL from an external source (like a browser or email), which triggers
387+
* the app's intent filters for deep links/app links.
388+
*
389+
* @param url The URL to open (can be https:// for App Links or custom scheme like iterable://)
390+
* @param targetPackage Optional package to target specifically (defaults to integration tests package)
391+
*/
392+
protected fun openDeepLinkViaAdb(url: String, targetPackage: String = "com.iterable.integration.tests") {
393+
Log.d("BaseIntegrationTest", "Opening deep link via adb: $url")
394+
395+
val instrumentation = InstrumentationRegistry.getInstrumentation()
396+
397+
// Use adb shell am start to open the URL
398+
// This mimics opening a link from an external source
399+
val command = "am start -a android.intent.action.VIEW -d \"$url\" -n $targetPackage/.activities.DeepLinkTestActivity"
400+
401+
Log.d("BaseIntegrationTest", "Executing command: $command")
402+
403+
try {
404+
instrumentation.uiAutomation.executeShellCommand(command).use { parcelFileDescriptor ->
405+
// Read output if needed for debugging
406+
val inputStream = android.os.ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor)
407+
val output = inputStream.bufferedReader().readText()
408+
Log.d("BaseIntegrationTest", "Command output: $output")
409+
}
410+
} catch (e: Exception) {
411+
Log.e("BaseIntegrationTest", "Failed to execute adb command", e)
412+
}
413+
}
414+
415+
/**
416+
* Open a deep link URL that should be handled by the app via App Links or intent filters.
417+
* This opens the URL without specifying a target activity, letting Android's intent
418+
* resolution system determine which app/activity should handle it.
419+
*
420+
* @param url The URL to open
421+
*/
422+
protected fun openDeepLinkViaIntent(url: String) {
423+
Log.d("BaseIntegrationTest", "Opening deep link via intent: $url")
424+
425+
val instrumentation = InstrumentationRegistry.getInstrumentation()
426+
427+
// Use am start without specifying a component to let Android resolve the intent
428+
val command = "am start -a android.intent.action.VIEW -d \"$url\""
429+
430+
Log.d("BaseIntegrationTest", "Executing command: $command")
431+
432+
try {
433+
instrumentation.uiAutomation.executeShellCommand(command).use { parcelFileDescriptor ->
434+
val inputStream = android.os.ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor)
435+
val output = inputStream.bufferedReader().readText()
436+
Log.d("BaseIntegrationTest", "Command output: $output")
437+
}
438+
} catch (e: Exception) {
439+
Log.e("BaseIntegrationTest", "Failed to execute adb command", e)
440+
}
441+
}
442+
443+
/**
444+
* Check if a specific app is in foreground
445+
*
446+
* @param packageName The package name to check
447+
* @return true if the app is in foreground
448+
*/
449+
protected fun isAppInForeground(packageName: String): Boolean {
450+
val instrumentation = InstrumentationRegistry.getInstrumentation()
451+
val command = "dumpsys activity activities | grep mResumedActivity"
452+
453+
return try {
454+
instrumentation.uiAutomation.executeShellCommand(command).use { parcelFileDescriptor ->
455+
val inputStream = android.os.ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor)
456+
val output = inputStream.bufferedReader().readText()
457+
Log.d("BaseIntegrationTest", "Foreground activity check: $output")
458+
output.contains(packageName)
459+
}
460+
} catch (e: Exception) {
461+
Log.e("BaseIntegrationTest", "Failed to check foreground app", e)
462+
false
463+
}
464+
}
465+
466+
/**
467+
* Open Chrome browser and navigate to a URL.
468+
* Used for testing that non-app links open in browser instead of the app.
469+
*
470+
* @param url The URL to open in Chrome
471+
*/
472+
protected fun openUrlInBrowser(url: String) {
473+
Log.d("BaseIntegrationTest", "Opening URL in browser: $url")
474+
475+
val instrumentation = InstrumentationRegistry.getInstrumentation()
476+
val command = "am start -a android.intent.action.VIEW -d \"$url\" -n com.android.chrome/com.google.android.apps.chrome.Main"
477+
478+
try {
479+
instrumentation.uiAutomation.executeShellCommand(command).use { parcelFileDescriptor ->
480+
val inputStream = android.os.ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor)
481+
val output = inputStream.bufferedReader().readText()
482+
Log.d("BaseIntegrationTest", "Browser command output: $output")
483+
}
484+
} catch (e: Exception) {
485+
Log.e("BaseIntegrationTest", "Failed to open URL in browser", e)
486+
}
487+
}
330488
}

0 commit comments

Comments
 (0)