skip to content

Golden Image Tests

matchesGoldenFile compares a rendered widget with a stored PNG, and --update-goldens rewrites the baselines. Interviewers ask why goldens pass locally but fail in CI, and how fonts cause it.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Flutter, how does a golden test with matchesGoldenFile work, and how do you create or refresh its baseline image?

level: juniorimportance: should knowfreq 42%

answer

  1. expectLater, not expect
  2. PNG path relative to the test file
  3. flutter test --update-goldens
  4. nearest RepaintBoundary is captured
  5. failures folder with four diff images

basics

~20 s

matchesGoldenFile renders the widget a finder locates, encodes it as PNG and compares it pixel for pixel with a stored file. Running flutter test --update-goldens writes the current rendering as the new baseline instead of comparing.

solid answer

~40 s

A golden test is a widget test that ends with `await expectLater(find.byKey(const Key('receipt')), matchesGoldenFile('goldens/receipt_light.png'))`. The matcher is asynchronous, so it needs `expectLater` and an `await`. It captures the image of the nearest `RepaintBoundary` at or above the matched widget, encodes it as PNG and hands it to the global `goldenFileComparator`; under `flutter test` that is a `LocalFileComparator`, which resolves the path relative to the test file and demands an exact pixel match. When the file is missing the test fails, so you create or refresh baselines with `flutter test --update-goldens`, which sets `autoUpdateGoldenFiles` and writes the rendering instead of comparing. On a mismatch it writes master, test, isolated-diff and masked-diff PNGs to a `failures` folder next to the goldens. Updated PNGs are reviewed and committed like code.

code

dart · 28 lines
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shop/receipt/receipt_card.dart';

void main() {
  testWidgets('receipt card, light theme', (WidgetTester tester) async {
    tester.view.physicalSize = const Size(1080, 1920);
    tester.view.devicePixelRatio = 3.0;
    addTearDown(tester.view.reset);

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(brightness: Brightness.light),
        home: Scaffold(
          body: RepaintBoundary(
            key: const Key('receipt'),
            child: ReceiptCard(receipt: sampleReceipt),
          ),
        ),
      ),
    );

    await expectLater(
      find.byKey(const Key('receipt')),
      matchesGoldenFile('goldens/receipt_light.png'),
    );
  });
}

go deeper

for a junior

Recall the shape: pump the widget, await expectLater with a finder and matchesGoldenFile, and create the baseline with flutter test --update-goldens.

for a middle

Explain what is captured (the nearest RepaintBoundary), how LocalFileComparator resolves paths and demands an exact match, and what the failures folder contains.

for a senior

Keep goldens deterministic and small: keyed RepaintBoundary captures, fixed view size and theme, and reviewed PNG diffs instead of reflexive updates.

for a principal

Decide which widgets merit pixel tests, since every golden is a baseline someone must re-approve on each visual change.

