diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f27f0cd..826ffed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,9 @@ on: types: [published] jobs: library: - runs-on: ubuntu-latest + runs-on: [ self-hosted-org, linux ] + container: + image: docker://docker.tuenti.io/android/novum_android:12 steps: - name: Checkout repo uses: actions/checkout@v4 @@ -16,10 +18,14 @@ jobs: ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEYID }} + NEXUS_USER: ${{ secrets.NEXUS_RELEASE_USER }} + NEXUS_PASS: ${{ secrets.NEXUS_RELEASE_PASSWORD }} run: | - ./gradlew publishReleasePublicationToSonatypeRepository -DLIBRARY_VERSION=${{ github.event.release.tag_name }} --max-workers 1 closeAndReleaseStagingRepository + ./gradlew publishReleasePublicationToMavenRepository -DLIBRARY_VERSION=${{ github.event.release.tag_name }} plugin: - runs-on: ubuntu-latest + runs-on: [ self-hosted-org, linux ] + container: + image: docker://docker.tuenti.io/android/novum_android:12 steps: - name: Checkout repo uses: actions/checkout@v4 @@ -28,6 +34,8 @@ jobs: env: GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} + NEXUS_USER: ${{ secrets.NEXUS_RELEASE_USER }} + NEXUS_PASS: ${{ secrets.NEXUS_RELEASE_PASSWORD }} run: | cd include-build - ../gradlew publishPlugins -DLIBRARY_VERSION=${{ github.event.release.tag_name }} \ No newline at end of file + ../gradlew publishGradlePluginPublicationToMavenRepository -DLIBRARY_VERSION=${{ github.event.release.tag_name }} \ No newline at end of file diff --git a/android-snaptesting/build.gradle.kts b/android-snaptesting/build.gradle.kts index f347328..04855c9 100644 --- a/android-snaptesting/build.gradle.kts +++ b/android-snaptesting/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(libs.androidx.test.monitor) implementation(libs.androidx.test.runner) implementation(libs.androidx.ui.test.junit4.android) + implementation(libs.espresso.core) } apply("${rootProject.projectDir}/mavencentral.gradle") diff --git a/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ScreenshotsRule.kt b/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ScreenshotsRule.kt index 8e44a38..f7ddc39 100644 --- a/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ScreenshotsRule.kt +++ b/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ScreenshotsRule.kt @@ -4,12 +4,19 @@ import android.app.Activity import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Build +import android.os.Looper +import android.view.View +import android.widget.EditText +import android.widget.HorizontalScrollView +import android.widget.ScrollView import androidx.annotation.RequiresApi import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onRoot +import androidx.test.espresso.Espresso import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.runner.screenshot.Screenshot import com.dropbox.differ.ImageComparator import com.dropbox.differ.Mask @@ -51,7 +58,7 @@ public class ScreenshotsRule( @RequiresApi(Build.VERSION_CODES.O) public fun compareScreenshot( rule: ComposeTestRule, - name: String?, + name: String? = null, ) { rule.waitForIdle() val bitmap = rule.onRoot().captureToImage().asAndroidBitmap() @@ -60,8 +67,12 @@ public class ScreenshotsRule( public fun compareScreenshot( activity: Activity, - name: String?, + name: String? = null, ) { + val view = activity.findViewById(android.R.id.content) + + disableFlakyComponentsAndWaitForIdle(view) + val bitmap = Screenshot.capture(activity).bitmap compareScreenshot(bitmap, name) } @@ -69,7 +80,7 @@ public class ScreenshotsRule( @Suppress("MemberVisibilityCanBePrivate") public fun compareScreenshot( bitmap: Bitmap, - name: String? + name: String? = null, ) { val resourceName = "${className}_${name ?: testName}.png" val fileName = "$resourceName.${System.nanoTime()}" @@ -141,4 +152,58 @@ public class ScreenshotsRule( ) } } + + private fun disableFlakyComponentsAndWaitForIdle(view: View? = null) { + if (view != null) { + disableAnimatedComponents(view) + } + if (notInAppMainThread()) { + waitForAnimationsToFinish() + } + } + + private fun disableAnimatedComponents(view: View) { + runOnUi { + hideEditTextCursors(view) + hideScrollViewBars(view) + } + } + + private fun hideEditTextCursors(view: View) { + view.childrenViews().forEach { + it.isCursorVisible = false + } + } + + private fun hideScrollViewBars(view: View) { + view.childrenViews().forEach { + hideViewBars(it) + } + + view.childrenViews().forEach { + hideViewBars(it) + } + } + + private fun hideViewBars(it: View) { + it.isHorizontalScrollBarEnabled = false + it.isVerticalScrollBarEnabled = false + it.overScrollMode = View.OVER_SCROLL_NEVER + } + + public fun waitForAnimationsToFinish() { + getInstrumentation().waitForIdleSync() + Espresso.onIdle() + } + + public fun runOnUi(block: () -> Unit) { + if (notInAppMainThread()) { + getInstrumentation().runOnMainSync { block() } + } else { + block() + } + } + + private fun notInAppMainThread() = Looper.myLooper() != Looper.getMainLooper() + } diff --git a/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ViewUtils.kt b/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ViewUtils.kt new file mode 100644 index 0000000..c58fa6f --- /dev/null +++ b/android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ViewUtils.kt @@ -0,0 +1,30 @@ +package com.telefonica.androidsnaptesting.screenshots + +import android.view.View +import android.view.ViewGroup + +@Suppress("UNCHECKED_CAST") +public inline fun View.childrenViews(): List = filterChildrenViews { + it is T +} as List + +public fun View.filterChildrenViews(filter: (View) -> Boolean): List { + val children = mutableSetOf() + val view = this + if (view !is ViewGroup) { + if (filter.invoke(view)) { + children.add(view) + } + } else { + for (i in 0 until view.childCount) { + view.getChildAt(i).let { + children.addAll(it.filterChildrenViews(filter)) + if (filter.invoke(it)) { + children.add(it) + } + } + } + } + + return children.toList() +} diff --git a/app/src/androidTest/java/com/telefonica/androidsnaptesting/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/telefonica/androidsnaptesting/ExampleInstrumentedTest.kt index 08932c3..173b13f 100644 --- a/app/src/androidTest/java/com/telefonica/androidsnaptesting/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/telefonica/androidsnaptesting/ExampleInstrumentedTest.kt @@ -2,6 +2,9 @@ package com.telefonica.androidsnaptesting import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.telefonica.androidsnaptesting.logs.LogsRecorder +import com.telefonica.androidsnaptesting.logs.LogsRule +import com.telefonica.androidsnaptesting.screenshots.ScreenshotsRule import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/include-build/build.gradle.kts b/include-build/build.gradle.kts index 7d2d7b7..2c6e64d 100644 --- a/include-build/build.gradle.kts +++ b/include-build/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.detekt) + alias(libs.plugins.publish) } allprojects { @@ -14,3 +15,5 @@ allprojects { buildUponDefaultConfig = true } } + +apply("${rootProject.projectDir}/../publish_maven_central.gradle") \ No newline at end of file diff --git a/include-build/gradle-plugin/build.gradle.kts b/include-build/gradle-plugin/build.gradle.kts index 7fb061c..abd7f82 100644 --- a/include-build/gradle-plugin/build.gradle.kts +++ b/include-build/gradle-plugin/build.gradle.kts @@ -31,3 +31,5 @@ gradlePlugin { } } } + +apply("${rootProject.projectDir}/mavencentral.gradle") \ No newline at end of file diff --git a/include-build/gradle/libs.versions.toml b/include-build/gradle/libs.versions.toml index d879c7c..b343cbb 100644 --- a/include-build/gradle/libs.versions.toml +++ b/include-build/gradle/libs.versions.toml @@ -5,6 +5,7 @@ ddmlib = "31.4.1" kotlin = "1.9.23" detekt = "1.23.6" publish-plugin = "1.2.0" +publish = "1.1.0" [libraries] android-builder-test-api = { module = "com.android.tools.build:builder-test-api", version.ref = "agp" } @@ -15,4 +16,5 @@ android-gradle = { module = "com.android.tools.build:gradle", version.ref = "agp [plugins] detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -publish-plugin = { id = "com.gradle.plugin-publish", version.ref = "publish-plugin" } \ No newline at end of file +publish-plugin = { id = "com.gradle.plugin-publish", version.ref = "publish-plugin" } +publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "publish" } \ No newline at end of file diff --git a/include-build/mavencentral.gradle b/include-build/mavencentral.gradle new file mode 100644 index 0000000..075e419 --- /dev/null +++ b/include-build/mavencentral.gradle @@ -0,0 +1,62 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +publishing { + repositories { + maven { + credentials { + username System.env.NEXUS_USER + password System.env.NEXUS_PASS + } + url "https://nexusng.tuenti.io/repository/maven-release-private/" + } + } + publications { + gradlePlugin(MavenPublication) { + groupId 'com.telefonica' + artifactId 'android-snaptesting-gradle-plugin' + version version + + artifact("$buildDir/libs/gradle-plugin-${version}.jar") + + pom { + name = 'Android Snaptesting Gradle Plugin' + description = 'Gradle Plugin for logs snapshot testing for Android Instrumentation tests.' + url = 'https://github.com/Telefonica/android-snaptesting' + licenses { + license { + name = 'The Apache License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + developers { + developer { + id = 'android-team-telefonica' + name = 'Android Team' + email = 'cto-android@telefonica.com' + } + } + scm { + connection = 'scm:git:github.com/Telefonica/android-snaptesting.git' + developerConnection = 'scm:git:ssh://github.com/Telefonica/android-snaptesting.git' + url = 'https://github.com/Telefonica/android-snaptesting/tree/main' + } + withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + project.configurations.getByName("implementation").dependencies.each { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } +} + +afterEvaluate { + tasks.getByName("publishGradlePluginPublicationToMavenLocal").dependsOn("jar") + tasks.getByName("publishGradlePluginPublicationToMavenRepository").dependsOn("jar") +} diff --git a/mavencentral.gradle b/mavencentral.gradle index 35239cb..49385d7 100644 --- a/mavencentral.gradle +++ b/mavencentral.gradle @@ -11,6 +11,15 @@ apply plugin: 'maven-publish' apply plugin: 'signing' publishing { + repositories { + maven { + credentials { + username System.env.NEXUS_USER + password System.env.NEXUS_PASS + } + url "https://nexusng.tuenti.io/repository/maven-release-private/" + } + } publications { release(MavenPublication) { groupId 'com.telefonica' @@ -59,7 +68,6 @@ publishing { afterEvaluate { tasks.getByName("publishReleasePublicationToMavenLocal").dependsOn("assembleRelease") - tasks.getByName("publishReleasePublicationToSonatypeRepository").dependsOn("assembleRelease") tasks.getByName("signReleasePublication").dependsOn("assembleRelease") }