Add screenshots to your Android tests

Overview

Testify — Android Screenshot Testing

Add screenshots to your Android tests

GitHub Actions Maven Central Maven Central

Expand your test coverage by including the View-layer. Testify allows you to easily set up a variety of screenshot tests in your application. Capturing a screenshot of your view gives you a new tool for monitoring the quality of your UI experience. It's also an easy way to review changes to your UI. Once you've established a comprehensive set of screenshots for your application, you can use them as a "visual dictionary". In this case, a picture really is worth a thousand words; it's easy to catch unintended changes in your view rendering by watching for differences in your captured images.

Testify screenshot tests are built on top of Android Instrumentation tests and so integrate seamlessly with your existing test suites. You can run tests and capture screenshots from within Android Studio or using the Gradle command-line tools. Testify also works well with most Continuous Integration services.

You can easily capture screenshots with different resolutions, orientations, API versions and languages by simply configuring different emulators. Testify natively supports grouping screenshot tests by device characteristics. Testify captures a bitmap of your specified View after all layout and draw calls have completed so you know that you're capturing an authentic rendering representative of what your users will see in your final product.

Set up Testify

Before building your screenshot test with Testify, make sure to set a dependency reference to the Testify plugin:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.shopify.testify:plugin:1.1.0-rc01"
    }
}

apply plugin: 'com.shopify.testify'

Device Configuration

It is required for you to turn off animations on your test device — leaving system animations turned on in the test device might cause synchronization issues which may lead your test to fail. Turn off animations from Settings by opening Developer options and turning all the following options off:

  • Window animation scale
  • Transition animation scale
  • Animator duration scale

You can find a recommended emulator configuration here.

Android Studio Plugin

Testify screenshot tests are built on top of Android Instrumentation tests and so already integrate seamlessly with existing test suites. Screenshots can be captured directly from within Android Studio or using the Gradle command-line tools.

However, the current Android Studio support relies fully on the fact that Testify tests extend ActivityTestRule and can be invoked using the built-in support for running instrumentation tests with various commands (notably sidebar icons) in Android Studio. These are limited to run and debug commands. Android Studio has no concept of recording or pulling screenshots from the device. Thus, it requires developers to drop to the Gradle Panel or command-line to fully use Testify.

With the installation of an an Intellij-platform plugin, many common Testify actions can be seamlessly integrated into your IDE. The Testify Android Studio plugin is available for Android Studio version 4.0 through 4.2 via the Intellij Marketplace.

Get from Marketplace

Write a test

Testify is a subclass of Android's ActivityTestRule. The testing framework launches the activity under test before each test method annotated with @Test and before any method annotated with @Before.

Each screenshot test method must be annotated with the @ScreenshotInstrumentation annotation.

Within your test method, you can configure the Activity as needed and call assertSame() to capture and validate your UI. The framework handles shutting down the activity after the test finishes and all methods annotated with @After are run.

@RunWith(AndroidJUnit4::class)
class MainActivityScreenshotTest {

    @get:Rule var rule = ScreenshotRule(MainActivity::class.java)

    @ScreenshotInstrumentation
    @Test
    fun default() {
        rule.assertSame()
    }
}

For additional testing scenarios, please refer to the recipe book.

Update your baseline

Testify works by referencing a PNG baseline found in your androidTest/assets directory for each test case that you write. As you write and run tests, an updated baseline image is maintained on your device or emulator. In order to update the baseline, you need to copy or pull the image from the device to your local development environment. Testify offers a variety of Gradle tasks to simplify the copying of your baseline images.

Record a new baseline

Run all the screenshot tests in your app and update the local baseline.

./gradlew screenshotRecord

Verify the tests

Run all the screenshot tests in your app and fail if any differences from the baseline are detected.

./gradlew screenshotTest

Pull images from the device

Copy images from the app_images directory on your emulator to your local androidTest/assets directory.

./gradlew screenshotPull

Erase any existing images from the device

Clear any baseline images that may be remaining on your emulator.

./gradlew screenshotClear

Generate a YAML test report

You can optionally generate a YAML test report for offline parsing by adding <meta-data android:name="testify-reporter" android:value="true" /> to your AndroidManifest.xml. Once enabled, Testify will create a report.yml cataloging the statistics about the most recent test run. You can view the report with:

./gradlew reportShow

You can copy the report.yml file to your local project directory with:

./gradlew reportPull

There are a variety of additional Gradle commands available through the Testify plugin. For advance usage, please refer to the Plugin guide.

License

MIT License

