Adds Dropshots and edge-to-edge tests

Change-Id: I4e0d5d0c9dfbf106acea951266c04139fab93c53

# Conflicts:
#	build.gradle.kts
ja/edge-to-edge-dropshots
Jose Alcérreca 6 months ago
parent 85129e4660
commit 9a6d41598d

@ -189,7 +189,8 @@ jobs:
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v3 uses: gradle/gradle-build-action@v3
- name: Build projects and run instrumentation tests - name: Build projects and run instrumentation tests including screenshots
id: dropshotsverify
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
@ -197,8 +198,43 @@ jobs:
disable-animations: true disable-animations: true
disk-size: 6000M disk-size: 6000M
heap-size: 600M heap-size: 600M
profile: pixel_5
ram-size: 4096M
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: ./gradlew connectedDemoDebugAndroidTest --daemon script: ./gradlew connectedDemoDebugAndroidTest --daemon
- name: Prevent pushing new screenshots if this is a fork (instrumented)
id: checkfork_screenshots_instrumented
continue-on-error: false
if: steps.dropshotsverify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Instrumented screenshot tests failed, please create a PR in your fork first." && exit 1
- name: Record new instrumented screenshots
id: screenshotsrecordinstrumented
if: steps.dropshotsverify.outcome == 'failure' && github.event_name == 'pull_request'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
disable-animations: true
disk-size: 6000M
heap-size: 600M
profile: pixel_5
force-avd-creation: false
ram-size: 4096M
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: adb shell rm -rf /storage/emulated/0/Download/screenshots && ./gradlew connectedDemoDebugAndroidTest -Pdropshots.record --stacktrace
- name: Push new screenshots if available
uses: stefanzweifel/git-auto-commit-action@4b8a201e31cadd9829df349894b28c54e6c19fe6
if: steps.screenshotsrecordinstrumented.outcome == 'success'
with:
file_pattern: '*/*.png'
disable_globbing: true
commit_message: "🤖 Updates instrumented screenshots"
- name: Run local tests (including Roborazzi) for the combined coverage report (only API 30) - name: Run local tests (including Roborazzi) for the combined coverage report (only API 30)
if: matrix.api-level == 30 if: matrix.api-level == 30
# There is no need to verify Roborazzi tests to generate coverage. # There is no need to verify Roborazzi tests to generate coverage.

@ -126,9 +126,12 @@ dependencies {
androidTestImplementation(projects.core.dataTest) androidTestImplementation(projects.core.dataTest)
androidTestImplementation(projects.core.datastoreTest) androidTestImplementation(projects.core.datastoreTest)
androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.navigation.testing) androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(libs.androidx.compose.ui.test)
androidTestImplementation(libs.hilt.android.testing) androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(libs.androidx.test.espresso.device)
androidTestImplementation(libs.androidx.test.uiautomator)
baselineProfile(projects.benchmarks) baselineProfile(projects.benchmarks)
} }

@ -0,0 +1,113 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.ui
import androidx.test.core.app.takeScreenshot
import androidx.test.espresso.device.common.executeShellCommand
import androidx.test.espresso.device.filter.RequiresDisplay
import androidx.test.espresso.device.sizeclass.HeightSizeClass.Companion.HeightSizeClassEnum
import androidx.test.espresso.device.sizeclass.WidthSizeClass.Companion.WidthSizeClassEnum
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.dropbox.dropshots.Dropshots
import com.google.samples.apps.nowinandroid.MainActivity
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@HiltAndroidTest
class EdgeToEdgeTest {
/**
* Manages the components' state and is used to perform injection on your test
*/
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
/**
* Grant [android.Manifest.permission.POST_NOTIFICATIONS] permission.
*/
@get:Rule(order = 2)
val postNotificationsPermission = GrantPostNotificationsPermissionRule()
@get:Rule(order = 3)
val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
@get:Rule(order = 4)
val dropshots = Dropshots()
@Before
fun setup() = hiltRule.inject()
@Before
fun enableDemoMode() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
executeShellCommand("cmd overlay enable-exclusive com.android.internal.systemui.navbar.threebutton")
executeShellCommand("settings put global sysui_demo_allowed 1")
executeShellCommand("am broadcast -a com.android.systemui.demo -e command enter")
executeShellCommand("am broadcast -a com.android.systemui.demo -e command notifications -e visible false")
executeShellCommand("am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1234")
executeShellCommand("am broadcast -a com.android.systemui.demo -e command network -e wifi hide")
executeShellCommand("am broadcast -a com.android.systemui.demo -e command network -e mobile hide")
}
}
@After
fun resetDemoMode() {
executeShellCommand("am broadcast -a com.android.systemui.demo -e command exit")
}
@RequiresDisplay(WidthSizeClassEnum.COMPACT, HeightSizeClassEnum.MEDIUM)
@SdkSuppress(minSdkVersion = 26, maxSdkVersion = 26)
@Test
fun edgeToEdge_Phone_Api26() {
testEdgeToEdge("edgeToEdge_Phone_Api26")
}
@RequiresDisplay(WidthSizeClassEnum.COMPACT, HeightSizeClassEnum.MEDIUM)
@SdkSuppress(minSdkVersion = 30, maxSdkVersion = 30)
@Test
fun edgeToEdge_Phone_Api30() {
testEdgeToEdge("edgeToEdge_Phone_Api30")
}
@RequiresDisplay(WidthSizeClassEnum.EXPANDED, HeightSizeClassEnum.MEDIUM)
@SdkSuppress(minSdkVersion = 30, maxSdkVersion = 30)
@Test
fun edgeToEdge_Tablet_Api30() {
testEdgeToEdge("edgeToEdge_Tablet_Api30")
}
private fun testEdgeToEdge(screenshotFileName: String) {
dropshots.assertSnapshot(takeScreenshot(), screenshotFileName)
}
}

