cashapp / paparazzi

Render your Android screens without a physical device or emulator
https://cashapp.github.io/paparazzi/
Apache License 2.0
2.32k stars 216 forks source link

Could not resolve resource value: xxx #657

Closed saintmarina closed 1 year ago

saintmarina commented 1 year ago

Description Paparazzi test(both record and verify) fails when tested Compose component gets resources with xxxResources() (e.g. stringResources() or painterResources(). Stack trace:

android.content.res.Resources$NotFoundException: Could not resolve resource value: 0x7F08007A.
    at app//android.content.res.Resources_Delegate.throwException(Resources_Delegate.java:1161)
    at app//android.content.res.Resources_Delegate.throwException(Resources_Delegate.java:1137)
    at app//android.content.res.Resources_Delegate.throwException(Resources_Delegate.java:1141)
    at app//android.content.res.Resources_Delegate.getResourceValue(Resources_Delegate.java:929)
    at app//android.content.res.Resources_Delegate.getValue(Resources_Delegate.java:900)
    at app//android.content.res.Resources.getValue(Resources.java:1428)
    at app//androidx.compose.ui.res.PainterResources_androidKt.painterResource(PainterResources.android.kt:61)
    at app//beam.components.ui.icons.NotificationIconKt.NotificationIcon-FNF3uiM(NotificationIcon.kt:22)
    at app//beam.components.ui.icons.ComposableSingletons$NotificationIconKt$lambda-1$1.invoke(NotificationIcon.kt:31)
    at app//beam.components.ui.icons.ComposableSingletons$NotificationIconKt$lambda-1$1.invoke(NotificationIcon.kt:30)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at app//androidx.compose.material3.TextKt.ProvideTextStyle(Text.kt:261)
    at app//androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81)
    at app//androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:80)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at app//androidx.compose.material3.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:73)
    at app//com.wbd.theme.ui.AppThemeKt$BaseTheme$1.invoke(AppTheme.kt:113)
    at app//com.wbd.theme.ui.AppThemeKt$BaseTheme$1.invoke(AppTheme.kt:112)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at app//com.wbd.theme.ui.AppThemeKt.BaseTheme(AppTheme.kt:105)
    at app//com.wbd.theme.ui.AppThemeKt.AppTheme(AppTheme.kt:44)
    at app//beam.components.ui.icons.NotificationIconKt.HomeToolbarIconPreview(NotificationIcon.kt:30)
    at app//beam.snapshot.test.ComposableSingletons$IconTestKt$lambda-1$1.invoke(IconTest.kt:13)
    at app//beam.snapshot.test.ComposableSingletons$IconTestKt$lambda-1$1.invoke(IconTest.kt:12)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:404)
    at app//androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:250)
    at app//androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:249)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at app//androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:177)
    at app//androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:123)
    at app//androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:122)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at app//androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:114)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:157)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:156)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:156)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:140)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at app//androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at app//androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:78)
    at app//androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3248)
    at app//androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3238)
    at app//androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
    at app//androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source)
    at app//androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3238)
    at app//androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3173)
    at app//androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:587)
    at app//androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:950)
    at app//androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
    at app//androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1060)
    at app//androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:131)
    at app//androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:182)
    at app//androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360)
    at app//androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:202)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:138)
    at app//androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
    at app//androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1147)
    at app//android.view.View.dispatchAttachedToWindow(View.java:20753)
    at app//android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490)
    at app//android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
    at app//android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
    at app//android.view.ViewGroup.addViewInner(ViewGroup.java:5290)
    at app//android.view.ViewGroup.addView(ViewGroup.java:5076)
    at app//android.view.ViewGroup.addView(ViewGroup.java:5016)
    at app//android.view.ViewGroup.addView(ViewGroup.java:4988)
    at app//app.cash.paparazzi.Paparazzi.takeSnapshots(Paparazzi.kt:295)
    at app//app.cash.paparazzi.Paparazzi.snapshot(Paparazzi.kt:214)
    at app//app.cash.paparazzi.Paparazzi.snapshot$default(Paparazzi.kt:213)
    at app//app.cash.paparazzi.Paparazzi.snapshot(Paparazzi.kt:206)
    at app//app.cash.paparazzi.Paparazzi.snapshot$default(Paparazzi.kt:195)
    at app//beam.snapshot.test.IconTest.typographyTitleSnapshot(IconTest.kt:12)
    at java.base@11.0.13/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base@11.0.13/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base@11.0.13/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base@11.0.13/java.lang.reflect.Method.invoke(Method.java:566)
    at app//org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at app//org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at app//org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at app//org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at app//app.cash.paparazzi.Paparazzi$apply$statement$1.evaluate(Paparazzi.kt:123)
    at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at java.base@11.0.13/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base@11.0.13/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base@11.0.13/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base@11.0.13/java.lang.reflect.Method.invoke(Method.java:566)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at com.sun.proxy.$Proxy5.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

Perhaps, the issue is related to #524.

Steps to Reproduce

  1. Create a component:
    @Composable
    fun MyIcon(
    modifier: Modifier = Modifier,
    contentDescription: String = stringResource(R.string.my_icon),
    tint: Color
    ) {
    Icon(
        modifier = modifier,
        tint = tint,
        painter = painterResource(id = R.drawable.ic_my_icon),
        contentDescription = contentDescription
    )
    }
  2. Test above mentioned component:
    
    open class BaseTest {
    @get:Rule
    val paparazzi = run {
        Paparazzi(
            environment = detectEnvironment().copy(
                platformDir = "${androidHome()}/platforms/android-32",
                compileSdkVersion = 32,
            )
        )
    }
    }

class IconTest : BaseTest() { @Test fun typographyTitleSnapshot() { paparazzi.snapshot { MyIcon(tint = Color.White) } } }


Provide a sample project, failing test, or steps to reproduce.

**Expected behavior**
Expect the test to run smoothly

**Additional information:**
- Paparazzi Version: 1.1.0
- OS: MacOS Monterey 12.6.1
- Compile SDK: 33
- Gradle Version: 7.6.0
- Android Gradle Plugin Version: 7.3.1
saintmarina commented 1 year ago

Never mind, it was a modularization issue.

juckrit commented 1 year ago

@saintmarina You are working on it right?. Because I have the same problem.

bartek-wesolowski commented 1 year ago

I have the same problem. It happens when I reference a resource that is outside of the module under test. I can reproduce it in HelloComposeTest when I replace val text = "Hello, Paparazzi" with val text = stringResource(some.other.module.R.string.hello) // define this string in another module The same happens with drawable and color resources.

saintmarina commented 1 year ago

@juckrit @bartek-wesolowski a unit test module can't access resources of another module. testFixtures are solving that problem. However, last I checked testFixtures weren't available for android kotlin modules. You can follow the JetBrains ticket here.

We ended up importing the module that is being tested into the paparazzi testing module (because we use robolectric, we had to conduct all the paparazzi tests in a separate module) with implementation instead of testImplementation.

TWiStErRob commented 1 year ago

Sidenote: testFixtures should be available to use in Kotlin modules, but the fixture classes themselves cannot be written in the Kotlin language.

saintmarina commented 1 year ago

@TWiStErRob yes, testFixtures are available to use in kotlin modules, however they are not available in android (application, library) kotlin modules.

ashley-figueira commented 1 year ago

@saintmarina How did you fix your issue? I am facing with the same problem. I have a Components library. I added a Screenshot test for a component in this library and it gives me this error. As soon as I move the test to a feature module then it works :/