android / android-test

An extensive framework for testing Android apps
https://android.github.io/android-test
Apache License 2.0
1.16k stars 311 forks source link

androidx.test.espresso:espresso-core 3.6.0 - Didn't find class "androidx.test.platform.concurrent.DirectExecutor" #2247

Closed wezley98 closed 3 months ago

wezley98 commented 3 months ago

Description

Receiving the following ui-test error after upgrading to espresso 3.6.0

Steps to Reproduce

toml

espresso = "3.6.0"
androidx-test = "1.6.0"
mockk = "1.13.11"

androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" }
androidx-test-services = { module = "androidx.test.services:test-services", version = "1.5.0" }
androidx-test-monitor = { module = "androidx.test:monitor", version.ref = "androidx-test" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version = "1.2.0" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
androidx-test-web = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }
androidx-test-orchestrator = { module = "androidx.test:orchestrator", version = "1.5.0" }
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
androidx-test-benchmark = { module = "androidx.benchmark:benchmark-macro-junit4", version = "1.2.4" }
test-mockk = { module = "io.mockk:mockk-android", version.ref = "mockk" }
test-mockk-kotlin = { module = "io.mockk:mockk", version.ref = "mockk" }
test-turbine = { module = "app.cash.turbine:turbine", version = "1.1.0" }

Expected Results

Tests run as expected without crashing.

Actual Results

Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.test.platform.concurrent.DirectExecutor" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.base.jar", zip file "/data/app/~~Uy3jTB7ZOqePIBnt7HXX_Q==/com.xxxx.xxxxx.debug.test-OgbO7f1zKdjx5KAz3cXZzg==/base.apk", zip file "/data/app/~~ywrK7Vn0-7iyqN2hw9cxcA==/com.xxxx.xxxx.debug-lqGeMrEJMvec5svx4SC9KQ==/base.apk", zip file "/data/user/0/com.bskyb.sportnews.debug/app_dxmaker_cache/Generated_1520517303.jar"],nativeLibraryDirectories=[/data/app/~~Uy3jTB7ZOqePIBnt7HXX_Q==/com.bskyb.sportnews.debug.test-OgbO7f1zKdjx5KAz3cXZzg==/lib/arm64, /data/app/~~ywrK7Vn0-7iyqN2hw9cxcA==/com.bskyb.sportnews.debug-lqGeMrEJMvec5svx4SC9KQ==/lib/arm64, /data/app/~~Uy3jTB7ZOqePIBnt7HXX_Q==/com.bskyb.sportnews.debug.test-OgbO7f1zKdjx5KAz3cXZzg==/base.apk!/lib/arm64-v8a, /data/app/~~ywrK7Vn0-7iyqN2hw9cxcA==/com.bskyb.sportnews.debug-lqGeMrEJMvec5svx4SC9KQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)

AndroidX Test and Android OS Versions

See Above.

Link to a public git repo demonstrating the problem:

wezley98 commented 3 months ago

Using the exact dependencies, but with espresso = "3.5.0" works fine.

brettchabot commented 3 months ago

My guess is your config is forcing an older. version of androidx.test:monitor. I'm guessing that is what the ''androidx-test = "1.6.0"' refers to. androidx.test artifacts are versioned independently - it is dangerous to have one constant for version.

wezley98 commented 3 months ago

@brettchabot Thanks for getting back to me I also just tried.

toml

espresso = "3.6.0"

androidx-test-core = { module = "androidx.test:core", version = "1.6.0" }
androidx-test-services = { module = "androidx.test.services:test-services", version = "1.5.0" }
androidx-test-monitor = { module = "androidx.test:monitor", version = "1.7.0" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version = "1.2.0" }
androidx-test-runner = { module = "androidx.test:runner", version = "1.6.0" }
androidx-test-rules = { module = "androidx.test:rules", version = "1.6.0" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
androidx-test-web = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }
androidx-test-orchestrator = { module = "androidx.test:orchestrator", version = "1.5.0" }
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
androidx-test-benchmark = { module = "androidx.benchmark:benchmark-macro-junit4", version = "1.2.4" }

