-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support DelayedPreviews (not tested, just initial push) #633
base: main
Are you sure you want to change the base?
Conversation
👀 |
The build failed because of a setting in the root build.gradle file:
I've addressed this by excluding the "roborazzi-annotations" module from this setting. |
I understand this is difficult, especially regarding ComposeRule. I'm exploring solutions. Please bear with me. |
Snapshot diff report
|
@sergio-sastre |
I think we might have multiple activities for each capture. I'm looking a little further into this. |
Great work! |
Thanks! I'm considering whether the name "DelayedPreview" is appropriate. |
I've pushed a preview with the DelayedPreview annotation and the current implementation does not seem to work. |
@@ -143,18 +197,28 @@ interface ComposePreviewTester<T : Any> { | |||
|
|||
@ExperimentalRoborazziApi | |||
class AndroidComposePreviewTester : ComposePreviewTester<AndroidPreviewInfo> { | |||
private val composeTestRule by lazy { createAndroidComposeRule<RoborazziActivity>() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@takahirom
Actually, it does not matter whether lazy or not since it is called in EVERY test.
I was afraid that using this TestRule in every test would increase the execution time, but I haven't noticed any significant increase though... I believe that is different from Activity Scenario, which launches an Activity and that does add execution time indeed...
It could also be that my laptop (Mac M2) is very fast and the amount of preview tests is not that high (less than 15...). I'm unsure about this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe test isolation is important. I think it's good to have a separate Activity for each test, as we do now. Of course, it depends on how slow it is, but I'm fine with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that too. However, even with this lazy, the ComposeTestRule is still being created in EVERY test, because it is called in other parts of the AndroidPreviewTester ( I’ve debugged it).
I’d also like it to be created when necessary … any ideas on how to avoid that?
@SuppressLint("VisibleForTests") | ||
val viewRootForTest = composeView.getChildAt(0) as ViewRootForTest | ||
doBeforeCapture() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@takahirom
I'm open to other solutions, but it does have to happen at this point. That was the reason why it was not working previously: the advanceTimeBy()
was being before the Composable was set as content in the Activity
// TODO -> maybe add also parameter for ignoreFrames, as used in mainClock.advanceTime() | ||
// TODO -> Make BINARY annotation only applicable to Methods | ||
// TODO -> Docu: mention about the 16ms frame in Android | ||
annotation class DelayCaptureRoboImage(val delayInMillis: Long) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@takahirom
Feel free to give feedback on the name ;)
I'm thinking whether it'd also make sense to add the capability to take screenshots after a set of time intervals... what could be interesting to verify animations.
I think in that case, we could create a different annotation for that like
IntervalCaptureRoboImage(intervalsInMillis: arrayOf(500, 100, 200))
If you like the idea, we could make it either in this PR or afterwards, up to you :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I think the name CaptureRoboImage
is a little misleading when used for DelayCaptureRoboImage
, because we filter Previews by @Preview
and not by @DelayCaptureRoboImage
. So I've renamed it to RoboManualAdvance
.
As for IntervalCaptureRoboImage
, could we perhaps have a repeatable annotation?
When considering IntervalCaptureRoboImage
, the use of CaptureRoboImage
name is understandable though.
@RoboManualAdvance(advanceTimeMillis = 100)
@RoboManualAdvance(advanceTimeMillis = 200)
@RoboManualAdvance(advanceTimeMillis = 500)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion!
I am also not very convinced about RoboManualAdvance, I think it does not tell its purpose clearly.
Its purpose is to advance the clock before capturing a screenshot.
I think something similar to
AdvanceClockBeforeCapture
or the like makes more sense.
as of repeatable annotations, ComposablePreviewScanner still does not support them as stated in the corresponding method:
That lies on me since it is a bit tricky 😅.
However, I agree that is a better solution.
I can create an issue and try to address it to support that in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I‘m planning to release support for repeated annotations in ComposablePreviewScanner 0.5.1.
However, what would be your expectation?
Would „duplicate“ previews for each repeated annotation, or would be more like a getRepeatedAnnotation()
and get a list with all annotations?
I‘m more inclined to the last one, but I‘m wondering how we could integrate it in this case 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. It might be good to consider when we should add another annotation.
@RoboManualAdvance(advanceTimeMillis = 100)
@RoboManualAdvance(advanceTimeMillis = 200)
@RoboFoo(true)
@RoboFoo(false)
We might want to take a screenshot when RoboFoo
is true and RoboManualAdvance
is 100. So it might be good to have one annotation like @RoboComposeOption()
? For example, we could use @RoboComposeOption(autoAdvance = false, advanceTimeMillis = 100, foo = true)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, I have renamed RoboManualAdvance to RoboComposePreviewOptions.
Honestly, I think that would be a separate thing.
This is about taking screenshots at different times.
RoboComposePreviewOptions sounds like a way to pass ComposePreviewOptions via the annotation.
I think it is better to keep things separate, to avoid the users of the api get confused and also make clearer separation of functionalities in code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your opinion.
Regarding annotation separation, I initially thought it would be challenging to support both variation screenshots and representative patterns. Do you have any ideas to facilitate this? (The theme serves as an example of another annotation type)
Use Case 1:
Capture variation screenshots (4 patterns):
@ComposePreviewOptions(advanceTimeMillis = [100, 3000], themes = [R.theme.xxxDark, R.theme.xxxLight])
Use Case 2:
Capture representative patterns (2 configurations):
@ComposePreviewOptions(advanceTimeMillis =[100], themes = [R.theme.xxxDark])
@ComposePreviewOptions(advanceTimeMillis =[3000], themes = [R.theme.xxxLight])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for showcasing some useful scenarios.
However, my suggestion is that we proceed first to support the scenario without repeatable annotations, and we focus only on the DelayedPreviews.
Let’s provide support for a valuable feature, and then iterate it to make it better 😊
For the other scenario with repeatable annotations, I need to think how to better provide support for it, and it is honestly bad timing: long holidays are approaching together with many other things and I have less time for this than I’d like too till May, likely 😅.
So I propose we focus now only on this DelayPreview, passing an array with intervals at which we want to tale screenshots, and the usecase2 is something we will support later.
what are your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main challenge with separate annotations is that they make it difficult to determine which variation should be captured in screenshots. Therefore, using a single annotation like @ComposePreviewOptions(advanceTimeMillis = [])
with array parameters might be preferable. Starting with a non-repeatable implementation of this annotation could be a good initial approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sergio-sastre
I pushed the changes, but there's an error that might be coming from preview.getAnnotation
.
Could you take a look at this stack trace?
The error occurs when running:
:sample-generate-preview-tests:testDebugUnitTest --tests "com.github.takahirom.*"
java.lang.IllegalArgumentException: Cannot use a ScanResult after it has been closed
at io.github.classgraph.ScanResult.getClassInfo(ScanResult.java:860)
at io.github.classgraph.ScanResultObject.getClassInfo(ScanResultObject.java:120)
at io.github.classgraph.AnnotationInfo.getClassInfo(AnnotationInfo.java:259)
at io.github.classgraph.AnnotationInfo.convertWrapperArraysToPrimitiveArrays(AnnotationInfo.java:476)
at io.github.classgraph.ObjectTypedValueWrapper.convertWrapperArraysToPrimitiveArrays(ObjectTypedValueWrapper.java:369)
at io.github.classgraph.AnnotationParameterValue.convertWrapperArraysToPrimitiveArrays(AnnotationParameterValue.java:167)
at io.github.classgraph.AnnotationParameterValueList.convertWrapperArraysToPrimitiveArrays(AnnotationParameterValueList.java:116)
at io.github.classgraph.AnnotationInfo.getParameterValues(AnnotationInfo.java:131)
at io.github.classgraph.AnnotationInfo.getParameterValues(AnnotationInfo.java:207)
at com.github.takahirom.roborazzi.AndroidComposePreviewTester.test(RoborazziPreviewScannerSupport.kt:306)
at com.github.takahirom.roborazzi.RoborazziPreviewParameterizedTests.test(RoborazziPreviewParameterizedTests.kt:38)
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 java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
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)
No description provided.