Screenshot testing Flutter Apps

This guide shows how to integrate Flutter golden tests with Screenshotbot for automated visual regression testing in CI/CD.

Overview

Instead of storing golden test images in your repository, this approach generates them during CI and uploads them to Screenshotbot for comparison and management. This keeps your repository clean while providing powerful visual testing capabilities.

Example Repository

See the complete working example here.

Key Concepts

Golden Test Creation

If you don't already have golden tests in your Flutter project, here's a simple golden test to get you started. If you already have golden tests, you can skip this step.

Create golden tests in your Flutter project using matchesGoldenFile():

testWidgets('MyHomePage golden test', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());
  await tester.pumpAndSettle();
  
  await expectLater(
    find.byType(MyApp),
    matchesGoldenFile('goldens/home_page.png'),
  );
});

Ignore Golden Files in Git

Add test/goldens to your .gitignore to prevent committing generated images. From this point onwards, the plan is to have all screenshots to be stored in Screenshotbot rather than in your git repository.

Make sure to rm -rf test/goldens to remove existing goldens too.

CI Integration

In your CI config:

  1. Generate golden files: Run flutter test --update-goldens (If you do the next step, you can ignore --update-goldens)
  2. Install Screenshotbot CLI: curl https://cdn.screenshotbot.io/recorder.sh | sh
  3. Upload to Screenshotbot: ~/screenshotbot/recorder --directory test/goldens --channel project-name
  4. Here's how our CI config looks for CircleCI, but you should be able to adapt this for any CI.

Configure Tests to Always Update Goldens (Optional but Recommended)

If you run flutter test locally with the above setup, the tests would fail since the golden images aren't present.

To ensure golden tests work seamlessly in both local development and CI, configure your tests to always generate/update golden files instead of comparing against existing ones.

Create test/flutter_test_config.dart:

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';

class AlwaysUpdateGoldenFileComparator extends LocalFileComparator {
  AlwaysUpdateGoldenFileComparator(Uri testFile) : super(testFile);

  @override
  Future<bool> compare(Uint8List imageBytes, Uri golden) async {
    // Ensure the golden file goes in test/goldens
    final Uri testGoldensUri = Uri.parse('test/goldens/');
    final String goldenPath = golden.path;
    
    // Remove 'goldens/' prefix if present and rebuild the path
    final String fileName = goldenPath.startsWith('goldens/') 
        ? goldenPath.substring('goldens/'.length)
        : goldenPath;
    
    final Uri finalGoldenUri = testGoldensUri.resolve(fileName);
    final File goldenFile = File.fromUri(finalGoldenUri);
    
    await goldenFile.parent.create(recursive: true);
    await goldenFile.writeAsBytes(imageBytes);
    return true; // Always pass
  }

  @override
  Future<void> update(Uri golden, Uint8List imageBytes) async {
    // This method is called when --update-goldens is used
    // Use the same logic as compare() to ensure consistent paths
    await compare(imageBytes, golden);
  }
}

Future< void > testExecutable(FutureOr<void> Function() testMain) async {
  // Set up always-update golden file comparator for all tests
  goldenFileComparator = AlwaysUpdateGoldenFileComparator(Uri.parse('test/'));
  
  // Run the tests
  return testMain();
}

Next Steps

At this point, you will get email notifications each time your screenshots change.

You can then integrate with your Code Review platform of you choice (for example, GitHub), to get build statuses.

Ready to get started?

Sign up or contact us.