In Flutter, how does a golden test with matchesGoldenFile work, and how do you create or refresh its baseline image?
answer
- expectLater, not expect
- PNG path relative to the test file
- flutter test --update-goldens
- nearest RepaintBoundary is captured
- failures folder with four diff images
basics
~20 smatchesGoldenFile 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 sA 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 linesimport '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
Recall the shape: pump the widget, await expectLater with a finder and matchesGoldenFile, and create the baseline with flutter test --update-goldens.
Explain what is captured (the nearest RepaintBoundary), how LocalFileComparator resolves paths and demands an exact match, and what the failures folder contains.
Keep goldens deterministic and small: keyed RepaintBoundary captures, fixed view size and theme, and reviewed PNG diffs instead of reflexive updates.
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