Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/badges/branches.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .github/badges/jacoco.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This is the Superwall Android SDK - an open-source framework for remotely config
./gradlew :app:connectedCheck

# Run integration tests with screenshot recording
./gradlew :app:connectedCheck -Pdropshots.record
./gradlew recordDebugAndroidTestScreenshots

# Build and publish SDK locally
./gradlew publishToMavenLocal
Expand Down
2 changes: 1 addition & 1 deletion app/src/androidTest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ If there are failing tests, the screenshots will be saved under `app/build/outpu

### Recording the screenshots

To record the screenshots, run `./gradlew :app:connectedCheck -Pdropshots.record` from the root of the project.
To record the screenshots, run `./gradlew recordDebugAndroidTestScreenshots` from the root of the project.
This will record new screenshots on your current device.

### Viewing the recorded screenshots
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ class AlternativeSetupTest {

@Before
fun grantPhonePermission() {
// Shouldn't be needed on > API 29, but dropshots is occasionally unable to write to external storage without this.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().uiAutomation.executeShellCommand(
(
"pm grant " + getTargetContext().packageName +
" android.permission.WRITE_EXTERNAL_STORAGE"
),
getInstrumentation().uiAutomation.grantRuntimePermission(
getTargetContext().packageName,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ class FlowScreenshotTestExecutor {

@Before
fun grantPhonePermission() {
// Shouldn't be needed on > API 29, but dropshots is occasionally unable to write to external storage without this.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().uiAutomation.executeShellCommand(
(
"pm grant " + getTargetContext().packageName +
" android.permission.WRITE_EXTERNAL_STORAGE"
),
getInstrumentation().uiAutomation.grantRuntimePermission(
getTargetContext().packageName,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@ class NoConnectionTestExecutor {

@Before
fun grantPhonePermission() {
// Shouldn't be needed on > API 29, but dropshots is occasionally unable to write to external storage without this.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().uiAutomation.executeShellCommand(
(
"pm grant " + getTargetContext().packageName +
" android.permission.WRITE_EXTERNAL_STORAGE"
),
getInstrumentation().uiAutomation.grantRuntimePermission(
getTargetContext().packageName,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ class PresentationRuleTests {

@Before
fun grantPhonePermission() {
// Shouldn't be needed on > API 29, but dropshots is occasionally unable to write to external storage without this.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().uiAutomation.executeShellCommand(
(
"pm grant " + getTargetContext().packageName +
" android.permission.WRITE_EXTERNAL_STORAGE"
),
getInstrumentation().uiAutomation.grantRuntimePermission(
getTargetContext().packageName,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ class SimpleScreenshotTestExecutor {

@Before
fun grantPhonePermission() {
// Shouldn't be needed on > API 29, but dropshots is occasionally unable to write to external storage without this.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().uiAutomation.executeShellCommand(
(
"pm grant " + getTargetContext().packageName +
" android.permission.WRITE_EXTERNAL_STORAGE"
),
getInstrumentation().uiAutomation.grantRuntimePermission(
getTargetContext().packageName,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.example.superapp.utils

import android.os.Environment
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.superwall.superapp.test.UITestInfo
import org.json.JSONArray
import org.json.JSONObject
Expand All @@ -15,10 +16,11 @@ private const val TAG = "EventTimeline"
/**
* Writes the event timeline for a [UITestInfo] to a JSON file on device storage.
*
* Output directory: /sdcard/Download/superwall-event-timelines/
* Output directory: /sdcard/Android/data/<pkg>/files/Download/superwall-event-timelines/
* (app-scoped external storage — no runtime permission required on API 30+).
*
* Pull results with:
* adb pull /sdcard/Download/superwall-event-timelines/ app/build/outputs/event-timelines/
* adb pull /sdcard/Android/data/<pkg>/files/Download/superwall-event-timelines/ app/build/outputs/event-timelines/
*/
fun writeTimelineToFile(
testInfo: UITestInfo,
Expand All @@ -28,10 +30,14 @@ fun writeTimelineToFile(
val timeline = testInfo.timeline
if (timeline.allEvents().isEmpty()) return

val dir = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
TIMELINE_DIR,
)
val context = InstrumentationRegistry.getInstrumentation().targetContext
val externalDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
if (externalDir == null) {
Log.w(TAG, "External storage unavailable; writing timelines to internal storage. " +
"Use 'adb shell run-as ${context.packageName}' to access files.")
}
val baseDir = externalDir ?: context.filesDir
val dir = File(baseDir, TIMELINE_DIR)
dir.mkdirs()

val fileName = "${testClassName}_${testMethodName}.json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ class ScreenshotTestFlow(
) {
var steps: LinkedList<Step> = LinkedList()

private val device = "${android.os.Build.MANUFACTURER}_${android.os.Build.MODEL}"
// Pinned to a stable identifier so snapshot filenames don't shift when the
// emulator system image renames Build.MODEL (e.g. sdk_gphone_arm64 vs sdk_gphone64_arm64).
private val device = "Google_sdk_gphone64_arm64"

@ScreenshotTestDSL
fun step(
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class MainApplication :
logging.level = LogLevel.debug
paywalls =
PaywallOptions().apply {
shouldPreload = false
shouldPreload = true
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ object UITestHandler {
"${it.id}"
}.joinToString(separator = ", "),
)
Superwall.instance.register(placement = "show_if_web_failed")
Superwall.instance.setUserAttributes(mapOf("is_user_eligible_for_dd_offer" to true))
Superwall.instance.register(placement = "swtest")
Superwall.instance.setUserAttributes(mapOf("is_user_eligible_for_dd_offer" to null))
},
),
UITestInfo(
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ threetenbp_version = "1.6.8"
uiautomator_version = "2.3.0"
workRuntimeKtx_version = "2.9.0"
serialization_version = "1.6.0"
dropshot_version = "0.4.2"
dropshot_version = "0.5.0"
ksp = "2.0.21-1.0.27"
install_referrer = "2.2"
publisher_version = "0.33.0"
Expand Down
10 changes: 8 additions & 2 deletions superwall/src/main/java/com/superwall/sdk/SdkContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.superwall.sdk
import com.superwall.sdk.config.ConfigManager
import com.superwall.sdk.misc.awaitFirstValidConfig
import com.superwall.sdk.models.config.Config
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.time.Duration.Companion.seconds

/**
* Cross-slice bridge used by the identity actor to call into other managers.
Expand All @@ -25,9 +27,13 @@ class SdkContextImpl(
}

override suspend fun fetchAssignments() {
configManager().getAssignments()
withTimeoutOrNull(30.seconds) {
configManager().getAssignments()
}
}

override suspend fun awaitConfig(): Config? =
configManager().configState.awaitFirstValidConfig()
withTimeoutOrNull(30.seconds) {
configManager().configState.awaitFirstValidConfig()
}
}
Loading
Loading