## What a golden test is A **golden test** (also called a golden file or snapshot test) checks that a widget still *looks* the same. Instead of asserting that a `Text` exists, it renders the widget to an image and compares that image with a **golden file** - a PNG committed to the repository that someone has already approved. In Flutter it is an ordinary `testWidgets` test that ends with the `matchesGoldenFile` matcher from `flutter_test`. A receipt widget is a good candidate: its value is almost entirely visual (alignment of item names and prices, the divider, the bold total), and a finder-based test would pass even if the total drifted onto the wrong line. ## The moving parts - **`matchesGoldenFile(key, {int? version})`** - the matcher. `key` is a `String` path or a `Uri`; `version` is optional and inserts a number before the extension (`receipt.png` becomes `receipt.2.png`). - **`expectLater`** - `matchesGoldenFile` is an `AsyncMatcher`, so it must be used with `await expectLater(...)`. With plain `expect` the comparison may not finish before the test ends. - **What gets captured** - for a `Finder`, the finder must match exactly one widget, and the image is that of the **first `RepaintBoundary` at or above it**. Without an explicit `RepaintBoundary`, that is often the whole route, which makes the golden larger and more fragile than intended. The matcher also accepts a `ui.Image` or a `Future<ui.Image>`. - **`goldenFileComparator`** - a global that performs the comparison. The `flutter test` bootstrap sets it to a **`LocalFileComparator`** for the current test file. - **`LocalFileComparator`** - treats the key as a path **relative to the test file's directory**, decodes both PNGs and requires an **exact** pixel match. A size difference fails immediately ("image sizes do not match"). ## Creating and refreshing baselines 1. Write the test and run it once: it fails with "Could not be compared against non-existent file" because there is no baseline yet. 2. Run `flutter test --update-goldens` (optionally with the test file path). The tool sets `autoUpdateGoldenFiles = true`; in that mode `matchesGoldenFile` calls the comparator's `update` method, which writes the PNG, and the matcher always reports success. 3. **Look at the generated PNGs**, then commit them alongside the test. 4. When a design change is intentional, repeat step 2 and review the image diff in the pull request like any other code change. Updating is never a fix on its own: `--update-goldens` makes any rendering, correct or broken, the new truth. ## Reading a failure When the comparison fails, `LocalFileComparator` throws with a message such as "Pixel test failed, 0.42%, 2016px diff detected." and writes feedback into a **`failures`** directory next to the golden key's location: | File | Shows | |---|---| | `*_masterImage.png` | the committed golden | | `*_testImage.png` | what the test rendered now | | `*_isolatedDiff.png` | only the differing pixels | | `*_maskedDiff.png` | differing pixels overlaid on the golden | Add the `failures` folder to `.gitignore`; it is output, not source. ## Making the capture deterministic - Wrap the widget under test in a `RepaintBoundary` with a key and point the finder at it, so the image contains only the receipt. - Fix the surface size (the default is 800 x 600 logical pixels) with `tester.view.physicalSize` and `devicePixelRatio`, reset through `addTearDown(tester.view.reset)`. - Give it a fixed theme, locale and data - no clocks, no network images, no random order. - Let animations finish before the capture, or the image depends on timing. ## When `flutter run` is used instead Running the same file on a device with `flutter run` uses a `TrivialComparator` that only prints a message, so golden assertions do nothing there. Golden tests are a `flutter test` feature on the host by default; on-device goldens through `integration_test` are a separate setup.

  • Why is await expectLater required rather than expect?
    `matchesGoldenFile` is an `AsyncMatcher`: capturing the layer, encoding the PNG and reading the golden are asynchronous. `expectLater` returns a future that completes when the comparison does, and awaiting it keeps the test alive until the result - pass or failure - is known.
  • What exactly is captured when the finder points at a Text inside a card?
    The image of the nearest `RepaintBoundary` at or above that `Text`. If the card has none, that may be the route's boundary, so the golden contains the whole screen. Put a `RepaintBoundary` around the widget you mean to pin and find that instead.
  • What does the version parameter of matchesGoldenFile do?
    The comparator's `getTestUri` inserts it before the extension, so `matchesGoldenFile('receipt.png', version: 2)` reads and writes `receipt.2.png`. It lets a changed rendering use a new file name rather than overwriting the old baseline in place.

saying these in an interview costs you the question

  • Running --update-goldens to make a red golden test pass without looking at the image
  • Using expect instead of await expectLater with matchesGoldenFile
  • The golden image contains only the widget the finder matched, whatever its ancestors
  • LocalFileComparator tolerates small pixel differences by default
  • Committing the failures folder along with the goldens
open as a page

Why does text in a Flutter golden image render as solid boxes, and how do you make goldens show the app's real fonts?

level: middleimportance: should knowfreq 36%

basics

~20 s

Under flutter test, text uses a test font whose glyphs are boxes: FlutterTest since Flutter 3.10, Ahem before. Unregistered font families fall back to it. Load real fonts with FontLoader, often once in flutter_test_config.dart, accepting that goldens become platform-sensitive.

open as a page

In Flutter golden tests, how do you cover a receipt widget in light and dark themes and at larger text scales without duplicating tests?

level: middleimportance: should knowfreq 30%

basics

~20 s

Parameterise one testWidgets body with a ValueVariant (or a loop) over theme and text scale, build the MaterialApp from the current value, and include that value in the golden file name so each combination gets its own baseline.

open as a page

Flutter golden tests for a receipt widget pass on a developer's macOS laptop but fail in Linux CI with a sub-1% diff; why, and how do you fix it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Rendering is not guaranteed pixel-identical across hosts: real fonts, anti-aliasing and Flutter versions can differ, and LocalFileComparator demands an exact match. Generate and compare goldens on one platform - the CI one - and skip golden checks elsewhere.

open as a page

In Flutter, how do you install a custom goldenFileComparator that tolerates small pixel differences, and what risk does the tolerance carry?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Subclass LocalFileComparator, override compare to call GoldenFileComparator.compareLists and accept results whose diffPercent is under a threshold, then assign it to goldenFileComparator. The risk: diffPercent is the fraction of differing pixels, so a loose threshold hides real regressions.

open as a page