Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions PAPARAZZI_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Paparazzi UI Snapshot Testing Setup

This project uses [Paparazzi](https://github.com/cashapp/paparazzi) for snapshot testing of Compose UI components.

## Overview

Paparazzi enables fast, deterministic screenshot testing without requiring an Android device or emulator. All UI snapshots are generated during unit tests and stored as golden images.

## Configuration

### Versions
- **Android Gradle Plugin**: 8.5.2
- **Kotlin**: 2.0.0
- **Paparazzi**: 1.3.4
- **Compile SDK**: 34

### Build Configuration

Paparazzi is configured in both `app/build.gradle` and `composed-barcodes/build.gradle`:

```gradle
plugins {
id 'app.cash.paparazzi'
}

dependencies {
testImplementation(libs.junit)
}
```

## Snapshot Tests

### Library Module (`composed-barcodes`)

**Location**: `composed-barcodes/src/test/kotlin/com/simonsickle/compose/barcodes/`

#### Test Files:
- `BarcodeSnapshotTest.kt` - Tests all Barcode component preview variations

#### Preview Composables:
Created in `BarcodePreviews.kt`, covering:
- QR Code (default, small, high-resolution, no-progress variations)
- Code 128, Code 39, EAN-13, UPC-A
- Data Matrix, Aztec, PDF417

### App Module (`app`)

**Location**: `app/src/test/java/com/simonsickle/composedbarcodes/examples/`

#### Test Files:
- `BarcodePreviewSnapshotTest.kt` - Tests all barcode example screens

Covers all 13 barcode types:
QR, Aztec, Code39, Code93, Code128, Codabar, DataMatrix, EAN8, EAN13, ITF, PDF417, UPC-A, UPC-E

## Running Tests

### Record Golden Images
```bash
# Record all snapshots
./gradlew recordPaparazziDebug

# Record for specific module
./gradlew composed-barcodes:recordPaparazziDebug
./gradlew app:recordPaparazziDebug
```

### Verify Snapshots
```bash
# Verify all snapshots match golden images
./gradlew verifyPaparazziDebug

# Verify for specific module
./gradlew composed-barcodes:verifyPaparazziDebug
./gradlew app:verifyPaparazziDebug
```

### View Reports
After running tests, view the generated report at:
- Library: `composed-barcodes/build/reports/paparazzi/debug/index.html`
- App: `app/build/reports/paparazzi/debug/index.html`

## CI Integration

The GitHub Actions workflow (`.github/workflows/unit-tests.yml`) runs Paparazzi tests automatically on push and pull requests:

```yaml
- name: Run Paparazzi snapshot tests - Library
run: ./gradlew composed-barcodes:verifyPaparazziDebug --stacktrace

- name: Run Paparazzi snapshot tests - App
run: ./gradlew app:verifyPaparazziDebug --stacktrace
```

## Implementation Details

### Synchronous Barcode for Testing

To ensure snapshots capture actual barcodes instead of loading states, the library provides a `SynchronousBarcode` composable specifically for preview and testing purposes. This version generates barcodes synchronously during composition, making it ideal for Paparazzi snapshot tests.

- **Production use**: Use the async `Barcode` composable with loading indicator support
- **Testing/Previews**: Use `SynchronousBarcode` for reliable snapshot testing

All preview composables in `BarcodePreviews.kt` use `SynchronousBarcode` to ensure accurate snapshot rendering.

**Important**: When using `SynchronousBarcode`, ensure the `width` and `height` parameters match the modifier dimensions to prevent barcode distortion. This maintains proper aspect ratios for each barcode type (e.g., square for QR codes and Data Matrix, wide rectangles for Code 128, EAN-13, and UPC-A).

## Known Limitations

1. **App Module**: There's a known Gradle dependency version mismatch in the app module (requires compileSdk 35 and AGP 8.6.0+, currently using 34 and 8.5.2). This doesn't affect the library module tests.

## Troubleshooting

### Tests Failing After Dependency Updates
Run `./gradlew --stop` to stop all Gradle daemons, then re-run tests.

### Snapshot Mismatches
If legitimate UI changes were made, record new golden images:
```bash
./gradlew recordPaparazziDebug
```

### Build Failures
Ensure you're using the correct SDK version (34) and the specified plugin versions.

## Best Practices

1. **Always record golden images** after intentional UI changes
2. **Review snapshot diffs** carefully before accepting changes
3. **Keep snapshots small** - test individual components rather than entire screens when possible
4. **Use descriptive test names** to make snapshot files easy to identify
13 changes: 11 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.compose'
id 'app.cash.paparazzi'
}

