1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/widgets.dart';
6///
7/// @docImport '_goldens_io.dart';
8/// @docImport 'binding.dart';
9/// @docImport 'matchers.dart';
10/// @docImport 'widget_tester.dart';
11library;
12
13import 'dart:typed_data';
14import 'dart:ui';
15
16import 'package:meta/meta.dart';
17import 'package:path/path.dart' as path;
18
19import '_goldens_io.dart' if (dart.library.js_interop) '_goldens_web.dart' as goldens;
20
21/// Compares image pixels against a golden image file.
22///
23/// Instances of this comparator will be used as the backend for
24/// [matchesGoldenFile].
25///
26/// Instances of this comparator will be invoked by the test framework in the
27/// [TestWidgetsFlutterBinding.runAsync] zone and are thus not subject to the
28/// fake async constraints that are normally imposed on widget tests (i.e. the
29/// need or the ability to call [WidgetTester.pump] to advance the microtask
30/// queue).
31///
32/// ## What is Golden File Testing?
33///
34/// The term __golden file__ refers to a master image that is considered the true
35/// rendering of a given widget, state, application, or other visual
36/// representation you have chosen to capture.
37///
38/// By keeping a master reference of visual aspects of your application, you can
39/// prevent unintended changes as you develop by testing against them.
40///
41/// Here, a minor code change has altered the appearance of a widget. A golden
42/// file test has compared the image generated at the time of the test to the
43/// golden master file that was generated earlier. The test has identified the
44/// change, preventing unintended modifications.
45///
46/// | Sample | Image |
47/// |--------------------------------|--------|
48/// | Golden Master Image | ![A golden master image](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_masterImage.png) |
49/// | Difference | ![The pixel difference](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_isolatedDiff.png) |
50/// | Test image after modification | ![Test image](https://flutter.github.io/assets-for-api-docs/assets/flutter-test/goldens/widget_testImage.png) |
51///
52/// {@macro flutter.flutter_test.matchesGoldenFile.custom_fonts}
53///
54/// See also:
55///
56/// * [LocalFileComparator] for the default [GoldenFileComparator]
57/// implementation for `flutter test`.
58/// * [matchesGoldenFile], the function that invokes the comparator.
59abstract class GoldenFileComparator {
60 /// Compares the pixels of decoded png [imageBytes] against the golden file
61 /// identified by [golden].
62 ///
63 /// The returned future completes with a boolean value that indicates whether
64 /// the pixels decoded from [imageBytes] match the golden file's pixels.
65 ///
66 /// In the case of comparison mismatch, the comparator may choose to throw a
67 /// [TestFailure] if it wants to control the failure message, often in the
68 /// form of a [ComparisonResult] that provides detailed information about the
69 /// mismatch.
70 ///
71 /// The method by which [golden] is located and by which its bytes are loaded
72 /// is left up to the implementation class. For instance, some implementations
73 /// may load files from the local file system, whereas others may load files
74 /// over the network or from a remote repository.
75 Future<bool> compare(Uint8List imageBytes, Uri golden);
76
77 /// Updates the golden file identified by [golden] with [imageBytes].
78 ///
79 /// This will be invoked in lieu of [compare] when [autoUpdateGoldenFiles]
80 /// is `true` (which gets set automatically by the test framework when the
81 /// user runs `flutter test --update-goldens`).
82 ///
83 /// The method by which [golden] is located and by which its bytes are written
84 /// is left up to the implementation class.
85 Future<void> update(Uri golden, Uint8List imageBytes);
86
87 /// Returns a new golden file [Uri] to incorporate any [version] number with
88 /// the [key].
89 ///
90 /// The [version] is an optional int that can be used to differentiate
91 /// historical golden files.
92 ///
93 /// Version numbers are used in golden file tests for package:flutter. You can
94 /// learn more about these tests [here](https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md).
95 Uri getTestUri(Uri key, int? version) {
96 if (version == null) {
97 return key;
98 }
99 final String keyString = key.toString();
100 final String extension = path.extension(keyString);
101 return Uri.parse('${keyString.split(extension).join()}.$version$extension');
102 }
103
104 /// Returns a [ComparisonResult] to describe the pixel differential of the
105 /// [test] and [master] image bytes provided.
106 static Future<ComparisonResult> compareLists(List<int> test, List<int> master) {
107 return goldens.compareLists(test, master);
108 }
109}
110
111/// Compares pixels against those of a golden image file.
112///
113/// This comparator is used as the backend for [matchesGoldenFile].
114///
115/// When using `flutter test`, a comparator implemented by [LocalFileComparator]
116/// is used if no other comparator is specified. It treats the golden key as
117/// a relative path from the test file's directory. It will then load the
118/// golden file's bytes from disk and perform a pixel-for-pixel comparison of
119/// the decoded PNGs, returning true only if there's an exact match.
120///
121/// When using `flutter test --update-goldens`, the [LocalFileComparator]
122/// updates the files on disk to match the rendering.
123///
124/// When using `flutter run`, the default comparator ([TrivialComparator])
125/// is used. It prints a message to the console but otherwise does nothing. This
126/// allows tests to be developed visually on a real device.
127///
128/// Callers may choose to override the default comparator by setting this to a
129/// custom comparator during test set-up (or using directory-level test
130/// configuration).
131///
132/// {@tool snippet}
133/// For example, some projects may wish to install a comparator with tolerance
134/// levels for allowable differences:
135///
136/// ```dart
137/// void main() {
138/// testWidgets('matches golden file with a 0.01 tolerance', (WidgetTester tester) async {
139/// final GoldenFileComparator previousGoldenFileComparator = goldenFileComparator;
140/// goldenFileComparator = _TolerantGoldenFileComparator(
141/// Uri.parse('test/my_widget_test.dart'),
142/// precisionTolerance: 0.01,
143/// );
144/// addTearDown(() => goldenFileComparator = previousGoldenFileComparator);
145///
146/// await tester.pumpWidget(const ColoredBox(color: Color(0xff00ff00)));
147///
148/// await expectLater(
149/// find.byType(ColoredBox),
150/// matchesGoldenFile('my_golden.png'),
151/// );
152/// });
153/// }
154///
155/// class _TolerantGoldenFileComparator extends LocalFileComparator {
156/// _TolerantGoldenFileComparator(
157/// super.testFile, {
158/// required double precisionTolerance,
159/// }) : assert(
160/// 0 <= precisionTolerance && precisionTolerance <= 1,
161/// 'precisionTolerance must be between 0 and 1',
162/// ),
163/// _precisionTolerance = precisionTolerance;
164///
165/// /// How much the golden image can differ from the test image.
166/// ///
167/// /// It is expected to be between 0 and 1. Where 0 is no difference (the same image)
168/// /// and 1 is the maximum difference (completely different images).
169/// final double _precisionTolerance;
170///
171/// @override
172/// Future<bool> compare(Uint8List imageBytes, Uri golden) async {
173/// final ComparisonResult result = await GoldenFileComparator.compareLists(
174/// imageBytes,
175/// await getGoldenBytes(golden),
176/// );
177///
178/// final bool passed = result.passed || result.diffPercent <= _precisionTolerance;
179/// if (passed) {
180/// result.dispose();
181/// return true;
182/// }
183///
184/// final String error = await generateFailureOutput(result, golden, basedir);
185/// result.dispose();
186/// throw FlutterError(error);
187/// }
188/// }
189/// ```
190/// {@end-tool}
191///
192/// See also:
193///
194/// * [flutter_test] for more information about how to configure tests at the
195/// directory-level.
196GoldenFileComparator goldenFileComparator = const TrivialComparator._();
197
198/// Compares image pixels against a golden image file.
199///
200/// Instances of this comparator will be used as the backend for
201/// [matchesGoldenFile] when tests are running on Flutter Web, and will usually
202/// implemented by deferring the screenshot taking and image comparison to a
203/// test server.
204///
205/// Instances of this comparator will be invoked by the test framework in the
206/// [TestWidgetsFlutterBinding.runAsync] zone and are thus not subject to the
207/// fake async constraints that are normally imposed on widget tests (i.e. the
208/// need or the ability to call [WidgetTester.pump] to advance the microtask
209/// queue). Prior to the invocation, the test framework will render only the
210/// [Element] to be compared on the screen.
211///
212/// See also:
213///
214/// * [GoldenFileComparator] for the comparator to be used when the test is
215/// not running in a web browser.
216/// * [DefaultWebGoldenComparator] for the default [WebGoldenComparator]
217/// implementation for `flutter test`.
218/// * [matchesGoldenFile], the function that invokes the comparator.
219@Deprecated(
220 'Use GoldenFileComparator instead. '
221 'This feature was deprecated after v3.28.0-0.1.pre.',
222)
223abstract class WebGoldenComparator {
224 /// Compares the rendered pixels of size [width]x[height] that is being
225 /// rendered on the top left of the screen against the golden file identified
226 /// by [golden].
227 ///
228 /// The returned future completes with a boolean value that indicates whether
229 /// the pixels rendered on screen match the golden file's pixels.
230 ///
231 /// In the case of comparison mismatch, the comparator may choose to throw a
232 /// [TestFailure] if it wants to control the failure message, often in the
233 /// form of a [ComparisonResult] that provides detailed information about the
234 /// mismatch.
235 ///
236 /// The method by which [golden] is located and by which its bytes are loaded
237 /// is left up to the implementation class. For instance, some implementations
238 /// may load files from the local file system, whereas others may load files
239 /// over the network or from a remote repository.
240 Future<bool> compare(double width, double height, Uri golden);
241
242 /// Updates the golden file identified by [golden] with rendered pixels of
243 /// [width]x[height].
244 ///
245 /// This will be invoked in lieu of [compare] when [autoUpdateGoldenFiles]
246 /// is `true` (which gets set automatically by the test framework when the
247 /// user runs `flutter test --update-goldens --platform=chrome`).
248 ///
249 /// The method by which [golden] is located and by which its bytes are written
250 /// is left up to the implementation class.
251 Future<void> update(double width, double height, Uri golden);
252
253 /// Compares the pixels of decoded png [bytes] against the golden file
254 /// identified by [golden].
255 ///
256 /// The returned future completes with a boolean value that indicates whether
257 /// the pixels rendered on screen match the golden file's pixels.
258 ///
259 /// In the case of comparison mismatch, the comparator may choose to throw a
260 /// [TestFailure] if it wants to control the failure message, often in the
261 /// form of a [ComparisonResult] that provides detailed information about the
262 /// mismatch.
263 ///
264 /// The method by which [golden] is located and by which its bytes are loaded
265 /// is left up to the implementation class. For instance, some implementations
266 /// may load files from the local file system, whereas others may load files
267 /// over the network or from a remote repository.
268 Future<bool> compareBytes(Uint8List bytes, Uri golden);
269
270 /// Compares the pixels of decoded png [bytes] against the golden file
271 /// identified by [golden].
272 ///
273 /// This will be invoked in lieu of [compareBytes] when [autoUpdateGoldenFiles]
274 /// is `true` (which gets set automatically by the test framework when the
275 /// user runs `flutter test --update-goldens --platform=chrome`).
276 ///
277 /// The method by which [golden] is located and by which its bytes are written
278 /// is left up to the implementation class.
279 Future<void> updateBytes(Uint8List bytes, Uri golden);
280
281 /// Returns a new golden file [Uri] to incorporate any [version] number with
282 /// the [key].
283 ///
284 /// The [version] is an optional int that can be used to differentiate
285 /// historical golden files.
286 ///
287 /// Version numbers are used in golden file tests for package:flutter. You can
288 /// learn more about these tests [here](https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md).
289 Uri getTestUri(Uri key, int? version) {
290 if (version == null) {
291 return key;
292 }
293 final String keyString = key.toString();
294 final String extension = path.extension(keyString);
295 return Uri.parse('${keyString.split(extension).join()}.$version$extension');
296 }
297}
298
299/// Compares pixels against those of a golden image file.
300///
301/// This comparator is used as the backend for [matchesGoldenFile] when tests
302/// are running in a web browser.
303///
304/// When using `flutter test --platform=chrome`, a comparator implemented by
305/// [DefaultWebGoldenComparator] is used if no other comparator is specified. It
306/// will send a request to the test server, which uses [goldenFileComparator]
307/// for golden file comparison.
308///
309/// When using `flutter test --update-goldens`, the [DefaultWebGoldenComparator]
310/// updates the files on disk to match the rendering.
311///
312/// When using `flutter run`, the default comparator
313/// (`_TrivialWebGoldenComparator`) is used. It prints a message to the console
314/// but otherwise does nothing. This allows tests to be developed visually on a
315/// web browser.
316///
317/// Callers may choose to override the default comparator by setting this to a
318/// custom comparator during test set-up (or using directory-level test
319/// configuration). For example, some projects may wish to install a comparator
320/// with tolerance levels for allowable differences.
321///
322/// See also:
323///
324/// * [flutter_test] for more information about how to configure tests at the
325/// directory-level.
326/// * [goldenFileComparator], the comparator used when tests are not running on
327/// a web browser.
328WebGoldenComparator get webGoldenComparator => _webGoldenComparator;
329WebGoldenComparator _webGoldenComparator = const _TrivialWebGoldenComparator._();
330set webGoldenComparator(WebGoldenComparator value) {
331 _webGoldenComparator = value;
332}
333
334/// Whether golden files should be automatically updated during tests rather
335/// than compared to the image bytes recorded by the tests.
336///
337/// When this is `true`, [matchesGoldenFile] will always report a successful
338/// match, because the bytes being tested implicitly become the new golden.
339///
340/// The Flutter tool will automatically set this to `true` when the user runs
341/// `flutter test --update-goldens`, so callers should generally never have to
342/// explicitly modify this value.
343///
344/// See also:
345///
346/// * [goldenFileComparator]
347bool autoUpdateGoldenFiles = false;
348
349/// Placeholder comparator that is set as the value of [goldenFileComparator]
350/// when the initialization that happens in the test bootstrap either has not
351/// yet happened or has been bypassed.
352///
353/// The test bootstrap file that gets generated by the Flutter tool when the
354/// user runs `flutter test` is expected to set [goldenFileComparator] to
355/// a comparator that resolves golden file references relative to the test
356/// directory. From there, the caller may choose to override the comparator by
357/// setting it to another value during test initialization. The only case
358/// where we expect it to remain uninitialized is when the user runs a test
359/// via `flutter run`. In this case, the [compare] method will just print a
360/// message that it would have otherwise run a real comparison, and it will
361/// return trivial success.
362///
363/// This class can't be constructed. It represents the default value of
364/// [goldenFileComparator].
365class TrivialComparator implements GoldenFileComparator {
366 const TrivialComparator._();
367
368 @override
369 Future<bool> compare(Uint8List imageBytes, Uri golden) {
370 // Ideally we would use markTestSkipped here but in some situations,
371 // comparators are called outside of tests.
372 // See also: https://github.com/flutter/flutter/issues/91285
373 // ignore: avoid_print
374 print('Golden file comparison requested for "$golden"; skipping...');
375 return Future<bool>.value(true);
376 }
377
378 @override
379 Future<void> update(Uri golden, Uint8List imageBytes) {
380 throw StateError('goldenFileComparator has not been initialized');
381 }
382
383 @override
384 Uri getTestUri(Uri key, int? version) {
385 return key;
386 }
387}
388
389class _TrivialWebGoldenComparator implements WebGoldenComparator {
390 const _TrivialWebGoldenComparator._();
391
392 @override
393 Future<bool> compare(double width, double height, Uri golden) {
394 return _warnAboutSkipping(golden);
395 }
396
397 @override
398 Future<void> update(double width, double height, Uri golden) {
399 throw StateError('webGoldenComparator has not been initialized');
400 }
401
402 @override
403 Uri getTestUri(Uri key, int? version) {
404 return key;
405 }
406
407 @override
408 Future<bool> compareBytes(Uint8List bytes, Uri golden) {
409 return _warnAboutSkipping(golden);
410 }
411
412 @override
413 Future<void> updateBytes(Uint8List bytes, Uri golden) {
414 throw StateError('webGoldenComparator has not been initialized');
415 }
416
417 Future<bool> _warnAboutSkipping(Uri golden) {
418 // Ideally we would use markTestSkipped here but in some situations,
419 // comparators are called outside of tests.
420 // See also: https://github.com/flutter/flutter/issues/91285
421 // ignore: avoid_print
422 print('Golden comparison requested for "$golden"; skipping...');
423 return Future<bool>.value(true);
424 }
425}
426
427/// The result of a pixel comparison test.
428///
429/// The [ComparisonResult] will always indicate if a test has [passed]. The
430/// optional [error] and [diffs] parameters provide further information about
431/// the result of a failing test.
432class ComparisonResult {
433 /// Creates a new [ComparisonResult] for the current test.
434 ComparisonResult({required this.passed, required this.diffPercent, this.error, this.diffs});
435
436 /// Indicates whether or not a pixel comparison test has failed.
437 final bool passed;
438
439 /// Error message used to describe the cause of the pixel comparison failure.
440 final String? error;
441
442 /// Map containing differential images to illustrate found variants in pixel
443 /// values in the execution of the pixel test.
444 final Map<String, Image>? diffs;
445
446 /// The calculated percentage of pixel difference between two images.
447 final double diffPercent;
448
449 /// Disposes the images held by this [ComparisonResult].
450 @mustCallSuper
451 void dispose() {
452 if (diffs == null) {
453 return;
454 }
455
456 for (final MapEntry<String, Image> entry in diffs!.entries) {
457 entry.value.dispose();
458 }
459 }
460}
461

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com