Copyright (c) 2021 Shopify

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Comments
  • Screenshot is taken before GlideImage drawable is loaded

    Screenshot is taken before GlideImage drawable is loaded

    Describe the bug I'm not sure that this is a bug, but am wondering if there is recommended way for the screenshot to have to wait to be taken until after an image is loaded.

    This issue relates to:

    • The timing of when the library takes the screenshot

    To Reproduce I have previously been simply loading a drawable into a compose Image like this:

    Image(
                    painter = painterResource(id = imageModel.drawableResId),
                    contentDescription = imageModel.contentDescription,
                    contentScale = ContentScale.Fit,
                    modifier = Modifier
                        .clip(CircleShape)
                        .size(dimensionResource(id = avatarSize.diameter))
                )
    

    Running screenshot tests with this worked and gave the result on the left in the screenshots. But we want to be able to switch over to use GlideImage to allow the image to be either loaded from a url OR a drawable like this (specifically, we are using this library https://github.com/skydoves/landscapist which uses https://github.com/bumptech/glide):

    GlideImage(
                imageModel = avatarOverlayBadge.image.imageModel, // imageModel could be of type drawable or a String url
                ...
            )
    

    This works in the app as expected and loads the image, however on the screenshot tests it seems now that the screenshot is taken before the image is loaded and gives the result on the right most of the time. We did not change anything in the screenshot test and are still using a drawable, it just seems the library takes slightly longer to show the drawable than directly using a Compose Image.

    I attempted to fix this using a CountingIdlingResource with something like:

    val idlingResource = CountingIdlingResource("AvatarImageLoadCompletionResource", true).apply {
                increment()
    }
    IdlingRegistry.getInstance().register(idlingResource)
    

    and then in the implementation code I decrement the idling resource on success of the image loading. This seemed to make the test work a rare amount of times.

    Expected behavior

    • I would expect the screenshot to look the same as the below image on the left

    Screenshots Screen Shot 2022-03-02 at 6 08 44 PM

    • Testify Key: 29-1080x1920@441dp-en_US

    Additional context Any help would be very much appreciated thank you!!

    Bug 
    opened by jennatauro 12
  • BottomSheetDialogFragment not included in screenshots?

    BottomSheetDialogFragment not included in screenshots?

    Describe the bug Trying to record the baseline screenshots for an app with a BottomSheetDialogFragment opened. The screenshot doesn't include the BottomSheetDialogFragment, only the everything that is behind it

    This issue relates to:

    • The Kotlin library
    • The Gradle plugin

    To Reproduce Steps to reproduce the behavior:

    1. App with a BottomSheetDialogFragment, open the dialog
    2. Create a test and click on 'Record baseline' on Android Studio
    3. Check the taken screenshot and the BottomSheetDialogFragment is not there

    Expected behavior The opened BottomSheetDialogFragment should be included in the screenshot

    Screenshots App with BottomSheetDialogFragment opened (disregard bottom nav menu): Screenshot_1631722353

    Recorded baseline screenshot: ModalsTest_fixedSizeScreenshotCheck

    Desktop (please complete the following information):

    • OS: MacOS
    • Version 11.5.2

    Target Android Device (please complete the following information):

    • Device: Pixel 4
    • Physical or Virtual: Android Virtual Device (Emulator)
    • API Level: 29
    • Testify Key: 29-1080x2280@440dp-en_US
    Bug 
    opened by amanzan 9
  • Remove hidePasswords from ScreenshotTestTask

    Remove hidePasswords from ScreenshotTestTask

    As discussed before, the Firebase emulator on CI does not allow changes to system settings therefore will show passwords (dotted with the last character visible) in an EditText.

    We should consider removing this step from Testify to make local screenshots more closely match Firebase.

    screen shot 2017-07-25 at 10 55 53 am
    opened by sander-m 8
  • generateDiffImages command fails

    generateDiffImages command fails

    Describe the bug I cannot generate Diff images

    This issue relates to:

    • [x] The Kotlin library
    • [x] The Gradle plugin
    • [ ] The IntelliJ Platform plugin
    • [ ] The sample code
    • [ ] The documentation

    To Reproduce Steps to reproduce the behavior:

    1. Clone this repo
    2. Install ImageMagick
    3. record baseline for MainActivityScreenshotTest
    4. change the text of MainActivityScreenshotTest to "Hello world 2"
    5. verify test -> it fails (as expected)
    6. run generateDiffImages from the CL and get the following error
    Testify-report

    Expected behavior It generates the diff

    Desktop (please complete the following information):

    • OS: MacOs
    • Versionc11.6

    Target Android Device (please complete the following information):

    • Device: Google Pixel 4a
    • Physical or Virtual: Virtual
    • API Level: 30
    • Testify Key: 30-1080x2340@440dp-de_DE
    Bug 
    opened by sergio-sastre 7
  • Replace Travis CI with GitHub Actions

    Replace Travis CI with GitHub Actions

    What does this change accomplish?

    Resolves #168

    Replace Travis CI with GitHub Actions

    ⚠️ Note: Requires #167 to be merged first.

    How have you achieved it?

    Migrate the existing Travis CI checks to GitHub workflows.

    New workflows:

    Also upgraded the baseline and test emulator to API 29.

    Notice

    This change must keep master in a shippable state; it may be shipped without further notice.

    opened by DanielJette 7
  • ActivityScenario samples

    ActivityScenario samples

    What does this change accomplish?

    This adds ActivityScenario samples, as per https://github.com/Shopify/android-testify/issues/244

    How have you achieved it?

    ...

    Tophat instructions

    ...

    Notice

    This change must keep master in a shippable state; it may be shipped without further notice.

    opened by oradkovsky 5
  • ActivityTestRule deprecated

    ActivityTestRule deprecated

    Is your feature request related to a problem? Please describe. Please share any plans regarding moving away from using https://developer.android.com/reference/androidx/test/rule/ActivityTestRule.html due to deprecation.

    This feedback relates to:

    • [x] The Kotlin library
    • [ ] The Gradle plugin
    • [ ] The IntelliJ Platform plugin
    • [ ] The sample code
    • [ ] The documentation

    Describe the solution you'd like The plugin heavily relies on https://developer.android.com/reference/androidx/test/rule/ActivityTestRule.html, while latter being deprecated.

    Describe alternatives you've considered Using whatever coming instead of https://developer.android.com/reference/androidx/test/rule/ActivityTestRule.html

    Additional context NA

    Feedback 
    opened by oradkovsky 5
  • Screens which inflate views at a later time are never captured

    Screens which inflate views at a later time are never captured

    Describe the bug Regardless of which capture method I try, I can never actually capture anything on screen which either inflates views after creation or adds views to a recyclerview. Using Screenshot.capture() as a fourth capture method would fix this. Screenshot.capture() gives you access to a bitmap so comparing shouldn't been an issue.

    This issue relates to:

    • [x] The Kotlin library
    • [ ] The Gradle plugin
    • [ ] The IntelliJ Platform plugin
    • [ ] The sample code
    • [ ] The documentation

    To Reproduce Try to screenshot any activity with just a recyclerview into which you add views after onCreate of the  Activity.

    Expected behavior Screenshot is correctly taken.

    Screenshots If applicable, add screenshots to help explain your problem.

    Desktop (please complete the following information):

    • OS: MacOS

    Target Android Device (please complete the following information):

    • Device: Pixel
    • Physical or Virtual: Android Virtual Device (Emulator)
    • API Level: 28
    • Testify Key: [e.g. Output from ./gradlew testifyKey]

    Additional context Add any other context about the problem here.

    Bug 
    opened by videogreg93 4
  • AGP 7: Cannot query the value of property 'applicationId' because configuration of project ':app' has not completed yet

    AGP 7: Cannot query the value of property 'applicationId' because configuration of project ':app' has not completed yet

    Describe the bug

    FAILURE: Build failed with an exception.
    
    * What went wrong:
    A problem occurred configuring project ':app'.
    > Cannot query the value of property 'applicationId' because configuration of project ':app' has not completed yet.
    
    * Try:
    Run with --info or --debug option to get more log output. Run with --scan to get full insights.
    
    * Exception is: org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':app'. at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75) at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:68) at org.gradle.configuration.project.LifecycleProjectEvaluator.access$400(LifecycleProjectEvaluator.java:51) at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:191) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:105) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:250) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:310) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:310) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:291) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:249) at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:91) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:63) at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:723) at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:150) at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:41) at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:69) at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:46) at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:64) at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40) at org.gradle.initialization.DefaultGradleLauncher.prepareProjects(DefaultGradleLauncher.java:226) at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:164) at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149) at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:125) at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:71) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213) at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67) at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56) at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53) at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47) at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35) at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35) at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:66) at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32) at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90) at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44) at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:59) at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86) at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:58) at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30) at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:34) at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53) at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:33) at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:28) at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104) at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55) at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64) at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37) at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54) at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67) at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50) at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36) at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36) at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25) at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59) at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31) at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58) at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42) at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47) at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31) at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78) at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75) at org.gradle.util.Swapper.swap(Swapper.java:38) at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52) at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) Caused by: java.lang.IllegalStateException: Cannot query the value of property 'applicationId' because configuration of project ':app' has not completed yet. at org.gradle.api.internal.provider.AbstractProperty$NonFinalizedValue.maybeFinalizeOnRead(AbstractProperty.java:368) at org.gradle.api.internal.provider.AbstractProperty.beforeRead(AbstractProperty.java:229) at org.gradle.api.internal.provider.AbstractProperty.calculateOwnValue(AbstractProperty.java:126) at org.gradle.api.internal.provider.AbstractMinimalProvider.get(AbstractMinimalProvider.java:84) at com.android.build.gradle.internal.api.BaseVariantImpl.getApplicationId(BaseVariantImpl.java:235) at com.android.build.gradle.internal.api.TestVariantImpl_Decorated.getApplicationId(Unknown Source) at com.shopify.testify.internal.GradleProjectExtensionsKt.getInferredDefaultTestVariantId(GradleProjectExtensions.kt:79) at com.shopify.testify.TestifySettingsFactory$Companion.create(TestifyExtension.kt:165) at com.shopify.testify.TestifyPlugin$AfterEvaluate.execute(TestifyPlugin.kt:63) at com.shopify.testify.TestifyPlugin$AfterEvaluate.execute(TestifyPlugin.kt:61) at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:100) at org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction$1.run(DefaultListenerBuildOperationDecorator.java:152) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction.execute(DefaultListenerBuildOperationDecorator.java:149) at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:95) at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:83) at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:43) at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:245) at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:157) at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61) at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:346) at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:249) at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:141) at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) at com.sun.proxy.$Proxy58.afterEvaluate(Unknown Source) at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:183) at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:180) at org.gradle.api.internal.project.DefaultProject.stepEvaluationListener(DefaultProject.java:1454) at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:189) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:105) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:250) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:310) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:310) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:291) at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:249) at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:91) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:63) at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:723) at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:150) at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:41) at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:69) at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:46) at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:64) at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71) at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40) at org.gradle.initialization.DefaultGradleLauncher.prepareProjects(DefaultGradleLauncher.java:226) at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:164) at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149) at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:125) at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:71) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213) at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67) at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56) at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53) at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47) at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35) at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35) at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:66) at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32) at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90) at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75) at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62) at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76) at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44) at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:59) at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86) at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:58) at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30) at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:34) at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53) at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:33) at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:28) at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104) at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55) at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64) at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37) at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54) at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67) at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50) at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36) at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36) at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25) at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59) at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31) at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58) at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42) at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47) at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31) at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78) at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75) at org.gradle.util.Swapper.swap(Swapper.java:38) at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104) at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52) at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)

    This issue relates to:

    • [ ] The Kotlin library
    • [x] The Gradle plugin
    • [ ] The IntelliJ Platform plugin
    • [ ] The sample code
    • [ ] The documentation

    To Reproduce

    1. With any existing Android application that has the Testify plugin applied,
    2. Edit your gradle-wrapper.properties to: distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
    3. Upgrade your AGP to com.android.tools.build:gradle:7.0.0
    4. Attempt to build your application

    Expected behavior A clear and concise description of what you expected to happen.

    Screenshots If applicable, add screenshots to help explain your problem.

    Desktop (please complete the following information):

    • OS: MacOS
    • Version [e.g. 10.15.5, 10]

    Target Android Device (please complete the following information):

    • Device: [e.g. Pixel, S20]
    • Physical or Virtual: [e.g. Android Virtual Device (Emulator), Firebase Cloud Image, Samsung Test Lab, Physical Device]
    • API Level: [e.g. 21, 28, 30]
    • Testify Key: [e.g. Output from ./gradlew testifyKey]

    Additional context

    Testify version 1.1.0-beta1

    Bug Gradle Plugin 
    opened by DanielJette 4
  • Apply annotations to task properties for up-to-date checks

    Apply annotations to task properties for up-to-date checks

    What does this change accomplish?

    Resolves https://github.com/Shopify/android-testify/issues/225

    Adds missing annotations on public properties in Tasks to assist with up-to-date checks. Missing annotations are now errors in Gradle 7.0+.

    As suggested in https://github.com/Shopify/android-testify/issues/225#issuecomment-879917888, I've confirmed this specific issue doesn't affect our ABI at all, and remains interopable with existing Gradle versions, at least with regards to the Plugin.

    How have you achieved it?

    Applied Internal to our public properties which were public for task subclasses.

    Tophat instructions

    1. Bump your Gradle to 7.0.2
    2. Run ./gradlew --warning-mode all clean Sample:screenshotTest --console=plain
    3. Tasks should not fail with something like the following
    ❯ ./gradlew screenshotRecord
    > Task :app:deviceLocale FAILED
    FAILURE: Build failed with an exception.
    * What went wrong:
    Some problems were found with the configuration of task ':app:deviceLocale' (type 'LocaleTask').
      - Type 'LocaleTask' property 'description' is missing an input or output annotation.
        Reason: A property without annotation isn't considered during up-to-date checking.
        Possible solutions:
          1. Add an input or output annotation.
          2. Mark it as @Internal.
        Please refer to https://docs.gradle.org/7.0/userguide/validation_problems.html#missing_annotation for more details about this problem.
    
    opened by jaredh 4
  • Create an Android Studio Plugin for Testify

    Create an Android Studio Plugin for Testify

    Android Studio Plugin for Testify

    TL:DR

    Create a native plugin for Testify to fully integrate all the key Testify commands directly into Android Studio.

    What?

    Develop an Intellij Platform Plugin suitable for use in Android Studio to seamlessly integrate the full Testify feature set directly into the IDE.

    Why?

    Testify screenshot tests are built on top of Android Instrumentation tests and so already integrate seamlessly with existing test suites. Screenshots can be captured directly from within Android Studio or using the Gradle command-line tools.

    However, the current Android Studio support relies fully on the fact that Testify tests extend ActivityTestRule and can be invoked using the built-in support for running instrumentation tests with various commands (notably sidebar icons) in Android Studio. These are limited to run and debug commands. Android Studio has no concept of recording or pulling screenshots from the device. Thus, it requires developers to drop to the Gradle Panel or command-line to fully use Testify. This project will enhance the developer experience by adding fully integrated IDE UI for all relevant Testify commands.

    How?

    Products based on the IntelliJ Platform can be modified and adjusted for custom purposes by adding plugins. Tool integration makes it possible to manipulate third-party tools and components directly from the IDE without switching contexts.

    This implies:

    • Implementation of additional actions
    • Related UI components
    • Access to external resources

    Android Studio plugins extend or add functionality to the Android Studio IDE. Plugins can be written in Kotlin or Java, or a mix of both, and are created using IntelliJ IDEA and the IntelliJ Platform.

    Resources

    IntelliJ Plugin 
    opened by DanielJette 4