android {
defaultConfig {
applicationId "com.simonsickle.composedbarcodes"
compileSdk = 36
compileSdk = 34
minSdkVersion 26
targetSdkVersion 36
targetSdkVersion 34
versionCode 2
versionName "1.1.0"

Expand All @@ -27,6 +29,10 @@ android {
targetCompatibility JavaVersion.VERSION_19
}

kotlinOptions {
jvmTarget = '19'
}

buildFeatures {
compose true
}
Expand All @@ -50,4 +56,7 @@ dependencies {

// Nav components
implementation(libs.androidx.navigation.compose)

// Testing
testImplementation(libs.junit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.simonsickle.compose.barcodes.Barcode
import com.simonsickle.compose.barcodes.SynchronousBarcode
import com.simonsickle.compose.barcodes.BarcodeType

@Composable
Expand All @@ -30,7 +30,7 @@ fun GenericBarcodeExample(
.fillMaxSize()
) {
if (barcodeType.isValueValid(value)) {
Barcode(
SynchronousBarcode(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.width(300.dp)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.simonsickle.composedbarcodes.examples

import app.cash.paparazzi.DeviceConfig
import app.cash.paparazzi.Paparazzi
import org.junit.Rule
import org.junit.Test

/**
* Paparazzi snapshot tests for all barcode preview composables.
* These tests ensure that the UI renders correctly and catches any visual regressions.
*/
class BarcodePreviewSnapshotTest {

@get:Rule
val paparazzi = Paparazzi(
deviceConfig = DeviceConfig.PIXEL_5,
showSystemUi = false
)

@Test
fun qrCodePreview() {
paparazzi.snapshot {
Qr()
}
}

@Test
fun aztecPreview() {
paparazzi.snapshot {
Aztec()
}
}

@Test
fun code39Preview() {
paparazzi.snapshot {
Code39()
}
}

@Test
fun code93Preview() {
paparazzi.snapshot {
Code93()
}
}

@Test
fun code128Preview() {
paparazzi.snapshot {
Code128()
}
}

@Test
fun codabarPreview() {
paparazzi.snapshot {
Codabar()
}
}

@Test
fun dataMatrixPreview() {
paparazzi.snapshot {
DataMatrix()
}
}

@Test
fun ean8Preview() {
paparazzi.snapshot {
Ean8()
}
}

@Test
fun ean13Preview() {
paparazzi.snapshot {
Ean13()
}
}

@Test
fun itfPreview() {
paparazzi.snapshot {
Itf()
}
}

@Test
fun pdf417Preview() {
paparazzi.snapshot {
Pdf417()
}
}

@Test
fun upcAPreview() {
paparazzi.snapshot {
UpcA()
}
}

@Test
fun upcEPreview() {
paparazzi.snapshot {
UpcE()
}
}
}
8 changes: 5 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
plugins {
id("com.android.application") version '9.1.0' apply false
id("com.android.library") version '9.1.0' apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
id("com.android.application") version '8.5.2' apply false
id("com.android.library") version '8.5.2' apply false
id("org.jetbrains.kotlin.android") version "2.0.0" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" apply false
id("app.cash.paparazzi") version "1.3.4" apply false
id("io.github.gradle-nexus.publish-plugin") version "2.0.0"
}

Expand Down
15 changes: 12 additions & 3 deletions composed-barcodes/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
plugins {
id "com.android.library"
id "org.jetbrains.kotlin.android"
id "org.jetbrains.kotlin.plugin.compose"
id "app.cash.paparazzi"
}

ext {
Expand All @@ -13,7 +15,7 @@ android {
namespace "com.simonsickle.compose"

defaultConfig {
compileSdk = 36
compileSdk = 34
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
Expand All @@ -35,6 +37,10 @@ android {
targetCompatibility JavaVersion.VERSION_19
}

kotlinOptions {
jvmTarget = '19'
}

buildFeatures {
compose true
}
Expand All @@ -50,7 +56,7 @@ android {

testOptions {
animationsDisabled true
targetSdk 36
targetSdk 34
}

publishing {
Expand All @@ -60,7 +66,7 @@ android {
}
}
lint {
targetSdk 36
targetSdk 34
}
}

Expand All @@ -75,6 +81,9 @@ dependencies {
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.ui.tooling.preview)
debugImplementation(libs.androidx.compose.ui.tooling)

// Testing
testImplementation(libs.junit)
}

apply from: "${rootDir}/scripts/publish-module.gradle"
Loading