diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
new file mode 100644
index 00000000..10bd8cf8
--- /dev/null
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Open_Screenshot_Report___internalDebug.xml b/.idea/runConfigurations/Open_Screenshot_Report___internalDebug.xml
new file mode 100644
index 00000000..d511e6cf
--- /dev/null
+++ b/.idea/runConfigurations/Open_Screenshot_Report___internalDebug.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/_app___testInternalDebugUnitTest.xml b/.idea/runConfigurations/_app___testInternalDebugUnitTest.xml
index d1701624..d75b4a10 100644
--- a/.idea/runConfigurations/_app___testInternalDebugUnitTest.xml
+++ b/.idea/runConfigurations/_app___testInternalDebugUnitTest.xml
@@ -4,7 +4,7 @@
-
+
@@ -12,6 +12,8 @@
+
+
diff --git a/.idea/runConfigurations/executeScreenshotTests___internalDebug.xml b/.idea/runConfigurations/executeScreenshotTests___internalDebug.xml
new file mode 100644
index 00000000..883a8e89
--- /dev/null
+++ b/.idea/runConfigurations/executeScreenshotTests___internalDebug.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/recordScreenshotTests___internalDebug.xml b/.idea/runConfigurations/recordScreenshotTests___internalDebug.xml
new file mode 100644
index 00000000..38a9003d
--- /dev/null
+++ b/.idea/runConfigurations/recordScreenshotTests___internalDebug.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ef404fa5..8a2f62b2 100644
--- a/README.md
+++ b/README.md
@@ -80,4 +80,9 @@ This project uses the `generateNavDrawerItems` method in the `ComposeActivity` t
* cancelOnTouchOutside (Boolean)
## LaunchPad Development
-Uncomment mavenLocal in main build.gradle.kts to test local library changes.
\ No newline at end of file
+Uncomment mavenLocal in main build.gradle.kts to test local library changes.
+
+## Compose UI Testing x Karumi Shots
+* Requires API 30+
+* Must re-install app prior to running any tests to get updated Manifest
+* Complete setup can be found here
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f9ba6d1f..393db8dd 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
kotlin(Config.ApplyPlugins.Kotlin.ANDROID)
id(Config.ApplyPlugins.KSP)
id(Config.ApplyPlugins.PARCELIZE)
+ id(Config.ApplyPlugins.KARUMI_SHOT_TESTING)
}
extra.set("jacocoCoverageThreshold", 0.30.toBigDecimal()) // module specific code coverage verification threshold
@@ -34,7 +35,10 @@ android {
targetSdk = Config.AndroidSdkVersions.TARGET_SDK
versionCode = BuildInfoManager.APP_VERSION.versionCode
versionName = BuildInfoManager.APP_VERSION.versionName
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ // testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ testInstrumentationRunner = "com.karumi.shot.ShotTestRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
@@ -127,6 +131,10 @@ android {
testOptions {
unitTests.isIncludeAndroidResources = true
}
+ shot {
+ applicationId = "com.bottlerocketstudios.brarchitecture.test"
+ // tolerance = 1.1
+ }
}
// Declare configurations per variant to use in the dependencies block below. See :data module for examples if needed here in the :app module.
@@ -178,4 +186,5 @@ dependencies {
espressoDependencies()
extJunitRunnerDependencies()
androidxCoreDependencies()
+ composeTestDependencies()
}
diff --git a/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.HomeScreenUITest_homeScreenNoResultItemsScreenshot.png b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.HomeScreenUITest_homeScreenNoResultItemsScreenshot.png
new file mode 100644
index 00000000..2b53a7ff
Binary files /dev/null and b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.HomeScreenUITest_homeScreenNoResultItemsScreenshot.png differ
diff --git a/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.HomeScreenUITest_homeScreenWithListItemsScreenshot.png b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.HomeScreenUITest_homeScreenWithListItemsScreenshot.png
new file mode 100644
index 00000000..79bcd2c0
Binary files /dev/null and b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.HomeScreenUITest_homeScreenWithListItemsScreenshot.png differ
diff --git a/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.PullRequestScreenshotTest_pullRequestNoResultItemsScreenshot.png b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.PullRequestScreenshotTest_pullRequestNoResultItemsScreenshot.png
new file mode 100644
index 00000000..b285959a
Binary files /dev/null and b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.PullRequestScreenshotTest_pullRequestNoResultItemsScreenshot.png differ
diff --git a/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.PullRequestScreenshotTest_pullRequestWithListItemsScreenshot.png b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.PullRequestScreenshotTest_pullRequestWithListItemsScreenshot.png
new file mode 100644
index 00000000..aba75954
Binary files /dev/null and b/app/screenshots/internal/debug/com.bottlerocketstudios.brarchitecture.PullRequestScreenshotTest_pullRequestWithListItemsScreenshot.png differ
diff --git a/app/src/androidTest/AndroidManifest.xml b/app/src/androidTest/AndroidManifest.xml
new file mode 100644
index 00000000..3ca81bff
--- /dev/null
+++ b/app/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/BaseUIScreenshotTest.kt b/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/BaseUIScreenshotTest.kt
new file mode 100644
index 00000000..ac11c6dd
--- /dev/null
+++ b/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/BaseUIScreenshotTest.kt
@@ -0,0 +1,12 @@
+package com.bottlerocketstudios.brarchitecture
+
+import com.karumi.shot.ScreenshotTest
+
+/**
+ * For later use to create extension functions to speed up development of UI tests
+ *
+ * Please see the Confluence page for setup, troubleshooting and more information on Compose UI x Karumi screenshot testing.
+ *
+ * https://confluence.bottlerocketapps.com/display/BKB/Screenshot+Testing%3A+Compose+UI+x+Karumi
+ */
+open class BaseUIScreenshotTest : ScreenshotTest
diff --git a/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/HomeScreenUITest.kt b/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/HomeScreenUITest.kt
new file mode 100644
index 00000000..705a6a3d
--- /dev/null
+++ b/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/HomeScreenUITest.kt
@@ -0,0 +1,111 @@
+package com.bottlerocketstudios.brarchitecture
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import com.bottlerocketstudios.brarchitecture.domain.models.GitRepository
+import com.bottlerocketstudios.brarchitecture.domain.models.Link
+import com.bottlerocketstudios.brarchitecture.domain.models.Links
+import com.bottlerocketstudios.brarchitecture.domain.models.User
+import com.bottlerocketstudios.brarchitecture.domain.models.Workspace
+import com.bottlerocketstudios.compose.home.HomeScreen
+import com.bottlerocketstudios.compose.home.HomeScreenState
+import com.bottlerocketstudios.compose.home.UserRepositoryUiModel
+import com.bottlerocketstudios.compose.resources.ArchitectureDemoTheme
+import com.bottlerocketstudios.compose.util.StringIdHelper
+import com.bottlerocketstudios.compose.util.asMutableState
+import org.junit.Rule
+import org.junit.Test
+import java.time.ZonedDateTime
+
+class HomeScreenUITest : BaseUIScreenshotTest() {
+
+ @get:Rule
+ val composeRule = createComposeRule()
+
+ @Test
+ fun homeScreenWithListItemsScreenshot() {
+ renderComponent(
+ HomeScreenState(
+ repositories = listOf(testCard1, testCard2).asMutableState(),
+ itemSelected = {}
+ )
+ )
+
+ compareScreenshot(composeRule.onRoot())
+ }
+
+ @Test
+ fun homeScreenNoResultItemsScreenshot() {
+ renderComponent(
+ HomeScreenState(
+ repositories = emptyList().asMutableState(),
+ itemSelected = {}
+ )
+ )
+
+ compareScreenshot(composeRule.onRoot())
+ }
+
+ fun renderComponent(state: HomeScreenState) { composeRule.setContent { ArchitectureDemoTheme { HomeScreen(state) } } }
+
+ // todo move somewhere that both UI test and Compose Preview
+ // can share it instead of setting it twice.
+ internal val testCard1 = UserRepositoryUiModel(
+ GitRepository(
+ scm = "scm1",
+ name = "name1",
+ owner = User(
+ username = "username1",
+ nickname = "nickname1",
+ accountStatus = "accountStatus1",
+ displayName = "displayName1",
+ createdOn = "createdOn1",
+ uuid = "uuid1",
+ links = Links(
+ avatar = Link(
+ href = "href1", name = ""
+ )
+ ),
+ avatarUrl = "avatarUrl1"
+ ),
+ workspace = Workspace(
+ slug = "slug1",
+ name = "name1",
+ uuid = "uuid1"
+ ),
+ isPrivate = false,
+ description = "description1",
+ updated = ZonedDateTime.now()
+ ),
+ formattedLastUpdatedTime = StringIdHelper.Raw("")
+ )
+ internal val testCard2 = UserRepositoryUiModel(
+ GitRepository(
+ scm = "scm2",
+ name = "name2",
+ owner = User(
+ username = "username2",
+ nickname = "nickname2",
+ accountStatus = "accountStatus2",
+ displayName = "displayName2",
+ createdOn = "createdOn2",
+ uuid = "uuid2",
+ links = Links(
+ avatar = Link(
+ href = "href2", name = ""
+ )
+ ),
+ avatarUrl = "avatarUrl2"
+ ),
+ workspace = Workspace(
+ slug = "slug2",
+ name = "name2",
+ uuid = "uuid2"
+ ),
+ isPrivate = false,
+ description = "description2",
+ updated = ZonedDateTime.now()
+ ),
+ formattedLastUpdatedTime = StringIdHelper.Raw("")
+ )
+}
diff --git a/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/PullRequestScreenshotTest.kt b/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/PullRequestScreenshotTest.kt
new file mode 100644
index 00000000..fb9400b6
--- /dev/null
+++ b/app/src/androidTest/java/com/bottlerocketstudios/brarchitecture/PullRequestScreenshotTest.kt
@@ -0,0 +1,76 @@
+package com.bottlerocketstudios.brarchitecture
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import com.bottlerocketstudios.compose.pullrequest.PullRequestItemState
+import com.bottlerocketstudios.compose.pullrequest.PullRequestScreen
+import com.bottlerocketstudios.compose.pullrequest.PullRequestScreenState
+import com.bottlerocketstudios.compose.resources.ArchitectureDemoTheme
+import com.bottlerocketstudios.compose.util.asMutableState
+import org.junit.Rule
+import org.junit.Test
+
+class PullRequestScreenshotTest : BaseUIScreenshotTest() {
+
+ @get:Rule
+ val composeRule = createComposeRule()
+
+ @Test
+ fun pullRequestWithListItemsScreenshot() {
+ renderComponent(testList)
+ compareScreenshot(composeRule.onRoot())
+ }
+
+ @Test
+ fun pullRequestNoResultItemsScreenshot() {
+ renderComponent(emptyList)
+ compareScreenshot(composeRule.onRoot())
+ }
+
+ fun renderComponent(state: PullRequestScreenState) { composeRule.setContent { ArchitectureDemoTheme { PullRequestScreen(state) } } }
+
+ // todo move somewhere that both UI test and Compose Preview
+ // can share it instead of setting it twice.
+ private val testList = PullRequestScreenState(
+ listOf(
+ PullRequestItemState(
+ prName = "ASAA-19/PR-Screen".asMutableState(),
+ prState = "Open".asMutableState(),
+ prCreation = "5 days ago".asMutableState(),
+ linesAdded = "0 Lines Added".asMutableState(),
+ linesRemoved = "0 Lines Removed".asMutableState()
+ ),
+ PullRequestItemState(
+ prName = "ASAA-20/PR-Screen".asMutableState(),
+ prState = "Open".asMutableState(),
+ prCreation = "4 days ago".asMutableState(),
+ linesAdded = "0 Lines Added".asMutableState(),
+ linesRemoved = "0 Lines Removed".asMutableState()
+ ),
+ PullRequestItemState(
+ prName = "ASAA-21/PR-Screen".asMutableState(),
+ prState = "Open".asMutableState(),
+ prCreation = "3 days ago".asMutableState(),
+ linesAdded = "0 Lines Added".asMutableState(),
+ linesRemoved = "0 Lines Removed".asMutableState()
+ ),
+ PullRequestItemState(
+ prName = "ASAA-22/PR-Screen".asMutableState(),
+ prState = "Open".asMutableState(),
+ prCreation = "2 days ago".asMutableState(),
+ linesAdded = "0 Lines Added".asMutableState(),
+ linesRemoved = "0 Lines Removed".asMutableState()
+ )
+ ).asMutableState(),
+ selectedText = "Open".asMutableState(),
+ selectionList = listOf("Open", "Merged", "Declined", "Superseded").asMutableState(),
+ onFilterSelectionClicked = {}
+ )
+
+ private val emptyList = PullRequestScreenState(
+ emptyList().asMutableState(),
+ selectedText = "Open".asMutableState(),
+ selectionList = listOf("Open", "Merged", "Declined", "Superseded").asMutableState(),
+ onFilterSelectionClicked = {}
+ )
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8f3b43e6..11f13ff0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,8 @@
+ package="com.bottlerocketstudios.brarchitecture"
+ android:sharedUserId="com.bottlerocketstudios.brarchitecture.uid">
diff --git a/build.gradle.kts b/build.gradle.kts
index b57a186f..ffb261a9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,6 +9,7 @@ buildscript {
classpath(Config.BuildScriptPlugins.ANDROID_GRADLE)
classpath(Config.BuildScriptPlugins.KOTLIN_GRADLE)
classpath(Config.BuildScriptPlugins.GRADLE_VERSIONS)
+ classpath(Config.BuildScriptPlugins.KARUMI_SCREENSHOT_TESTING)
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle.kts files
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
index bd0455ab..606088b1 100644
--- a/buildSrc/src/main/kotlin/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/Dependencies.kt
@@ -1,3 +1,4 @@
+import TestLibraries.COMPOSE_UI_TEST
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.dsl.DependencyHandler
import java.util.Locale
@@ -63,6 +64,10 @@ object Config {
// Gradle version plugin; use dependencyUpdates task to view third party dependency updates via `./gradlew dependencyUpdates` or AS Gradle -> [project]] -> Tasks -> help -> dependencyUpdates
// https://github.com/ben-manes/gradle-versions-plugin/releases
const val GRADLE_VERSIONS = "com.github.ben-manes:gradle-versions-plugin:0.41.0"
+
+ // Gradle plugin and a core android library thought to run screenshot tests for Android
+ // https://github.com/pedrovgs/Shot/releases
+ const val KARUMI_SCREENSHOT_TESTING = "com.karumi:shot:5.14.1"
}
/**
@@ -78,6 +83,7 @@ object Config {
// const val JACOCO = "jacoco" // https://docs.gradle.org/current/userguide/jacoco_plugin.html - Helper jacoco gradle files manage applying the jacoco plugin
const val PARCELIZE = "kotlin-parcelize"
const val KSP = "com.google.devtools.ksp"
+ const val KARUMI_SHOT_TESTING = "shot"
object Kotlin {
const val ANDROID = "android"
}
@@ -314,6 +320,7 @@ private object TestLibraries {
const val ANDROIDX_TEST_CORE = "androidx.test:core:1.4.0"
const val ANDROIDX_TEST_CORE_KTX = "androidx.test:core-ktx:1.4.0"
const val ESPRESSO_CORE = "androidx.test.espresso:espresso-core:3.4.0"
+ const val ESPRESSO_CONTRIB = "androidx.test.espresso:espresso-contrib:3.4.0"
const val JUNIT_EXT_RUNNER = "androidx.test.ext:junit:1.1.3"
const val JUNIT_EXT_RUNNER_KTX = "androidx.test.ext:junit-ktx:1.1.3"
@@ -329,6 +336,7 @@ private object TestLibraries {
// Compose - Testing
// https://developer.android.com/jetpack/compose/testing
// Test rules and transitive dependencies:
+ const val COMPOSE_UI_TEST = "androidx.compose.ui:ui-test:${Config.Compose.COMPOSE_VERSION}"
const val JUNIT_COMPOSE_TESTING = "androidx.compose.ui:ui-test-junit4:${Config.Compose.COMPOSE_VERSION}"
// Needed for createComposeRule, but not createAndroidComposeRule:
const val MANIFEST_COMPOSE_TESTING = "androidx.compose.ui:ui-test-manifest:${Config.Compose.COMPOSE_VERSION}"
@@ -500,6 +508,7 @@ fun DependencyHandler.turbineDependencies() {
}
fun DependencyHandler.composeTestDependencies() {
+ androidTestImplementation(COMPOSE_UI_TEST)
androidTestImplementation(TestLibraries.JUNIT_COMPOSE_TESTING)
debugImplementation(TestLibraries.MANIFEST_COMPOSE_TESTING)
}
diff --git a/scripts/open_screenshot_report_internalDebug.sh b/scripts/open_screenshot_report_internalDebug.sh
new file mode 100644
index 00000000..6a36843b
--- /dev/null
+++ b/scripts/open_screenshot_report_internalDebug.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+# The purpose of this script is to allow an Android Studio Run Configuration to be created to execute the following command to open the screenshot report results
+
+open app/build/reports/shot/internal/debug/verification/index.html