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'; |
11 | library; |
12 | |
13 | import 'dart:typed_data'; |
14 | import 'dart:ui'; |
15 | |
16 | import 'package:meta/meta.dart'; |
17 | import 'package:path/path.dart'as path; |
18 | |
19 | import '_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 |  | |
49 | /// | Difference |  | |
50 | /// | Test image after modification |  | |
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. |
59 | abstract 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. |
196 | GoldenFileComparator 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 | ) |
223 | abstract 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. |
328 | WebGoldenComparator get webGoldenComparator => _webGoldenComparator; |
329 | WebGoldenComparator _webGoldenComparator = const _TrivialWebGoldenComparator._(); |
330 | set 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] |
347 | bool 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]. |
365 | class 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 | |
389 | class _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. |
432 | class 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 |
Definitions
- GoldenFileComparator
- compare
- update
- getTestUri
- compareLists
- goldenFileComparator
- WebGoldenComparator
- compare
- update
- compareBytes
- updateBytes
- getTestUri
- webGoldenComparator
- _webGoldenComparator
- webGoldenComparator
- autoUpdateGoldenFiles
- TrivialComparator
- _
- compare
- update
- getTestUri
- _TrivialWebGoldenComparator
- _
- compare
- update
- getTestUri
- compareBytes
- updateBytes
- _warnAboutSkipping
- ComparisonResult
- ComparisonResult
Learn more about Flutter for embedded and desktop on industrialflutter.com