Flutter golden image tests diff threshold
Asked Answered
T

3

10

I am making use of golden image tests for simple stateless widgets in flutter. Every few test runs, I get the following error printed to console:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test (but after the test had completed):
  Expected: one widget whose rasterized image matches golden image
"goldens/mywidget.png"
  Actual: _WidgetTypeFinder:<zero widgets with type "MyWidget" (ignoring offstage widgets)>
   Which: Golden "goldens/mywidget.png": Pixel test failed, 0.04% diff detected.
          Failure feedback can be found at
/home/cirrus/project/test/widgets/failures

When the exception was thrown, this was the stack:

This is infuriating for 2 reasons:

  1. Nothing has changed, the diffs appear to be subpixel rendering differences. This shouldn't be happening, and is thus rendering golden tests pretty useless.
  2. Even with the message like the above, the test passes. So flutter thinks there's an exception, but doesn't even fail the test for it.

Is there a way to set a diff threshold so that tests with a diff below 5% for example are ignored, to avoid my test output being overly verbose and noisy?

It is worth noting that this issue is not isolated to our CI server, it happens on our local machines too, so seems odd that the same machine would render the test differently depending on the time of day.

Tema answered 24/6, 2020 at 9:9 Comment(4)
I have only found api.flutter.dev/flutter/flutter_test/goldenFileComparator.html where it would be possible to add a custom comparator (perhaps copy-paste the Flutter-integrated with some adjustment or perhaps it can be inherited - not checked yet)Pluckless
I'm no longer working on the project, but in the end it was quicker and easier to just replace the golden file tests with standard widget tests. Something akin to Jest snapshot tests would be more useful than image comparison IMOTema
I just posted it for others landing here. Jest looks interesting. I think that would be a great fit for Flutter as well.Pluckless
Issue #2 would likely be because you are not awaiting the expectLater. The test is reaching the end and passing, and then the exception occurs afterwards.Oler
R
9

I encountered the same problem, and was looking for a similar solution. After discussion on a Github issue I created, I learned a way to set the threshold.

First, create a file somewhere in your test folder (I put it in test/utils), say local_file_comparator_with_threshold.dart:

    import 'dart:typed_data';
    
    import 'package:flutter/foundation.dart';
    import 'package:flutter_test/flutter_test.dart';
    
    /// Works just like [LocalFileComparator] but includes a [threshold] that, when
    /// exceeded, marks the test as a failure.
    class LocalFileComparatorWithThreshold extends LocalFileComparator {
      /// Threshold above which tests will be marked as failing.
      /// Ranges from 0 to 1, both inclusive.
      final double threshold;
    
      LocalFileComparatorWithThreshold(Uri testFile, this.threshold)
          : assert(threshold >= 0 && threshold <= 1),
            super(testFile);
    
      /// Copy of [LocalFileComparator]'s [compare] method, except for the fact that
      /// it checks if the [ComparisonResult.diffPercent] is not greater than
      /// [threshold] to decide whether this test is successful or a failure.
      @override
      Future<bool> compare(Uint8List imageBytes, Uri golden) async {
        final result = await GoldenFileComparator.compareLists(
          imageBytes,
          await getGoldenBytes(golden),
        );
    
        if (!result.passed && result.diffPercent <= threshold) {
          debugPrint(
            'A difference of ${result.diffPercent * 100}% was found, but it is '
            'acceptable since it is not greater than the threshold of '
            '${threshold * 100}%',
          );
    
          return true;
        }
    
        if (!result.passed) {
          final error = await generateFailureOutput(result, golden, basedir);
          throw FlutterError(error);
        }
        return result.passed;
      }
    }

And then, you can override your test configuration in flutter_test_config.dart (you need to put in your project's test folder, create one if you don't already have one):

import 'dart:async';

import 'package:flutter_test/flutter_test.dart';

import 'utils/local_file_comparator_with_threshold.dart';

/// Customise your threshold here
/// For example, the error threshold here is 0.5%
/// Golden tests will pass if the pixel difference is equal to or below 0.5%
const _kGoldenTestsThreshold = 0.5 / 100;

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  if (goldenFileComparator is LocalFileComparator) {
    final testUrl = (goldenFileComparator as LocalFileComparator).basedir;

    goldenFileComparator = LocalFileComparatorWithThreshold(
      // flutter_test's LocalFileComparator expects the test's URI to be passed
      // as an argument, but it only uses it to parse the baseDir in order to
      // obtain the directory where the golden tests will be placed.
      // As such, we use the default `testUrl`, which is only the `baseDir` and
      // append a generically named `test.dart` so that the `baseDir` is
      // properly extracted.
      Uri.parse('$testUrl/test.dart'),
      _kGoldenTestsThreshold,
    );
  } else {
    throw Exception(
      'Expected `goldenFileComparator` to be of type `LocalFileComparator`, '
      'but it is of type `${goldenFileComparator.runtimeType}`',
    );
  }

  await testMain();
}

After this setup, whenever you run your golden tests in the project, they will pass when the pixel difference is less than or equal to the threshold you set in flutter_test_config.dart.

Roush answered 20/7, 2022 at 9:4 Comment(0)
A
0

This error can be caused due to the font that is using in your project (widget)

Assembler answered 12/10, 2021 at 10:59 Comment(0)
H
0

I have published useGoldenFileComparatorWithThreshold() function in my package called hrk_flutter_test_batteries.

  1. First add the package

    flutter pub add dev:hrk_flutter_test_batteries
    
  2. Use it as follows

    testWidgets('Test description', (tester) async {
    
      ...
    
      useGoldenFileComparatorWithThreshold(0.05); // 05.00%
      await expectLater(
        find.byType(TestWidget),
        matchesGoldenFile('test_widget.png'),
      );
    });
    
Highbred answered 30/7 at 20:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.