Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
Implement compose preview screenshot testing for module screen, updat…
Browse files Browse the repository at this point in the history
…e base screenshot
  • Loading branch information
isaacnguyen0809 committed Sep 17, 2024
1 parent f2c3795 commit 6ba89bf
Show file tree
Hide file tree
Showing 286 changed files with 1,289 additions and 1,000 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/compose_screenshot_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Screenshots tests

on:
push:
branches:
- main
pull_request:

jobs:
sceenshot_test:
runs-on: ubuntu-latest
steps:
- name: Checkout GIT
uses: actions/checkout@v4

- name: Setup Java SDK
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: '18'

- name: Enable Gradle Wrapper caching (optimization)
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Run Compose preview screenshot tests
run: ./gradlew :shared:ui:testing:validateDebugScreenshotTest

- name: Upload build reports
uses: actions/upload-artifact@v4
if: always()
with:
name: my-artifact
path: shared/ui/testing/build/reports
26 changes: 0 additions & 26 deletions .github/workflows/paparazzi_screenshot_test.yml

This file was deleted.

2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ dependencies {
implementation(libs.ksp.plugin)
implementation(libs.cashapp.molecule.plugin)
implementation(libs.room.plugin)
implementation(libs.paparazzi.plugin)
implementation(libs.module.graph.plugin)
implementation(libs.compose.screenshot.plugin)

// Make version catalog available in precompiled scripts
// https://github.com/gradle/gradle/issues/15383#issuecomment-1567461389
Expand Down
3 changes: 3 additions & 0 deletions buildSrc/src/main/kotlin/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object BuildConfigConstants {
const val IMAGE_DIFFERENCE_THRESHOLD = 0.013f
}
13 changes: 13 additions & 0 deletions buildSrc/src/main/kotlin/ivy.compose.preview.testing.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("ivy.compose")
/*
Will refactor code for import plugin for compose preview screenshot testing
when compose.screenshot has a stable version -
remove android.experimental.enableScreenshotTest
*/
// id("com.android.compose.screenshot")
}

