golden_matrix
Matrix-based visual regression testing for Flutter widgets and screens.
Write one golden test declaration, run it across themes, locales, devices, text scales, and UI states.
The Problem
Flutter golden tests check one specific case. When you add themes, locales, device sizes, and states — you get copy-paste and combinatorial explosion:
// Without golden_matrix: manual loops, boilerplate wrappers
for (final locale in supportedLocales) {
for (final device in devices) {
testGoldens('screen_${locale.languageCode}_${device.name}', (tester) async {
// 30+ lines of wrapper setup per combination...
});
}
}
The Solution
// With golden_matrix: one declaration, full coverage
matrixGolden(
'PrimaryButton',
scenarios: [
MatrixScenario('default', builder: () => const PrimaryButton(label: 'OK')),
MatrixScenario('disabled', builder: () => const PrimaryButton(label: 'OK', enabled: false)),
],
axes: MatrixAxes(
themes: [MatrixTheme.light, MatrixTheme.dark],
locales: [Locale('en'), Locale('ar')],
textScales: [1.0, 2.0],
devices: [MatrixDevice.phoneSmall, MatrixDevice.phoneLarge],
),
);
// 2 scenarios x 2 themes x 2 locales x 2 scales x 2 devices = 32 golden files
Features
- Declarative matrix — define axes (themes, locales, devices, text scales, directions), get all combinations automatically
- Smart defaults —
MatrixAxes()with no arguments produces one valid test (light, en, 1.0x, phoneSmall) - Direction inference — Arabic, Hebrew, Farsi automatically get RTL; no manual setup
- Sampling strategies —
full,smoke,priorityBased,pairwise(all-pairs coverage) - Pairwise sampling — covers all parameter pairs with minimal test cases (e.g. 270 → ~30)
- Presets —
MatrixPreset.componentSmoke,componentFull,screenSmokefor quick setup - Exclude/include rules —
MatrixRule.exclude(...),MatrixRule.includeOnly(...)with predicates - Screen-level testing —
screenMatrixGolden()with full control viaappBuilder - Overflow detection — captures
RenderFlex overflowand layout errors as warnings in reports - HTML reports — self-contained HTML with thumbnails, scenario grouping, filters, dark mode
- Tolerance — configurable pixel diff threshold for flaky-free CI
- Custom themes —
MatrixTheme.datafor attaching arbitrary context (custom theme systems, brand config) - 7 device presets — phoneSmall, phoneMedium, phoneLarge, androidSmall, androidMedium, tablet, tabletLandscape (+ named aliases)
- Font loading —
loadAppFonts()loads real fonts (Roboto + app fonts) instead of Ahem squares - Zero external dependencies — only Flutter SDK
Quick Start
1. Add dependency
# pubspec.yaml
dev_dependencies:
golden_matrix: ^0.6.0
2. Set up font loading
// test/flutter_test_config.dart
import 'dart:async';
import 'package:golden_matrix/golden_matrix.dart';
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
await loadAppFonts();
return testMain();
}
3. Write your first matrix test
import 'package:flutter/widgets.dart';
import 'package:golden_matrix/golden_matrix.dart';
void main() {
matrixGolden(
'MyButton',
scenarios: [
MatrixScenario('default', builder: () => const MyButton(label: 'OK')),
MatrixScenario('disabled', builder: () => const MyButton(label: 'OK', enabled: false)),
],
axes: MatrixAxes(
themes: [MatrixTheme.light, MatrixTheme.dark],
devices: [MatrixDevice.phoneSmall, MatrixDevice.tablet],
),
);
}
4. Generate baselines and run
flutter test --update-goldens # generate baselines
flutter test # run regression tests
API
matrixGolden — component testing
Auto-wraps your widget in a MaterialApp with theme, locale, directionality, text scale, and device configuration.
matrixGolden(
'ProfileCard',
scenarios: [
MatrixScenario('loading', builder: () => const ProfileCard.loading()),
MatrixScenario('data', builder: () => ProfileCard(user: fakeUser)),
MatrixScenario('error', builder: () => const ProfileCard.error('Timeout')),
],
axes: MatrixAxes(
themes: [MatrixTheme.light, MatrixTheme.dark],
locales: [Locale('en'), Locale('ru'), Locale('ar')],
textScales: [1.0, 2.0],
devices: [MatrixDevice.iphoneSE, MatrixDevice.galaxyA51, MatrixDevice.tablet],
),
rules: [
MatrixRule.exclude((c) => c.locale.languageCode != 'ar' && c.direction == TextDirection.rtl),
],
);
screenMatrixGolden — screen testing
You provide the full MaterialApp via appBuilder — for DI, navigation, custom themes, etc.
screenMatrixGolden(
'LoginScreen',
appBuilder: (combination) => MaterialApp(
theme: combination.theme.resolve(),
locale: combination.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: LoginScreen(
errorMessage: combination.scenario.name == 'error' ? 'Invalid credentials' : null,
),
),
states: [
MatrixScenario('default', builder: () => const SizedBox.shrink()),
MatrixScenario('error', builder: () => const SizedBox.shrink()),
],
preset: MatrixPreset.screenSmoke,
);
Presets
matrixGolden('Widget', scenarios: [...], preset: MatrixPreset.componentSmoke);
matrixGolden('Widget', scenarios: [...], preset: MatrixPreset.componentFull);
screenMatrixGolden('Screen', appBuilder: ..., preset: MatrixPreset.screenSmoke);
Sampling
// Full Cartesian product (default)
matrixGolden('Widget', scenarios: [...], axes: axes);
// Smoke: base combo + one delta per axis (~5 instead of 32)
matrixGolden('Widget', scenarios: [...], axes: axes, sampling: MatrixSampling.smoke);
// Pairwise: all parameter pairs covered (~12 instead of 36)
matrixGolden('Widget', scenarios: [...], axes: axes, sampling: MatrixSampling.pairwise);
// Priority-based: high-value combos first, capped at N
matrixGolden('Widget', scenarios: [...], axes: axes,
sampling: MatrixSampling.priorityBased, maxCombinations: 10);
Rules
MatrixRule.exclude((c) => c.theme.name == 'dark' && c.textScale > 1.5)
MatrixRule.includeOnly((c) => c.device.name == 'phoneSmall' || c.device.name == 'tablet')
Tolerance
Allow small pixel differences for stable CI:
matrixGolden(
'Widget',
scenarios: [...],
axes: axes,
tolerance: 0.05 / 100, // 0.05% pixel diff allowed
);
Skip
Conditionally skip tests (e.g. platform-specific golden files):
matrixGolden(
'Widget',
scenarios: [...],
axes: axes,
skip: !Platform.isMacOS,
);
Custom Wrapper
Override the default Scaffold(body: Center(child:)) layout:
matrixGolden(
'Widget',
scenarios: [...],
axes: axes,
wrapChild: (child) => child, // no Scaffold, no Center
);
Custom Theme Data
Attach arbitrary context to themes — custom theme systems, brand configs, feature flags:
matrixGolden(
'Widget',
scenarios: [...],
axes: MatrixAxes(
themes: [
MatrixTheme.custom('light', ThemeData.light(), data: MyTheme.light()),
MatrixTheme.custom('dark', ThemeData.dark(), data: MyTheme.dark()),
],
),
);
// Access in screenMatrixGolden appBuilder:
appBuilder: (combination) {
final myTheme = combination.theme.data as MyTheme;
return MyThemeProvider(theme: myTheme, child: MaterialApp(...));
}
Device Presets
// Generic sizes
MatrixDevice.phoneSmall // 375x667, 2.0x (iPhone SE)
MatrixDevice.phoneMedium // 390x844, 3.0x (iPhone 15)
MatrixDevice.phoneLarge // 414x896, 3.0x (iPhone 15 Pro Max)
MatrixDevice.androidSmall // 360x800, 4.0x (Galaxy S20)
MatrixDevice.androidMedium // 412x915, 2.625x (Galaxy A51)
MatrixDevice.tablet // 768x1024, 2.0x (iPad)
MatrixDevice.tabletLandscape // 1024x768, 2.0x
// Named aliases
MatrixDevice.iphoneSE // = phoneSmall
MatrixDevice.iphone15 // = phoneMedium
MatrixDevice.galaxyS20 // = androidSmall
MatrixDevice.galaxyA51 // = androidMedium
// Custom
MatrixDevice(name: 'pixel7', logicalSize: Size(412, 915), pixelRatio: 2.75)
Overflow Detection
golden_matrix automatically captures RenderFlex overflow and layout errors during rendering. Warnings appear in JSON and HTML reports with orange badges — no configuration needed.
HTML Reports
After tests run, golden_matrix generates self-contained HTML reports alongside golden files:
- Summary with pass/fail/warning counts
- Scenario grouping with collapsible sections
- Thumbnail grid with clickable full-size images
- Filter by scenario, theme, or status
- Dark mode support via
prefers-color-scheme
See example reports and golden files in the GitHub repository.
Golden File Structure
goldens/
default/
light_en_ltr_1x_phonesmall.png
dark_ar_rtl_2x_phonelarge.png
disabled/
light_en_ltr_1x_phonesmall.png
Naming: goldens/<scenario>/<theme>_<locale>_<direction>_<textScale>_<device>.png
Requirements
- Flutter SDK >= 3.16.0
- Dart SDK >= 3.2.0
License
MIT