@ -34,6 +34,7 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
apply("org.jetbrains.kotlin.android") apply("org.jetbrains.kotlin.android")
apply("nowinandroid.android.lint") apply("nowinandroid.android.lint")
apply("com.dropbox.dependency-guard") apply("com.dropbox.dependency-guard")
apply("com.dropbox.dropshots")
} }
extensions.configure<ApplicationExtension> { extensions.configure<ApplicationExtension> {

@ -40,6 +40,7 @@ plugins {
alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.dependencyGuard) apply false alias(libs.plugins.dependencyGuard) apply false
alias(libs.plugins.dropshots) apply false
alias(libs.plugins.firebase.crashlytics) apply false alias(libs.plugins.firebase.crashlytics) apply false
alias(libs.plugins.firebase.perf) apply false alias(libs.plugins.firebase.perf) apply false
alias(libs.plugins.gms) apply false alias(libs.plugins.gms) apply false

@ -37,7 +37,7 @@ kotlin.code.style=official
# Disable build features that are enabled by default, # Disable build features that are enabled by default,
# https://developer.android.com/build/releases/gradle-plugin#default-changes # https://developer.android.com/build/releases/gradle-plugin#default-changes
android.defaults.buildfeatures.resvalues=false #android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false android.defaults.buildfeatures.shaders=false
# Run Roborazzi screenshot tests with the local tests # Run Roborazzi screenshot tests with the local tests

@ -17,22 +17,24 @@ androidxCore = "1.12.0"
androidxCoreSplashscreen = "1.0.1" androidxCoreSplashscreen = "1.0.1"
androidxDataStore = "1.0.0" androidxDataStore = "1.0.0"
androidxEspresso = "3.5.1" androidxEspresso = "3.5.1"
androidxEspressoDevice = "1.0.0-beta01"
androidxHiltNavigationCompose = "1.2.0" androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.7.0" androidxLifecycle = "2.7.0"
androidxMacroBenchmark = "1.2.4" androidxMacroBenchmark = "1.2.4"
androidxMetrics = "1.0.0-alpha04" androidxMetrics = "1.0.0-alpha04"
androidxNavigation = "2.8.0-alpha06" androidxNavigation = "2.8.0-alpha06"
androidxProfileinstaller = "1.3.1" androidxProfileinstaller = "1.3.1"
androidxTestCore = "1.5.0" androidxTestCore = "1.6.0-beta01"
androidxTestExt = "1.1.5" androidxTestExt = "1.1.5"
androidxTestRules = "1.5.0" androidxTestRules = "1.5.0"
androidxTestRunner = "1.5.2" androidxTestRunner = "1.5.2"
androidxTracing = "1.3.0-alpha02" androidxTracing = "1.3.0-alpha02"
androidxUiAutomator = "2.2.0" androidxUiAutomator = "2.3.0"
androidxWindowManager = "1.3.0-alpha03" androidxWindowManager = "1.3.0-alpha03"
androidxWork = "2.9.0" androidxWork = "2.9.0"
coil = "2.6.0" coil = "2.6.0"
dependencyGuard = "0.5.0" dependencyGuard = "0.5.0"
dropshots = "0.4.1"
firebaseBom = "32.4.0" firebaseBom = "32.4.0"
firebaseCrashlyticsPlugin = "2.9.9" firebaseCrashlyticsPlugin = "2.9.9"
firebasePerfPlugin = "1.4.2" firebasePerfPlugin = "1.4.2"
@ -98,6 +100,7 @@ androidx-navigation-testing = { group = "androidx.navigation", name = "navigatio
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" } androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" }
androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" }
androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" } androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" }
androidx-test-espresso-device = { group = "androidx.test.espresso", name = "espresso-device", version.ref = "androidxEspressoDevice" }
androidx-test-ext = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExt" } androidx-test-ext = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExt" }
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" } androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" } androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" }
@ -160,6 +163,7 @@ android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidxMacroBenchmark"} baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidxMacroBenchmark"}
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
dependencyGuard = { id = "com.dropbox.dependency-guard", version.ref = "dependencyGuard" } dependencyGuard = { id = "com.dropbox.dependency-guard", version.ref = "dependencyGuard" }
dropshots = { id = "com.dropbox.dropshots", version.ref = "dropshots" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsPlugin" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsPlugin" }
firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerfPlugin" } firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerfPlugin" }
gms = { id = "com.google.gms.google-services", version.ref = "gmsPlugin" } gms = { id = "com.google.gms.google-services", version.ref = "gmsPlugin" }

Loading…
Cancel
Save