android {
experimentalProperties["android.experimental.enableScreenshotTest"] = true
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/ivy.feature.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ plugins {
org.jetbrains.kotlin.plugin.compose
id("ivy.module")
id("ivy.compose")
id("ivy.paparazzi")
id("ivy.compose.preview.testing")
}
21 changes: 0 additions & 21 deletions buildSrc/src/main/kotlin/ivy.paparazzi.gradle.kts

This file was deleted.

26 changes: 12 additions & 14 deletions docs/CI-Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@

If you see any of the PR checks failing (❌) go to [Actions](https://github.com/Ivy-Apps/ivy-wallet/actions) and find it there. Or simply click "Details" next to the failed check and explore the logs to see why it has failed.

## PR description check

It means that you didn't follow our [official PR template](../.github/PULL_REQUEST_TEMPLATE.md).
Update your PR description with all necessary information. You can also check the exact error by
clicking "Details" on the failing (❌) check.

## Detekt
[Detekt](https://detekt.dev/) is a static code analyzer for Kotlin that we use to enforce code readability and good practices.

Expand Down Expand Up @@ -77,15 +71,19 @@ This GitHub Action checks whether your `@Composable` functions are stable (i.e.
Do that only if the failure is in legacy code. If the script is failing, open it and execute the commands inside it manually.


## Paparazzi Tests
Paparazzi is used for automated visual testing. It captures screenshots of various `@Composable` and then compares them against baseline images to detect any visual differences or regressions.
## Compose preview screenshot testing

Screenshot testing is an effective way to verify how your UI looks to users. The Compose Preview Screenshot Testing tool combines the simplicity and features of composable previews with the productivity gains of running host-side screenshot tests. Compose Preview Screenshot Testing is designed to be as easy to use as composable previews.

A screenshot test is an automated test that takes a screenshot of a piece of UI and then compares it against a previously approved reference image. If the images don't match, the test fails and produces an HTML report to help you compare and find the differences.

**Fixing Paparazzi issues:**
We also create an annotation called @IvyPreviews to serve the purpose of exporting and checking images for large-sized devices and small-sized devices.

1. Run `./gradlew verifyPaparazziDebug` locally to execute the Paparazzi tests and identify the specific screens where failures occur.
2. Upon failure, the system generates detailed reports pinpointing the changes introduced since the last successful test run. Review these reports to understand the nature of the failures.
3. If the identified changes are intentional, proceed by running `./gradlew {module_name}:recordPaparazziDebug` to update the baseline screenshots specifically for the failed UI components.
4. After updating the baseline screenshots, rerun `./gradlew {module_name}:verifyPaparazziDebug` to ensure that the tests now pass with the updated baselines.
5. Repeat this process iteratively for all modules that have failed Paparazzi tests, ensuring thorough validation and updating of baseline images as needed.
1. Run `./gradlew validateDebugScreenshotTest` locally to execute the screenshot testing and identify the specific screens where failures occur.
2. Upon failure, the system generates detailed reports pinpointing the changes introduced since the last successful test run. Review these reports to understand the nature of the failures. You can find the report at the following path: {module}/build/reports/screenshotTest/preview/{variant}/index.html
For example: screen/balance/build/reports/screenshotTest/preview/debug/index.html
3. If the identified changes are intentional, proceed by running `./gradlew :module:update{Variant}ScreenshotTest` to update the baseline screenshots specifically for the failed UI components. For example : `./gradlew :screen:balance:updateDebugScreenshotTest`
4. After updating the baseline screenshots, rerun `./gradlew :module:validate{Variant}ScreenshotTest` to ensure that the tests now pass with the updated baselines. For example : `./gradlew :screen:balance:validateDebugScreenshotTest`
5. Repeat this process iteratively for all modules that have failed compose preview screenshot testing, ensuring thorough validation and updating of baseline images as needed.

> **Note**: Ensure the use of static data only. Dynamic data, such as current time or date, will cause test failures due to its variability.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ kotlin.code.style=official
# Speed-up Gradle Builds! (OPTIMIZATION)
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true

android.experimental.enableScreenshotTest=true
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ hilt = "2.52"
room = "2.6.1"
androidx-work = "2.9.1"
kotlinx-collections = "0.3.7"
paparazzi = "1.3.3"
screenshot = "0.0.1-alpha06"

# Android
min-sdk = "28"
Expand Down Expand Up @@ -60,8 +60,6 @@ kotest-assertions-arrow = { module = "io.kotest.extensions:kotest-assertions-arr
kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
cashapp-molecule-plugin = { module = "app.cash.molecule:molecule-gradle-plugin", version = "1.4.3" }
cashapp-turbine = { module = "app.cash.turbine:turbine", version = "1.1.0" }
paparazzi-plugin = { module = "app.cash.paparazzi:paparazzi-gradle-plugin", version.ref = "paparazzi" }
paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
google-testparameterinjector = { module = "com.google.testparameterinjector:test-parameter-injector", version = "1.16" }

# Integartion (Android) testing
Expand Down Expand Up @@ -145,6 +143,8 @@ ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.
# Modules Graph
module-graph-plugin = { module = "com.jraska.module.graph.assertion:plugin", version = "2.6.0" }

# Compose Preview Testing
compose-screenshot-plugin = { module = "com.android.compose.screenshot:com.android.compose.screenshot.gradle.plugin", version.ref = "screenshot" }

[bundles]
kotlin = [
Expand Down
8 changes: 6 additions & 2 deletions screen/accounts/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
plugins {
id("ivy.feature")
id("com.android.compose.screenshot")
}

android {
namespace = "com.ivy.accounts"
testOptions {
screenshotTests {
imageDifferenceThreshold = BuildConfigConstants.IMAGE_DIFFERENCE_THRESHOLD
}
}
}

dependencies {
Expand All @@ -14,6 +20,4 @@ dependencies {
implementation(projects.shared.ui.navigation)
implementation(projects.temp.legacyCode)
implementation(projects.temp.oldDesign)

testImplementation(projects.shared.ui.testing)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions screen/accounts/src/main/java/com/ivy/accounts/AccountsTab.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import com.ivy.navigation.TransactionsScreen
import com.ivy.navigation.navigation
import com.ivy.navigation.screenScopedViewModel
import com.ivy.ui.R
import com.ivy.ui.annotation.IvyPreviews
import com.ivy.ui.rememberScrollPositionListState
import com.ivy.wallet.ui.theme.Green
import com.ivy.wallet.ui.theme.GreenLight
Expand Down Expand Up @@ -334,7 +335,7 @@ private fun AccountHeader(
}
}

@Preview
@IvyPreviews
@Composable
private fun PreviewAccountsTabCompactModeDisabled(theme: Theme = Theme.LIGHT) {
IvyWalletPreview(theme = theme) {
Expand Down Expand Up @@ -421,7 +422,7 @@ private fun PreviewAccountsTabCompactModeDisabled(theme: Theme = Theme.LIGHT) {
}
}

@Preview
@IvyPreviews
@Composable
private fun PreviewAccountsTabCompactModeEnabled(theme: Theme = Theme.LIGHT) {
IvyWalletPreview(theme = theme) {
Expand Down Expand Up @@ -510,17 +511,16 @@ private fun PreviewAccountsTabCompactModeEnabled(theme: Theme = Theme.LIGHT) {

/** For screen shot testing **/
@Composable
fun AccountsTabNonCompactUITest(dark: Boolean) {
fun AccountsTabUITest(dark: Boolean) {
val theme = when (dark) {
true -> Theme.DARK
false -> Theme.LIGHT
}
PreviewAccountsTabCompactModeDisabled(theme)
}

/** For screen shot testing **/
@Composable
fun AccountsTabCompactUITest(dark: Boolean) {
fun AccountsTabUICompactModeTest(dark: Boolean) {
val theme = when (dark) {
true -> Theme.DARK
false -> Theme.LIGHT
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@file:Suppress("UnusedPrivateMember")

import androidx.compose.runtime.Composable
import com.ivy.accounts.AccountsTabUICompactModeTest
import com.ivy.accounts.AccountsTabUITest
import com.ivy.ui.annotation.IvyPreviews

@IvyPreviews
@Composable
private fun PreviewAccountsTabLight() {
AccountsTabUITest(dark = false)
}

@IvyPreviews
@Composable
private fun PreviewAccountsTabDark() {
AccountsTabUITest(dark = true)
}

@IvyPreviews
@Composable
private fun PreviewAccountsTabCompactModeDark() {
AccountsTabUICompactModeTest(dark = true)
}

@IvyPreviews
@Composable
private fun PreviewAccountsTabCompactModeLight() {
AccountsTabUICompactModeTest(dark = false)
}

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 6 additions & 2 deletions screen/attributions/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
plugins {
id("ivy.feature")
id("com.android.compose.screenshot")
}

android {
namespace = "com.ivy.attributions"
testOptions {
screenshotTests {
imageDifferenceThreshold = BuildConfigConstants.IMAGE_DIFFERENCE_THRESHOLD
}
}
}

dependencies {
implementation(projects.shared.base)
implementation(projects.shared.domain)
implementation(projects.shared.ui.core)
implementation(projects.shared.ui.navigation)

testImplementation(projects.shared.ui.testing)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.ivy.navigation.Navigation
import com.ivy.navigation.navigation
import com.ivy.navigation.screenScopedViewModel
import com.ivy.ui.R
import com.ivy.ui.annotation.IvyPreviews
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

Expand Down Expand Up @@ -156,7 +157,7 @@ private fun AttributionsSectionDivider(
)
}

@Preview
@IvyPreviews
@Composable
private fun AttributionsUIPreview(isDark: Boolean = false) {
val attributionItems = persistentListOf<AttributionItem>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@file:Suppress("UnusedPrivateMember")

import androidx.compose.runtime.Composable
import com.ivy.attributions.AttributionScreenUiTest
import com.ivy.ui.annotation.IvyPreviews

@IvyPreviews
@Composable
private fun AttributionsUILight() {
AttributionScreenUiTest(isDark = false)
}

@IvyPreviews
@Composable
private fun AttributionsUIDark() {
AttributionScreenUiTest(isDark = true)
}
Loading

0 comments on commit 6ba89bf

Please sign in to comment.