robolectric / robolectric

Android Unit Testing Framework
http://robolectric.org
Other
5.91k stars 1.37k forks source link

Jetpack Compose Screenshot Tooling #6756

Closed realdadfish closed 1 year ago

realdadfish commented 3 years ago

I'm using Robolectric 4.5.1 to test my Jetpack Compose UI. Due to the nature of Compose Testing, one does not get access to a full-blown "view-alike" tree, but only a "semantic tree", that is built alongside with the actual visible elements. This semantic tree is not only used for testing, but also accessibility support, so testing in Compose is a little more like UIAutomator testing (in comparison to the whitebox-alike instrumented Espresso Testing what one is used to).

The semantic tree, as the name suggests, does not hold any visual information about what is seen on screen, no colors, no icons, nothing. So to properly test a Compose implementation, one has to reside on screen snapshotting, e.g. by utilizing [SemanticsNodeInteraction.captureToImage()](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction#(androidx.compose.ui.test.SemanticsNodeInteraction).captureToImage()). This internally uses PixelCopy.request() to create an offscreen buffer of what's drawn on screen, after a draw call happened.

However, when called from Robolectric, thisPixelCopy is never triggered, as it fails to register the "first" draw on screen, i.e. drawDone is never true (tested with sdk = 28 and sdk = 29):

 val decorView = windowToCapture.decorView
    handler.post {
        if (Build.VERSION.SDK_INT >= 29 && decorView.isHardwareAccelerated) {
            FrameCommitCallbackHelper.registerFrameCommitCallback(decorView.viewTreeObserver) {
                drawDone = true
            }
        } else {
            decorView.viewTreeObserver.addOnDrawListener(object : ViewTreeObserver.OnDrawListener {
                var handled = false
                override fun onDraw() {
                    if (!handled) {
                        handled = true
                        handler.post {
                            drawDone = true
                            decorView.viewTreeObserver.removeOnDrawListener(this)
                        }
                    }
                }
            })
        }
        decorView.invalidate()
    }

(WindowCapture.android.kt from androidx.compose.ui:ui-test:1.0.1)

On previous occasions I tried to use PixelCopy myself to capture screen contents within Robolectric, which always resulted in a black screen.

Is there any support for screenshot making on the horizon for Robolectric or is this impossible to do?

utzcoz commented 3 years ago

On previous occasions I tried to use PixelCopy myself to capture screen contents within Robolectric, which always resulted in a black screen.

I think Robolectric doesn't render realistic layout of app for tests, There is a related issue: Render layouts.

Is there any support for screenshot making on the horizon for Robolectric or is this impossible to do?

If Robolectric can render realistic layout of app for tests, relates methods, such as PixelCopy can be supported correctly. https://github.com/robolectric/robolectric/issues/4804#issuecomment-511249381 has shown a potential solution created by square, and open-sourced with project cashapp/pararazzi. From this demo talk: SF Android GDG @ Square 2019 - Keeping your Pixels perfect, we can know it uses layoutlib(we can check https://github.com/cashapp/paparazzi/tree/master/libs for details) from Android Studio for layout preview, we used many times. If Roboelctric can integrate cashapp/pararazzi directly or leverage layoutlib to implement those features, I think Robolectric can bring support for real rendering to users. It is also useful for VectorDrawable or other xml based drawable.

@realdadfish hope I understand your problem correctly, and above statements can answer some problems.

realdadfish commented 3 years ago

Yes, this is exactly the issue.

utzcoz commented 3 years ago

@hoisie what about making this supporting continuous? We can try to make it come true for 4.8 release, and it can be useful for folks using Jetpack Compose.

hoisie commented 3 years ago

Yes, rendering and graphics is one of the weakest points of Robolectric (it is mostly no-ops and some fakes). We are planning to make improvements in this area. The approach that we took with native SQLite seemed to work pretty well so may use a similar approach with graphics libraries and see if that works.

Debdutta-Panda commented 2 years ago

It is 4.9 already, issue not fixed right?

yschimke commented 1 year ago

Now supported ?

https://github.com/robolectric/robolectric/releases/tag/robolectric-4.10-alpha-1

utzcoz commented 1 year ago

@realdadfish From Robolectric 4.10, it is possible to use Robolectric as Jetpack Compose Screenshot Tooling. There are some community work: https://github.com/takahirom/roborazzi and https://github.com/sergio-sastre/AndroidUiTestingUtils.