Releases(final-release)
  • final-release(Aug 8, 2022)

    Warning

    🚚 We have moved! 🚧

    Effective August 1, 2022, all development will occur in our new repository: https://github.com/ndtp/android-testify/

    Please consult the Migration Guide for information on how to move your project to the latest artifacts.

    Source code(tar.gz)
    Source code(zip)
  • v0.1.3(May 10, 2022)

  • 1.2.0-alpha01(Jan 18, 2022)

  • 1.1.0(Nov 2, 2021)

  • v0.1.2(Sep 16, 2021)

  • 1.1.0-rc01(Sep 9, 2021)

    1.1.0-rc01

    Library

    Bug fixes

    • https://github.com/Shopify/android-testify/issues/228, https://github.com/Shopify/android-testify/issues/215 Account for uneven processing chunk sizes. As Testify processes, it divides the images into chunks for faster, parallel processing. A bug in the original code assumed that each processing chunk would be equally sized. This caused an out-of-bounds exception in any case where the number of pixels in the image could not be evenly divided.

    • https://github.com/Shopify/android-testify/issues/216 You can now use ScreenshotRule.setExactness() in conjunction with ScreenshotRule.defineExclusionRects(). You can now define both an exclusion area and an exactness threshold.

    Added

    • Method ScreenshotRule.getExactness():Float? added.

    Changes

    • Method ScreenshotRule.setExactness(exactness: Float?): ScreenshotRule<T> now accepts a nullable value.

    • TestifyFeatures.GenerateDiffs now visualizes exclusion regions and the exactness tolerance. When enabled, GenerateDiffs will write a companion image for your screenshot test which can help you more easily identify which areas of your test have triggered the screenshot failure. Diff files are only generated for failing tests. The generated file will be created in the same directory as your baseline images. Diff files can be pulled from the device using :screenshotPull.

      • Black pixels are identical between the baseline and test image
      • Grey pixels have been excluded from the comparison
      • Yellow pixels are different, but within the Exactness threshold
      • Red pixels are different
    • Method DeviceIdentifier.getDeviceDimensions(context: Context): Pair<Int, Int> is now public.

    Source code(tar.gz)
    Source code(zip)
  • 1.1.0-beta3(Aug 11, 2021)

    1.1.0-beta3

    Library

    Gradle Plugin

    Bug fixes

    • Several internal changes to support Gradle 7.
    • Fix https://github.com/Shopify/android-testify/issues/225 Apply annotations to task properties for up-to-date checks. Adds missing annotations on public properties in Tasks to assist with up-to-date checks. Missing annotations are now errors in Gradle 7.0+.
    • Fix https://github.com/Shopify/android-testify/issues/234 Using the Testify plugin with AGP 7+ would generate the error "Cannot query the value of property 'applicationId' because configuration of project ':app' has not completed yet". Fixed by catching the error and providing a sensible default. In the case where the test packaged ID is incorrectly inferred, the user can specify testify { testPackageId "my.custom.package.test" } in their build.gradle to override the inferred value.

    Library

    Changes

    • Renamed the rootViewId setter on ScreenshotTestRule for better Java interoperability. It has been renamed to setRootViewIdResource.
    Source code(tar.gz)
    Source code(zip)
  • v0.1.1(May 27, 2021)

  • 1.1.0-beta2(May 17, 2021)

    1.1.0-beta2

    Library

    Bug fixes

    • Fix "ParallelPixelProcessor.kt:90 - java.lang.IndexOutOfBoundsException: index=315250 out of bounds (limit=315250)" https://github.com/Shopify/android-testify/issues/215

    Updates

    • AGP from 4.2.0-beta6 to 4.2.0

    Sample

    Changes

    • Remove kotlin-android-extensions from Sample and replace with viewBinding
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0-beta1(Apr 20, 2021)

  • 1.0.0-rc3(Jan 26, 2021)

  • v0.1.0(Jan 26, 2021)

  • 1.0.0-rc2(Oct 27, 2020)

    1.0.0-rc2

    Library

    Bug fixes

    • Increase the timeout values on orientation change. Addresses various Failed to apply requested rotation and Activity did not resume errors when invoking setOrientation.

    Updates

    • Android Gradle Plugin to 4.1.0
    • Gradle Wrapper to 6.5

    Gradle Plugin

    Bug fixes

    • Access task names lazily via names property. We were previously accessing task names in a way which resulted in early configuration of tasks resulting in Gradle failing to sync on the latest versions of Gradle and Android Gradle Plugin when custom lint checks were used in a project. Likely related to https://issuetracker.google.com/issues/67482030#comment2. Use the TaskContainer.names which doesn't cause all tasks to be resolved immediately.

    Sample App

    • Update Sample application to use a Pixel 3a API 30 baseline emulator.
    Source code(tar.gz)
    Source code(zip)
    plugin-1.0.0-rc2-javadoc.jar(159.88 KB)
    plugin-1.0.0-rc2-sources.jar(261 bytes)
    plugin-1.0.0-rc2.jar(107.54 KB)
    plugin-1.0.0-rc2.pom(1.33 KB)
    testify-1.0.0-rc2-javadoc.jar(135.16 KB)
    testify-1.0.0-rc2-sources.jar(57.41 KB)
    testify-1.0.0-rc2.aar(88.76 KB)
    testify-1.0.0-rc2.pom(2.21 KB)
  • 1.0.0-rc1(Sep 4, 2020)

    1.0.0-rc1

    Changes

    • Replace Travis CI with GitHub Actions. https://github.com/Shopify/android-testify/actions

    Library

    Bug fixes

    • Fix https://github.com/Shopify/android-testify/issues/138 Introduce the setFocusTarget method on ScreenshotRule which allows for keyboard focus to be placed on an explicit View.

    • Fix https://github.com/Shopify/android-testify/issues/165 Increase the timeout on the ActivityLifecycleMonitor to 5 seconds to allow for the rotation to complete. Deregister the lifecycle callback.

    • Fix https://github.com/Shopify/android-testify/issues/166 Replace the existing FuzzyCompare algorithm with CIEDE2000. Calculate the colour difference value between two colours in lab space. The CIELAB color space (also known as CIE L* a* b* or sometimes abbreviated as simply "Lab" color space) is a color space defined by the International Commission on Illumination (CIE) in 1976. It expresses color as three values: L* for the lightness from black (0) to white (100), a* from green (−) to red (+), and b* from blue (−) to yellow (+). CIELAB was designed so that the same amount of numerical change in these values corresponds to roughly the same amount of visually perceived change. The CIE calls their distance metric ΔE * ab where delta is a Greek letter often used to denote difference, and E stands for Empfindung; German for "sensation". If deltaE < 1 then the difference can't recognized by human eyes.

    Plugin

    Changed

    • The gradle plugin has moved to Plugins/Gradle
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta5(Jun 30, 2020)

    1.0.0-beta5

    Library

    New

    • Add defineExclusionRects method to ScreenshotRule. You can now use defineExclusionRects to exclude regions from a bitmap comparison.

    Updates

    • Android Gradle Plugin to 4.0.0
    • AndroidX Junit to 1.1.1
    • AndroidX Test Rules to 1.2.0
    • Kotlin to 1.3.72
    • Mockito to 3.3.3
    Source code(tar.gz)
    Source code(zip)
    plugin-1.0.0-beta5-javadoc.jar(159.78 KB)
    plugin-1.0.0-beta5-sources.jar(261 bytes)
    plugin-1.0.0-beta5.jar(107.62 KB)
    plugin-1.0.0-beta5.pom(1.33 KB)
    testify-1.0.0-beta5-javadoc.jar(131.66 KB)
    testify-1.0.0-beta5-sources.jar(53.90 KB)
    testify-1.0.0-beta5.aar(83.08 KB)
    testify-1.0.0-beta5.pom(2.04 KB)
  • 1.0.0-beta4(Jun 17, 2020)

    Plugin

    New

    • Added autoImplementLibrary member to the testify extension. Defaults to true. When set to false, prevents the Plugin from automatically adding a Library androidTestImplementation dependency to your project. This is useful for local debugging or if you require a different version of the library and plugin.

    Library

    Bug fixes

    • Fixed issue #153 - Orientation change will now be reliably applied regardless of how many times you invoke setOrientation in a single test class.
    Source code(tar.gz)
    Source code(zip)
    plugin-1.0.0-beta4-javadoc.jar(159.78 KB)
    plugin-1.0.0-beta4-sources.jar(261 bytes)
    plugin-1.0.0-beta4.jar(107.67 KB)
    plugin-1.0.0-beta4.pom(1.33 KB)
    testify-1.0.0-beta4-javadoc.jar(127.27 KB)
    testify-1.0.0-beta4-sources.jar(51.30 KB)
    testify-1.0.0-beta4.aar(79.47 KB)
    testify-1.0.0-beta4.pom(2.04 KB)
  • 1.0.0-beta3(Jun 11, 2020)

  • 0.10.0(Jun 1, 2020)

  • 1.0.0-beta1(May 2, 2020)

    1.0.0-beta1 -- May 2, 2020

    Breaking changes introduced. Bumped Testify to 1.0.0-beta1

    Library

    New

    • :warning: Screenshot images now are written to subdirectories under the screenshot directory on device. Screenshot paths on device now include the full device key and such are properly defined to fully represent the device configuration used to generate the images. This can be a breaking change if you use custom scripting to pull/access screenshot images from the emulator.
    • :warning: The emulator locale is now encoded into the device key by default. Previously, only the language was encoded in the key. You can disable this behavior using the Locale (testify-experimental-locale) feature. This change will require you to rename your baseline image directory to include the full locale path.
    • TestifyFeatures Enable or disable some features at runtime via manifest entry or in code.
    • ScreenshotRule.withExperimentalFeatureEnabled Used in conjunction with TestifyFeatures, you can selectively enable an experimental feature on the test rule. Features are reset after assertSame is called.
    • TestifyLayout can now accept a layout resource name or a layout resource ID. This is useful for library projects where resource IDs are not stable.
    • Two new bitmap capture algorithms have been added. You can now select between PixelCopy, Canvas and DrawingCache capture methods. These can be enabled with by passing either CanvasCapture or PixelCopyCapture to withExperimentalFeatureEnabled, or by enabling testify-canvas-capture or testify-experimental-capture in your manifest.

    Bug Fixes

    • Fully support changing locale during testing on API 19+
    • Correctly access external files directory on API 28+
    • Correctly detect the locale on API 22
    • SoftwareRenderViewModification now qualifies all views, not just ImageView
    • setFontScale now works correctly on API 21+

    Testing changes

    • Add TestActivity to androidTest configuration of Library for testing
    • Update Library baseline test images to support en_US locale
    • Add tests for Experimental PixelCopy API
    • Update DeviceIdentifierTest to work with locale (not language)
    • Add tests for TestifyFeatures

    Updates

    • Gradle from 4.10.2 to 6.2.1
    • Android Gradle Plugin from 3.3.2 to 3.6.1
    • ktlint from 0.29.0 to 0.36.0
    • Compile and Target SDK from 28 to 29
    • AndroidX ConstraintLayout from 2.0.0-alpha3 to 2.0.0-beta4
    • AndroidX AppCompat from 1.0.0 to 1.1.0
    • AndroidX Core Ktx from 1.0.1 to 1.1.0
    • AndroidX Test Core from 2.0.0-rc01 to 2.1.0
    • AndroidX Test Espresso from 3.1.0 to 3.2.0
    • Dokka from 0.9.18 to 0.10.0
    • Kotlin from 1.3.21 to 1.3.70
    • Mockito2 from 2.23.0 to 2.28.2
    • Travis CI now downloads and installs Android SDK 29
    • Split Bintray gradle script to separate file
    • Kotlin stdlib8 from stdlib7

    Plugin:

    New

    • :warning: Breaking Change The testify {} gradle extension been restructured. Testify no longer requires the testify extension to be defined in your project. This is particularly valuable for Android library projects as Testify can now correctly infer most settings automatically. Most settings can now be inferred. Testify now supports multiple flavor dimensions and product flavors.
      • testContextId has been deleted and is no longer needed.
      • applicationIdSuffix has been deleted. Its value can now be inferred.
      • installTask has been added. You can specify which task to run to install your APK. This is automatically inferred, but you may wish to override the default value.
      • installAndroidTestTask has been added. You can specify which task to run to install the Android Instrumentation test package. This is automatically inferred, but you may wish to override the default value. You can view the inferred extension values by running ./gradlew testifySettings
    • Added verbose logging support. Add -Pverbose=true to your gradle commands to enable verbose logging. e.g. ./gradlew Sample:screenshotTest -Pverbose=true
    • The device key is now based off the emulator locale, not language. (e.g. en_US instead of just en)
    • Testify plugin no longer requires adb root access. screenshotPull and screenshotClear can now work on any device or Google Play emulator image.

    Bug Fixes

    • screenshotClear will now properly delete files when running from a Windows client.
    • Screenshots are now copied to the correct baseline directory when captured on a landscape emulator.
    • Screenshots are now copied to the correct baseline directory regardless of emulator locale.
    • Testify now more accurately detects the appropriate install task for your project. Testify relies on this to correctly insall your APK for testing and can now infer more project types correctly.
    • Correctly set and get the locale language and region on API 22.
    • Correctly support overridden display density.

    Testing changes

    • JVM tests now log status to console.

    Updates

    • Update to kotlin stdlib8 from stdlib7.

    Sample App:

    • Update compile SDK from 28 to 29
    • Update target SDK from 28 to 29
    • Extend MaterialComponents theme instead of AppCompat theme
    • Re-record baseline using locale key
    Source code(tar.gz)
    Source code(zip)
    plugin-1.0.0-beta1-javadoc.jar(159.15 KB)
    plugin-1.0.0-beta1-sources.jar(261 bytes)
    plugin-1.0.0-beta1.jar(107.25 KB)
    plugin-1.0.0-beta1.pom(1.33 KB)
    testify-1.0.0-beta1-javadoc.jar(123.92 KB)
    testify-1.0.0-beta1-sources.jar(48.57 KB)
    testify-1.0.0-beta1.aar(72.90 KB)
    testify-1.0.0-beta1.pom(2.04 KB)
  • 1.0.0-alpha1(Apr 5, 2019)

