takahirom / roborazzi

Make JVM Android integration test visible 🤖📸
https://takahirom.github.io/roborazzi/
Apache License 2.0
729 stars 35 forks source link

"IllegalArgumentException: Software rendering doesn't support drawRenderNode" caused by Modifier.animateItem() in compose 1.7.0-alpha06 #290

Open alexjlockwood opened 7 months ago

alexjlockwood commented 7 months ago

I have a sample project (which you can download here in RoborazziSample.zip) that runs the following test:

@Test
fun lazyColumnTest() {
    composeTestRule.setContent {
        LazyColumn {
            item {
                Text(
                    modifier = Modifier.animateItem(),
                    text = "Text",
                )
            }
        }
    }
}

When I run this test, I get an exception:

java.lang.IllegalArgumentException: Software rendering doesn't support drawRenderNode

My sample project uses the following versions:

The issue seems to be related to Modifier.animateItem() in Compose 1.7.0-alpha06, as this test works fine if I downgrade to Compose 1.7.0-alpha05 and replace the modifier with the older Modifier.animateItemPlacement(). (If you are unfamiliar with these APIs, basically they allow for automatic fade in/out/reordering animations in a compose list).

I am uncertain of the details causing this exception, but I do notice that in the latest Compose changelog there is mention of changes to GraphicsLayer and I noticed that there's also mention of GraphicsLayer in the PR that introduced this new Modifier.animateLayer() API.

Full stack trace:

java.lang.IllegalArgumentException: Software rendering doesn't support drawRenderNode
    at android.graphics.Canvas.drawRenderNode(Canvas.java:2329)
    at androidx.compose.ui.graphics.layer.GraphicsLayerV29.draw(GraphicsLayerV29.android.kt:233)
    at androidx.compose.ui.graphics.layer.GraphicsLayer.draw$ui_graphics_release(AndroidGraphicsLayer.android.kt:510)
    at androidx.compose.ui.graphics.layer.GraphicsLayerKt.drawLayer(GraphicsLayer.kt:55)
    at androidx.compose.ui.platform.GraphicsLayerOwnerLayer.drawLayer(GraphicsLayerOwnerLayer.android.kt:107)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:415)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:964)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:196)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:68)
    at androidx.compose.foundation.DrawStretchOverscrollModifier.draw(AndroidOverscroll.android.kt:156)
    at androidx.compose.ui.node.BackwardsCompatNode.draw(BackwardsCompatNode.kt:350)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:110)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:89)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:431)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:58)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:450)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:449)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:502)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:258)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:449)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:447)
    at androidx.compose.ui.platform.RenderNodeLayer.drawLayer(RenderNodeLayer.android.kt:317)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:415)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:279)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:68)
    at androidx.compose.foundation.lazy.LazyListItemAnimator$DisplayingDisappearingItemsNode.draw(LazyListItemAnimator.kt:447)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:110)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:89)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:431)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:58)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:450)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:449)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2408)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:502)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:258)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:449)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:447)
    at androidx.compose.ui.platform.RenderNodeLayer.drawLayer(RenderNodeLayer.android.kt:317)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:415)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:964)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:196)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:428)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:420)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:964)
    at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:1505)
    at android.view.View.draw(View.java:23892)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.robolectric.shadows.ShadowView$_View_$$Reflector21.draw(Unknown Source)
    at org.robolectric.shadows.ShadowView.draw(ShadowView.java:261)
    at android.view.View.draw(View.java)
    at android.view.View.draw(View.java:23762)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw(View.java:23760)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw(View.java:23760)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw(View.java:23760)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw(View.java:23892)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.robolectric.shadows.ShadowView$_View_$$Reflector21.draw(Unknown Source)
    at org.robolectric.shadows.ShadowView.draw(ShadowView.java:261)
    at android.view.View.draw(View.java)
    at com.android.internal.policy.DecorView.draw(DecorView.java:809)
    at org.robolectric.shadows.ShadowPixelCopy.takeScreenshot(ShadowPixelCopy.java:169)
    at org.robolectric.shadows.ShadowPixelCopy.request(ShadowPixelCopy.java:96)
    at android.view.PixelCopy.request(PixelCopy.java)
    at com.github.takahirom.roborazzi.ViewScreenshotKt.generateBitmapFromPixelCopy(ViewScreenshot.kt:204)
    at com.github.takahirom.roborazzi.ViewScreenshotKt.generateBitmapFromPixelCopy(ViewScreenshot.kt:187)
    at com.github.takahirom.roborazzi.ViewScreenshotKt.generateBitmap(ViewScreenshot.kt:124)
    at com.github.takahirom.roborazzi.ViewScreenshotKt.fetchImage(ViewScreenshot.kt:23)
    at com.github.takahirom.roborazzi.ComposeScreenshotKt.fetchImage(ComposeScreenshot.kt:59)
    at com.github.takahirom.roborazzi.RoboComponent$Compose.<init>(capture.kt:184)
    at com.github.takahirom.roborazzi.RoboComponent$Compose.<init>(capture.kt:176)
    at com.github.takahirom.roborazzi.RoborazziKt.captureRoboImage(Roborazzi.kt:277)
    at com.github.takahirom.roborazzi.RoborazziRule.runTest(RoborazziRule.kt:225)
    at com.github.takahirom.roborazzi.RoborazziRule.access$runTest(RoborazziRule.kt:27)
    at com.github.takahirom.roborazzi.RoborazziRule$apply$1.evaluate(RoborazziRule.kt:132)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:54)
    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:505)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1$1.invoke(ComposeUiTest.android.kt:333)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withComposeIdlingResource(ComposeUiTest.android.kt:385)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withComposeIdlingResource(ComposeUiTest.android.kt:219)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1.invoke(ComposeUiTest.android.kt:332)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withWindowRecomposer(ComposeUiTest.android.kt:359)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withWindowRecomposer(ComposeUiTest.android.kt:219)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1.invoke(ComposeUiTest.android.kt:331)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withTestCoroutines(ComposeUiTest.android.kt:372)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withTestCoroutines(ComposeUiTest.android.kt:219)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1.invoke(ComposeUiTest.android.kt:330)
    at androidx.compose.ui.test.IdlingStrategy.withStrategy(IdlingStrategy.android.kt:52)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invoke(ComposeUiTest.android.kt:329)
    at androidx.compose.ui.test.IdlingResourceRegistry.withRegistry(IdlingResourceRegistry.jvm.kt:155)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1.invoke(ComposeUiTest.android.kt:328)
    at androidx.compose.ui.test.ComposeRootRegistry.withRegistry(ComposeRootRegistry.android.kt:146)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(ComposeUiTest.android.kt:327)
    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.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:588)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$2(SandboxTestRunner.java:290)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:101)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
