diff --git a/.github/workflows/AndroidCIWithGmd.yaml b/.github/workflows/AndroidCIWithGmd.yaml index 78902cb32..e10c49f9e 100644 --- a/.github/workflows/AndroidCIWithGmd.yaml +++ b/.github/workflows/AndroidCIWithGmd.yaml @@ -10,9 +10,6 @@ jobs: android-ci: runs-on: macos-12 - strategy: - matrix: - device-config: [ "pixel4api30aospatd", "pixelcapi30aospatd" ] steps: - name: Checkout @@ -36,9 +33,12 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v2 + - name: Build AndroidTest apps + run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest + - name: Run instrumented tests with GMD run: ./gradlew cleanManagedDevices --unused-only && - ./gradlew ${{ matrix.device-config }}DemoDebugAndroidTest -Dorg.gradle.workers.max=1 + ./gradlew ciDemoDebugAndroidTest -Dorg.gradle.workers.max=1 -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true - name: Upload test reports @@ -46,5 +46,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: test-reports - path: | - '**/*/build/reports/androidTests/' + path: '**/build/reports/androidTests' diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index c15daea80..0389dcf56 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -48,15 +48,22 @@ jobs: - name: Upload build outputs (APKs) uses: actions/upload-artifact@v3 with: - name: build-outputs - path: app/build/outputs + name: APKs + path: '**/build/outputs/apk/**/*.apk' - - name: Upload build reports + - name: Upload lint reports (HTML) if: always() uses: actions/upload-artifact@v3 with: - name: build-reports - path: app/build/reports + name: lint-reports + path: '**/build/reports/lint-results-*.html' + + - name: Upload test results (XML) + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-results + path: '**/build/test-results/test*UnitTest/**.xml' androidTest: needs: build @@ -82,6 +89,9 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Build AndroidTest apps + run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest --daemon + - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: @@ -90,11 +100,11 @@ jobs: disable-animations: true disk-size: 6000M heap-size: 600M - script: ./gradlew connectedDemoDebugAndroidTest -x :benchmark:connectedDemoBenchmarkAndroidTest + script: ./gradlew connectedDemoDebugAndroidTest -x :benchmark:connectedDemoBenchmarkAndroidTest --daemon - name: Upload test reports if: always() uses: actions/upload-artifact@v3 with: name: test-reports-${{ matrix.api-level }} - path: '*/build/reports/androidTests' + path: '**/build/reports/androidTests' diff --git a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt index a37eb7a77..5aa3ab02e 100644 --- a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt +++ b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt @@ -16,12 +16,14 @@ package com.google.samples.apps.nowinandroid.ui +import androidx.annotation.StringRes import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.assertIsSelected import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithContentDescription @@ -34,10 +36,10 @@ import com.google.samples.apps.nowinandroid.R import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import kotlin.properties.ReadOnlyProperty import com.google.samples.apps.nowinandroid.feature.bookmarks.R as BookmarksR import com.google.samples.apps.nowinandroid.feature.foryou.R as FeatureForyouR import com.google.samples.apps.nowinandroid.feature.interests.R as FeatureInterestsR @@ -69,35 +71,19 @@ class NavigationTest { @get:Rule(order = 2) val composeTestRule = createAndroidComposeRule() - // The strings used for matching in these tests - private lateinit var done: String - private lateinit var navigateUp: String - private lateinit var forYouLoading: String - private lateinit var forYou: String - private lateinit var interests: String - private lateinit var sampleTopic: String - private lateinit var appName: String - private lateinit var saved: String - private lateinit var settings: String - private lateinit var brand: String - private lateinit var ok: String + private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) = + ReadOnlyProperty { _, _ -> activity.getString(resId) } - @Before - fun setup() { - composeTestRule.activity.apply { - done = getString(FeatureForyouR.string.done) - navigateUp = getString(FeatureForyouR.string.navigate_up) - forYouLoading = getString(FeatureForyouR.string.for_you_loading) - forYou = getString(FeatureForyouR.string.for_you) - interests = getString(FeatureInterestsR.string.interests) - sampleTopic = "Headlines" - appName = getString(R.string.app_name) - saved = getString(BookmarksR.string.saved) - settings = getString(SettingsR.string.top_app_bar_action_icon_description) - brand = getString(SettingsR.string.brand_android) - ok = getString(SettingsR.string.dismiss_dialog_button_text) - } - } + // The strings used for matching in these tests + private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.navigate_up) + private val forYou by composeTestRule.stringResource(FeatureForyouR.string.for_you) + private val interests by composeTestRule.stringResource(FeatureInterestsR.string.interests) + private val sampleTopic = "Headlines" + private val appName by composeTestRule.stringResource(R.string.app_name) + private val saved by composeTestRule.stringResource(BookmarksR.string.saved) + private val settings by composeTestRule.stringResource(SettingsR.string.top_app_bar_action_icon_description) + private val brand by composeTestRule.stringResource(SettingsR.string.brand_android) + private val ok by composeTestRule.stringResource(SettingsR.string.dismiss_dialog_button_text) @Test fun firstScreen_isForYou() { diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt index 8129defeb..86e29be33 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt @@ -18,7 +18,7 @@ package com.google.samples.apps.nowinandroid import com.android.build.api.dsl.CommonExtension import com.android.build.api.dsl.ManagedVirtualDevice -import org.gradle.api.Project +import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.invoke /** @@ -27,16 +27,17 @@ import org.gradle.kotlin.dsl.invoke internal fun configureGradleManagedDevices( commonExtension: CommonExtension<*, *, *, *>, ) { - val deviceConfigs = listOf( - DeviceConfig("Pixel 4", 30, "aosp-atd"), - DeviceConfig("Pixel 6", 31, "aosp"), - DeviceConfig("Pixel C", 30, "aosp-atd"), - ) + val pixel4 = DeviceConfig("Pixel 4", 30, "aosp-atd") + val pixel6 = DeviceConfig("Pixel 6", 31, "aosp") + val pixelC = DeviceConfig("Pixel C", 30, "aosp-atd") + + val allDevices = listOf(pixel4, pixel6, pixelC) + val ciDevices = listOf(pixel4, pixelC) commonExtension.testOptions { managedDevices { devices { - deviceConfigs.forEach { deviceConfig -> + allDevices.forEach { deviceConfig -> maybeCreate(deviceConfig.taskName, ManagedVirtualDevice::class.java).apply { device = deviceConfig.device apiLevel = deviceConfig.apiLevel @@ -44,6 +45,13 @@ internal fun configureGradleManagedDevices( } } } + groups { + maybeCreate("ci").apply { + ciDevices.forEach { deviceConfig -> + targetDevices.add(devices[deviceConfig.taskName]) + } + } + } } } }