@@ -30,14 +30,137 @@ plugins {
3030 alias(libs.plugins.detekt)
3131 // needed to make renovate run without shot, as shot requires Android SDK
3232 // https://github.com/pedrovgs/Shot/issues/300
33- if ( System .getenv( " SHOT_TEST " ) == " true " ) alias(libs.plugins.shot)
33+ alias(libs.plugins.shot)
3434 id(" checkstyle" )
3535 id(" pmd" )
3636}
3737apply (from = " ${rootProject.projectDir} /jacoco.gradle.kts" )
3838
3939println (" Gradle uses Java ${Jvm .current()} " )
4040
41+ // Shot 6.1.0 is incompatible with AGP 9 - it tries to use the removed AppExtension API.
42+ // Manually register Shot tasks using the new ApplicationExtension API.
43+ run {
44+ val androidExt = extensions.getByType(com.android.build.api.dsl.ApplicationExtension ::class .java)
45+ val shotExt by lazy { extensions.getByType(ShotExtension ::class .java) }
46+
47+ // Configure ShotTask properties via reflection (Scala var setters aren't accessible from Kotlin)
48+ fun configureShotTask (
49+ task : com.karumi.shot.tasks.ShotTask ,
50+ flavorOpt : Any , buildTypeName : String , appId : String , adbPath : String
51+ ) {
52+ val cls = com.karumi.shot.tasks.ShotTask ::class .java
53+ fun setField (name : String , value : Any? ) {
54+ cls.getDeclaredField(name).apply { isAccessible = true ; set(task, value) }
55+ }
56+ setField(" flavor" , flavorOpt)
57+ setField(" buildTypeName" , buildTypeName)
58+ setField(" appId" , appId)
59+ setField(" orchestrated" , false )
60+ setField(" projectPath" , projectDir.absolutePath)
61+ setField(" buildPath" , layout.buildDirectory.get().asFile.absolutePath)
62+ setField(" shotExtension" , shotExt)
63+ setField(" directorySuffix" ,
64+ if (project.hasProperty(" directorySuffix" ))
65+ scala.Some (project.property(" directorySuffix" ).toString())
66+ else scala.Option .empty<String >())
67+ setField(" recordScreenshots" , project.hasProperty(" record" ))
68+ setField(" printBase64" , project.hasProperty(" printBase64" ))
69+ setField(" projectName" , project.name)
70+ setField(" adbPath" , adbPath)
71+ }
72+
73+ afterEvaluate {
74+ // Only register if Shot's own registration failed (no executeScreenshotTests task)
75+ if (tasks.names.none { it == " executeScreenshotTests" }) {
76+ val baseTask = tasks.register(
77+ " executeScreenshotTests" ,
78+ com.karumi.shot.tasks.ExecuteScreenshotTestsForEveryFlavor ::class .java
79+ )
80+
81+ val adbPath = try {
82+ com.karumi.shot.AdbPathExtractor .extractPath(project) ? : " "
83+ } catch (_: Throwable ) {
84+ val sdkDir = System .getenv(" ANDROID_HOME" ) ? : System .getenv(" ANDROID_SDK_ROOT" ) ? : " "
85+ " $sdkDir /platform-tools/adb"
86+ }
87+
88+ val flavors = androidExt.productFlavors.map { it.name }
89+ val buildTypes = androidExt.buildTypes.map { it.name }
90+
91+ for (buildTypeName in buildTypes) {
92+ val flavorList = flavors.ifEmpty { listOf (" " ) }
93+ for (flavorName in flavorList) {
94+ val prefix = if (flavorName.isEmpty()) buildTypeName
95+ else " $flavorName${buildTypeName.replaceFirstChar { it.uppercase() }} "
96+
97+ val flavorOpt: Any = if (flavorName.isNotEmpty())
98+ scala.Some (flavorName) else scala.Option .empty<String >()
99+ val appId = (androidExt.defaultConfig.testApplicationId
100+ ? : " ${androidExt.defaultConfig.applicationId ? : androidExt.namespace} .test" )
101+
102+ val instrTaskName =
103+ " connected${flavorName.replaceFirstChar { it.uppercase() }}${buildTypeName.replaceFirstChar { it.uppercase() }} AndroidTest"
104+
105+ // Skip variants where the instrumentation task doesn't exist
106+ val instrTask = try { tasks.named(instrTaskName) } catch (_: Throwable ) { continue }
107+
108+ val removeBefore = tasks.register(
109+ " ${prefix} RemoveScreenshotsBefore" ,
110+ com.karumi.shot.tasks.RemoveScreenshotsTask ::class .java
111+ )
112+ removeBefore.configure(Action < com.karumi.shot.tasks.RemoveScreenshotsTask > {
113+ configureShotTask(this , flavorOpt, buildTypeName, appId, adbPath)
114+ })
115+
116+ val removeAfter = tasks.register(
117+ " ${prefix} RemoveScreenshotsAfter" ,
118+ com.karumi.shot.tasks.RemoveScreenshotsTask ::class .java
119+ )
120+ removeAfter.configure(Action < com.karumi.shot.tasks.RemoveScreenshotsTask > {
121+ configureShotTask(this , flavorOpt, buildTypeName, appId, adbPath)
122+ })
123+
124+ val download = tasks.register(
125+ " ${prefix} DownloadScreenshots" ,
126+ com.karumi.shot.tasks.DownloadScreenshotsTask ::class .java
127+ )
128+ download.configure(Action < com.karumi.shot.tasks.DownloadScreenshotsTask > {
129+ configureShotTask(this , flavorOpt, buildTypeName, appId, adbPath)
130+ })
131+
132+ val execute = tasks.register(
133+ " ${prefix} ExecuteScreenshotTests" ,
134+ com.karumi.shot.tasks.ExecuteScreenshotTests ::class .java
135+ )
136+ execute.configure(Action < com.karumi.shot.tasks.ExecuteScreenshotTests > {
137+ configureShotTask(this , flavorOpt, buildTypeName, appId, adbPath)
138+ })
139+
140+ if (shotExt.runInstrumentation) {
141+ execute.configure(Action < com.karumi.shot.tasks.ExecuteScreenshotTests > {
142+ dependsOn(instrTask)
143+ dependsOn(download)
144+ dependsOn(removeAfter)
145+ })
146+ download.configure(Action < com.karumi.shot.tasks.DownloadScreenshotsTask > {
147+ mustRunAfter(instrTask)
148+ })
149+ instrTask.configure { dependsOn(removeBefore) }
150+ removeAfter.configure(Action < com.karumi.shot.tasks.RemoveScreenshotsTask > {
151+ mustRunAfter(download)
152+ })
153+ }
154+
155+ baseTask.configure(Action < com.karumi.shot.tasks.ExecuteScreenshotTestsForEveryFlavor > {
156+ dependsOn(execute)
157+ })
158+ }
159+ }
160+ }
161+ }
162+ }
163+
41164configurations.configureEach {
42165 // via prism4j, already using annotations explicitly
43166 exclude(group = " org.jetbrains" , module = " annotations-java5" )
@@ -115,8 +238,7 @@ android {
115238 buildConfigField(" boolean" , " RUNTIME_PERF_ANALYSIS" , perfAnalysis.toString())
116239
117240 // arguments to be passed to functional tests
118- testInstrumentationRunner = if (shotTest) " com.karumi.shot.ShotTestRunner"
119- else " com.nextcloud.client.TestRunner"
241+ testInstrumentationRunner = " com.karumi.shot.ShotTestRunner"
120242
121243 versionCode = versionMajor * 10000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
122244 versionName = when {
0 commit comments