Still the same issue.

This is the full stacktrace if it helps?

java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/test/platform/concurrent/DirectExecutor;
at androidx.test.espresso.InteractionResultsHandler.gatherAnyResult(InteractionResultsHandler.java:52)
at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:383)
at androidx.test.espresso.ViewInteraction.desugaredPerform(ViewInteraction.java:212)
at androidx.test.espresso.ViewInteraction.perform(ViewInteraction.java:140)
at androidx.test.espresso.web.sugar.Web$WebInteraction.doEval(Web.java:300)
at androidx.test.espresso.web.sugar.Web$WebInteraction.withElement(Web.java:226)
at com.sky.sport.robots.WebViewRobot.assertWebContent(WebViewRobot.kt:32)
at com.sky.sport.robots.WebViewRobot.assertWebContent$default(WebViewRobot.kt:30)
at com.sky.sport.ui.article.ArticleModalLinkTest.test_article_with_unlocked_video_does_not_open_modal$lambda$8(ArticleModalLinkTest.kt:128)
at com.sky.sport.ui.article.ArticleModalLinkTest.$r8$lambda$zHFGxhjXXKZnIETijvbXyHoOn9w(Unknown Source:0)
at com.sky.sport.ui.article.ArticleModalLinkTest$$ExternalSyntheticLambda7.invoke(D8$$SyntheticClass:0)
at com.sky.sport.robots.WebViewRobotKt.article(WebViewRobot.kt:19)
at com.sky.sport.ui.article.ArticleModalLinkTest.test_article_with_unlocked_video_does_not_open_modal(ArticleModalLinkTest.kt:127)
... 54 trimmed
Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.test.platform.concurrent.DirectExecutor" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.base.jar", zip file "/data/app/~~iglW2KN1GjacQjeXzEKk_A==/com.bskyb.sportnews.debug.test-wzF8Us8EeGDtRfPteZNJOw==/base.apk", zip file "/data/app/~~u7u03-m896uQ_fw8fc3opw==/com.bskyb.sportnews.debug-JQtkrmeuRycDaE4t1wP2bQ==/base.apk", zip file "/data/user/0/com.bskyb.sportnews.debug/app_dxmaker_cache/Generated_1520517303.jar"],nativeLibraryDirectories=[/data/app/~~iglW2KN1GjacQjeXzEKk_A==/com.bskyb.sportnews.debug.test-wzF8Us8EeGDtRfPteZNJOw==/lib/arm64, /data/app/~~u7u03-m896uQ_fw8fc3opw==/com.bskyb.sportnews.debug-JQtkrmeuRycDaE4t1wP2bQ==/lib/arm64, /data/app/~~iglW2KN1GjacQjeXzEKk_A==/com.bskyb.sportnews.debug.test-wzF8Us8EeGDtRfPteZNJOw==/base.apk!/lib/arm64-v8a, /data/app/~~u7u03-m896uQ_fw8fc3opw==/com.bskyb.sportnews.debug-JQtkrmeuRycDaE4t1wP2bQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
... 68 more

The crash is tracing back to this method in our code, which is called with: assertWebContent("Unlocked video", HEADING_CLASS_NAME)

fun assertWebContent(text: String, locatorName: String, locatorType: Locator = Locator.CLASS_NAME) {
        onWebView(withId(R.id.sky_web_component))
            .withElement(findElement(locatorType, locatorName))
            .check(
                WebViewAssertions.webMatches(
                    DriverAtoms.getText(),
                    CoreMatchers.containsString(text)
                )
            )
    }

I've also tried

build.gradle.kts

configurations.all {
        resolutionStrategy {
            force("androidx.test:monitor:1.7.0")
        }
    }

Which results in a different crash

java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/test/internal/platform/app/ActivityInvoker$-CC;
at androidx.test.core.app.InstrumentationActivityInvoker.getIntentForActivity(Unknown Source:0)
at androidx.test.core.app.ActivityScenario.<init>(ActivityScenario.java:180)
at androidx.test.core.app.ActivityScenario.launch(ActivityScenario.java:201)
at androidx.test.ext.junit.rules.ActivityScenarioRule.lambda$new$0(ActivityScenarioRule.java:77)
at androidx.test.ext.junit.rules.ActivityScenarioRule$$ExternalSyntheticLambda1.get(Unknown Source:2)
at androidx.test.ext.junit.rules.ActivityScenarioRule.before(ActivityScenarioRule.java:110)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:50)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$apply$1$evaluate$1.invoke(AndroidComposeTestRule.android.kt:272)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$apply$1$evaluate$1.invoke(AndroidComposeTestRule.android.kt:271)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$AndroidComposeUiTestImpl.withDisposableContent(ComposeUiTest.android.kt:495)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1$1.invoke(ComposeUiTest.android.kt:327)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withComposeIdlingResource(ComposeUiTest.android.kt:379)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withComposeIdlingResource(ComposeUiTest.android.kt:230)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1.invoke(ComposeUiTest.android.kt:326)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withWindowRecomposer(ComposeUiTest.android.kt:353)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withWindowRecomposer(ComposeUiTest.android.kt:230)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1.invoke(ComposeUiTest.android.kt:325)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withTestCoroutines(ComposeUiTest.android.kt:366)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withTestCoroutines(ComposeUiTest.android.kt:230)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1.invoke(ComposeUiTest.android.kt:324)
at androidx.compose.ui.test.junit4.EspressoLink.withStrategy(EspressoLink.android.kt:66)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invoke(ComposeUiTest.android.kt:323)
at androidx.compose.ui.test.junit4.IdlingResourceRegistry.withRegistry(IdlingResourceRegistry.jvm.kt:157)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1.invoke(ComposeUiTest.android.kt:322)
at androidx.compose.ui.test.junit4.ComposeRootRegistry.withRegistry(ComposeRootRegistry.android.kt:146)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(ComposeUiTest.android.kt:321)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$apply$1.evaluate(AndroidComposeTestRule.android.kt:271)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:68)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:463)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2402)
Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.test.internal.platform.app.ActivityInvoker$-CC" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.base.jar", zip file "/data/app/~~ltIhkbem7PvvS9pvAnGmKw==/com.bskyb.sportnews.debug.test-CKtpCUkJSr5xoPLRVbDIuQ==/base.apk", zip file "/data/app/~~_YGMmZuAm2U0DFsnBFaK4w==/com.bskyb.sportnews.debug-r-lCbnrJ65QqDMO-juGl8A==/base.apk"],nativeLibraryDirectories=[/data/app/~~ltIhkbem7PvvS9pvAnGmKw==/com.bskyb.sportnews.debug.test-CKtpCUkJSr5xoPLRVbDIuQ==/lib/arm64, /data/app/~~_YGMmZuAm2U0DFsnBFaK4w==/com.bskyb.sportnews.debug-r-lCbnrJ65QqDMO-juGl8A==/lib/arm64, /data/app/~~ltIhkbem7PvvS9pvAnGmKw==/com.bskyb.sportnews.debug.test-CKtpCUkJSr5xoPLRVbDIuQ==/base.apk!/lib/arm64-v8a, /data/app/~~_YGMmZuAm2U0DFsnBFaK4w==/com.bskyb.sportnews.debug-r-lCbnrJ65QqDMO-juGl8A==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
... 54 more

Is this an issue with Compose 1.6.0? Do we need a newer version of compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4" } eg compose-1.7.0-beta? or could it be that androidx.compose.ui:ui-test-junit4 needs updating internally to use the new espresso versions?

brettchabot commented 3 months ago

Those androidx.test versions look correct to me.

Is it possible to look at 'gradle dependencies' and see what versions are actually being used? I suspect compose is using an old version of espresso

ibartj commented 3 months ago

Those androidx.test versions look correct to me.

Is it possible to look at 'gradle dependencies' and see what versions are actually being used? I suspect compose is using an old version of espresso

@brettchabot, do you think it can have something to do with this ... ?

|         +--- androidx.compose.ui:ui-test:1.7.0-beta01
|         |    \--- androidx.compose.ui:ui-test-android:1.7.0-beta01
|         |         ...
|         |         +--- androidx.test.espresso:espresso-core:3.5.0 -> 3.6.0 (*)
|         |         +--- androidx.test.espresso:espresso-idling-resource:3.5.0 -> 3.6.0
|         |         ...
+--- org.robolectric:robolectric:4.12.2
|    ...
|    +--- androidx.test.espresso:espresso-idling-resource:3.5.1 -> 3.6.0
|    ...
brettchabot commented 3 months ago

That all looks fine. Can you check what version of androidx.test:monitor and androidx.test:core are used? It should be 1.7.0 and 1.6.0

ibartj commented 3 months ago

That all looks fine. Can you check what version of androidx.test:monitor and androidx.test:core are used? It should be 1.7.0 and 1.6.0

We have the androidx.test:core set to 1.6.0, but at many places it either uses 1.5.0 (under androidx.fragment:fragment-testing:1.8.0) or seems to be forced to 1.5.0 (1.6.0 -> 1.5.0 under androidx.test.espresso:espresso-core:3.6.0 and androidx.test.ext:junit:1.2.0). Then there are also configurations where there is androidx.test:core:{strictly 1.5.0} -> 1.5.0 (c) set at the top level. I admit I don't completely understand it.

ibartj commented 3 months ago

šŸ‘@brettchabot, the test completes OK when I add the following to my build.gradle:

configurations.configureEach {
    resolutionStrategy {
        force 'androidx.test:core:1.6.0'
        // or force libs.test.core
    }
}
wezley98 commented 3 months ago

@ibartj Can also confirm this workaround has worked for us also.

brettchabot commented 3 months ago

androidx.test releases are backwards compatible so I'd be confused why configurations are forcing a specific version. That sounds like the root cause. I don't think there is much we can do on the androidx.test end if build configurations are set up to force incorrect version dependencies.

TWiStErRob commented 3 months ago

@brettchabot the forcing is the workaround to undo the "forcing" done by AGP, right?

Albeit somewhat wrongly, because it'll cause problems in the future when updates happen. Make sure you use version catalog versions (if you use them otherwise) in force calls to prevent future surprises. There's very likely a better API to deal with this in Gradle DSL, but I'm not sure which. force should be last resort, someone said somewhere šŸ˜….

brettchabot commented 3 months ago

Hmm. androidx.test libraries should not be part of the app/implementation classpath, so I still don't know of a good reason why a specific androidx.test core version is being forced.

There used to be an issue where users needed to put androidx.fragment:testing on the app classpath, which would transitively bring in androidx.test.core. That issue has now been fixed with the separate androidx.fragment:testing-manifest artifact. Perhaps that is the historically reason why the 'force androidx.test.core' is there.

FWIW I'm a huge proponent of just building a single apk to run tests. It saves performance and unnecessary build complexities like this. IIRC gradle library modules do this, and I thought there was a flag one could see for app modules.

TWiStErRob commented 3 months ago

Nice spot @brettchabot!

@wezley98 @ibartj would you mind testing this theory?

  1. Remove force workaround
  2. Replace fragment:testing with fragment:testing-manifest as described in the release notes?

@brettchabot I found that docs are still recommending the old approach!

ibartj commented 3 months ago

Nice spot @brettchabot!

@wezley98 @ibartj would you mind testing this theory?

  1. Remove force workaround
  2. Replace fragment:testing with fragment:testing-manifest as described in the release notes?

@brettchabot I found that docs are still recommending the old approach!

That's it @TWiStErRob! It works.

brettchabot commented 2 months ago

brettchabot I found that docs are still recommending the old approach!

Thanks for flagging @TWiStErRob, I just submitted a change to correct the docs.