Share MPS code snippets. More than just screenshots.

skadi gist Share MPS code snippets. More than just screenshots. Repository Content ide-plugin: MPS Plugin that creates a gist from the IDE written in

Skadi 6 Jan 8, 2023
🎓 Learning Kotlin Coroutines for Android by example. 🚀 Sample implementations for real-world Android use cases. 🛠 Unit tests included!

Kotlin Coroutines - Use Cases on Android ?? Learning Kotlin Coroutines for Android by example. ?? Sample implementations for real-world Android use ca

Lukas Lechner 2.1k Jan 3, 2023
A simple android application for IQ tests, contains 40 questions and issues.

IQ-Android A simple android application for IQ tests, contains 40 questions and issues. Compatible with API Level 14 and higher Support only arabic la

Majd Zain AL Deen 2 Nov 4, 2022
Running Axon Server in Testcontainers tests.

Axon Server TestContainer Running axon server in testcontainers tests. Idea Run an Axon Server docker container from within your (junit) tests. Usage

holixon 3 Nov 21, 2022
Gradle plugin to manage tests which should only run nightly and not every time a CI/CD pipeline builds.

NightlyTestsPlugin Gradle Plugin to configure which (j)Unit tests should only be run nightly and not everytime a CI/CD pipeline is triggered. Usage To