takahirom commented 7 months ago

Thank you for reporting this issue. I was unable to reproduce the issue using your sample project.

image

It may or may not be related, but I'm using an Apple M1 Max, version 13.5.1.

I suggest trying to set the robolectric.screenshot.hwrdr.native property to true as a possible solution to the issue:

  init {
    val USE_HARDWARE_RENDERER_NATIVE_ENV = "robolectric.screenshot.hwrdr.native"
    System.setProperty(USE_HARDWARE_RENDERER_NATIVE_ENV, "true")
  }
takahirom commented 7 months ago

Sorry, I wasn't using the recordRoborazziDebug task, so I'm able to reproduce the issue. Additionally, I managed to fix the issue using the hardware rendering option. Deciding whether we can utilize it is also challenging for me though.

takahirom commented 7 months ago

@alexjlockwood I was able to reproduce this issue without Roborazzi.

@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ExampleUnitTest {
  @get:Rule
  val composeTestRule = createAndroidComposeRule<ComponentActivity>()

  @Test
  fun lazyColumnTest() {
    composeTestRule.setContent {
      LazyColumn {
        item {
          Text(
            modifier = Modifier.animateItem(),
            text = "Text",
          )
        }
      }
    }
    val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
    PixelCopy.request(
      composeTestRule.activity.window, bitmap, PixelCopy.OnPixelCopyFinishedListener {
      }, Handler(Looper.getMainLooper())
    )
  }
}

The issue likely originates not from Roborazzi but from Robolectric, as Robolectric may need to incorporate the drawRenderNode() method in its shadow. Using hardware rendering serves as a potential workaround; should this be the advised approach for resolving the issue, we might need to implement hardware rendering. Therefore, if you could report this issue to Robolectric, it would be greatly appreciated.