VISUS Health IT GmbH 0 Dec 7, 2021
Titanium Android module: Add a view to a BottomNavigation

ti.bottomNavView Titanium Android module that allows you to add a custom view to a BottomNavigation: Install <module>ti.bottomNavView</module> Method

Michael Gangolf 5 Aug 23, 2022
Add Bubble Navigation Bar in Android Jetpack Compose.

BubbleNavigationBarCompose How it looks Setup Open the file settings.gradle (it looks like that) dependencyResolutionManagement { repositoriesMode

Emir Demirli 11 Jan 5, 2023
Add Expandable Horizontal Pager in Android Jetpack Compose.

ExpandableHorizontalPagerCompose Add Expandable Horizontal Pager in Android Jetpack Compose. How it looks Usage BoxWithConstraints( modifier = Mod

Emir Demirli 15 Jan 15, 2023
Add Ios Swipe Search TextField Component in Android Jetpack Compose.

IosSwipeSearchCompose Add Ios Swipe Search TextField Component in Android Jetpack Compose. How it looks Usage val text = remember { mutableStateOf("")

Emir Demirli 11 Jan 9, 2023
Praveen Kumar Kumaresan 0 Jan 17, 2022
Add page & limit as GET parameter to get all titans data in a paginated format

Add page & limit as GET parameter to get all titans data in a paginated format (default values : page=1 and limit=3). Here the page param is the page number which you require and limit is the maximum number of items per page. Sample Request :

krishna chaitanya 1 Jan 31, 2022
Kotlin extension function provides a facility to "add" methods to class without inheriting a class or using any type of design pattern

What is Kotlin Extension Function ? Kotlin extension function provides a facility to "add" methods to class without inheriting a class or using any ty

mohsen 21 Dec 3, 2022
To help to promote your android app by prompting users to rate your app in a bottom Sheet.

RateBottomSheet This an Android library to help to promote your Android App by prompting users to rate your app in the Google Play Store with a materi

Farham Hosseini 5 Jul 8, 2022
Binding your extras more easier, more simpler for your Android project

Ktan Ktan make your intent / arguments more easier and readable. And most important, will help you bind all extras for your activity / fragment. And a

Ade Fruandta 3 Jan 7, 2023
Easy app for managing your files without ads, respecting your privacy & security

Simple File Manager Can also be used for browsing root files and SD card content. You can easily rename, copy, move, delete and share anything you wis

Simple Mobile Tools 1.2k Dec 29, 2022
TakeNotes, taking care of your tasks and your health

Take Notes - Para tornar sua rotina mais Saudável TakeNotes, cuidando de suas tarefas e de sua saúde Sobre • Funcionalidades • Layout • Como executar

null 0 Dec 7, 2021
Simple view which allow you to customise your pizza's toppings and size as per your choice.

TwistedPizzaToppingsView Overview Simple view which allows options to customize your pizza toppings and size as per your choice. Features Android 12 s

MindInventory 19 Dec 18, 2022