Giter Site home page Giter Site logo

betterment / alchemist Goto Github PK

View Code? Open in Web Editor NEW
240.0 7.0 30.0 2.4 MB

A Flutter tool that makes golden testing easy.

License: MIT License

Dart 81.29% Shell 0.33% Kotlin 0.06% Swift 0.49% Objective-C 0.02% CMake 7.64% C++ 8.81% C 0.60% HTML 0.77%

alchemist's Introduction

๐Ÿง™๐Ÿผ Alchemist

Very Good Ventures

Betterment

Developed with ๐Ÿ’™ by Very Good Ventures ๐Ÿฆ„ and Betterment โ˜€๏ธ.

ci codecov pub package License: MIT


A Flutter tool that makes golden testing easy.

Alchemist is a Flutter package that provides functions, extensions and documentation to support golden tests.

Heavily inspired by Ebay Motor's golden_toolkit package, Alchemist attempts to make writing and running golden tests in Flutter easier.

A short guide can be found in example.md file (or the example tab on pub.dev). A full example project is available in the example directory.

Feature Overview

Table of Contents

About platform tests vs. CI tests

Alchemist can perform two kinds of golden tests.

One is platform tests, which generate golden files with human readable text. These can be considered regular golden tests and are usually only run on a local machine.

Example platform golden test

The other is CI tests, which look and function the same as platform tests, except that the text blocks are replaced with colored squares.

Example CI golden test

The reason for this distinction is that the output of platform tests is dependent on the platform the test is running on. In particular, individual platforms are known to render text differently than others. This causes readable golden files generated on macOS, for example, to be ever so slightly off from the golden files generated on other platforms, such as Windows or Linux, causing CI systems to fail the test. CI tests, on the other hand, were made to circumvent this, and will always have the same output regardless of the platform.

Additionally, CI tests are always run using the Ahem font family, which is a font that solely renders square characters. This is done to ensure that CI tests are platform agnostic -- their output is always consistent regardless of the host platform.

Basic usage

Writing the test

In your project's test/ directory, add a file for your widget's tests. Then, write and run golden tests by using the goldenTest function.

We recommend putting all golden tests related to the same component into a test group.

Every goldenTest commonly contains a group of scenarios related to each other (for example, all scenarios that test the same constructor or widget in a particular context).

This example shows a basic golden test for ListTiles that makes use of some of the more advanced features of the goldenTest API to control the output of the test.

import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('ListTile Golden Tests', () {
    goldenTest(
      'renders correctly',
      fileName: 'list_tile',
      builder: () => GoldenTestGroup(
        scenarioConstraints: const BoxConstraints(maxWidth: 600),
        children: [
          GoldenTestScenario(
            name: 'with title',
            child: ListTile(
              title: Text('ListTile.title'),
            ),
          ),
          GoldenTestScenario(
            name: 'with title and subtitle',
            child: ListTile(
              title: Text('ListTile.title'),
              subtitle: Text('ListTile.subtitle'),
            ),
          ),
          GoldenTestScenario(
            name: 'with trailing icon',
            child: ListTile(
              title: Text('ListTile.title'),
              trailing: Icon(Icons.chevron_right_rounded),
            ),
          ),
        ],
      ),
    );
  });
}

Then, simply run Flutter test and pass the --update-goldens flag to generate the golden files.

flutter test --update-goldens

Recommended Setup Guide

For a more detailed explanation on how Betterment uses Alchemist, read the included Recommended Setup Guide.

Test groups

While the goldenTest function can take in and performs tests on any arbitrary widget, it is most commonly given a GoldenTestGroup. This is a widget used for organizing a set of widgets that groups multiple testing scenarios together and arranges them in a table format.

Alongside the children parameter, GoldenTestGroup contains two additional properties that can be used to customize the resulting table view:

Field Default Description
int? columns null The amount of columns in the grid. If left unset, this will be determined based on the amount of children.
ColumnWidthBuilder? columnWidthBuilder null A function that returns the width for each column. If left unset, the width of each column is determined by the width of the widest widget in that column.

Test scenarios

Golden test scenarios are typically encapsulated in a GoldenTestScenario widget. This widget contains a name property that is used to identify the scenario, along with the widget it should display. The regular constructor allows a name and child to be passed in, but the .builder and .withTextScaleFactor constructors allow the use of a widget builder and text scale factor to be passed in respectively.

Generating the golden file

To run the test and generate the golden file, run flutter test with the --update-goldens flag.

# Should always succeed
flutter test --update-goldens

After all golden tests have run, the generated golden files will be in the goldens/ci/ directory relative to the test file. Depending on the platform the test was run on (and the current AlchemistConfig), platform goldens will be in the goldens/<platform_name> directory.

lib/
test/
โ”œโ”€ goldens/
โ”‚  โ”œโ”€ ci/
โ”‚  โ”‚  โ”œโ”€ my_widget.png
โ”‚  โ”œโ”€ macos/
โ”‚  โ”‚  โ”œโ”€ my_widget.png
โ”‚  โ”œโ”€ linux/
โ”‚  โ”‚  โ”œโ”€ my_widget.png
โ”‚  โ”œโ”€ windows/
โ”‚  โ”‚  โ”œโ”€ my_widget.png
โ”œโ”€ my_widget_golden_test.dart
pubspec.yaml

Testing and comparing

When you want to run golden tests regularly and compare them to the generated golden files (in a CI process for example), simply run flutter test.

By default, all golden tests will have a "golden" tag, meaning you can select when to run golden tests.

# Run all tests.
flutter test

# Only run golden tests.
flutter test --tags golden

# Run all tests except golden tests.
flutter test --exclude-tags golden

Advanced usage

Alchemist has several extensions and mechanics to accommodate for more advanced golden testing scenarios.

About AlchemistConfig

All tests make use of the AlchemistConfig class. This configuration object contains various settings that can be used to customize the behavior of the tests.

A default AlchemistConfig is provided for you, and contains the following settings:

Field Default Description
bool forceUpdateGoldenFiles false If true, the golden files will always be regenerated, regardless of the --update-goldens flag.
ThemeData? theme null The theme to use for all tests. If null, the default ThemeData.light() will be used.
PlatformGoldensConfig platformGoldensConfig const PlatformGoldensConfig() The configuration to use when running readable golden tests on a non-CI host.
CiGoldensConfig ciGoldensConfig const CiGoldensConfig() The configuration to use when running obscured golden tests in a CI environment.

Both the PlatformGoldensConfig and CiGoldensConfig classes contain a number of settings that can be used to customize the behavior of the tests. These are the settings both of these objects allow you to customize:

Field Default Description
bool enabled true Indicates if this type of test should run. If set to false, this type of test is never allowed to run. Defaults to true.
bool obscureText true for CI, false for platform Indicates if the text in the rendered widget should be obscured by colored rectangles. This is useful for circumventing issues with Flutter's font rendering between host platforms.
bool renderShadows false for CI, true for platform Indicates if shadows should actually be rendered, or if they should be replaced by opaque colors. This is useful because shadow rendering can be inconsistent between test runs.
FilePathResolver filePathResolver <_defaultFilePathResolver> A function that resolves the path to the golden file, relative to the test that generates it. By default, CI golden test files are placed in goldens/ci/, and readable golden test files are placed in goldens/.
ThemeData? theme null The theme to use for this type of test. If null, the enclosing AlchemistConfig's theme will be used, or ThemeData.light() if that is also null. Note that CI tests are always run using the Ahem font family, which is a font that solely renders square characters. This is done to ensure that CI tests are always consistent across platforms.

Alongside these arguments, the PlatformGoldensConfig contains an additional setting:

Field Default Description
Set<HostPlatform> platforms All platforms The platforms that platform golden tests should run on. By default, this is set to all platforms, meaning that a golden file will be generated if the current platform matches any platforms in the provided set.
Advanced theming

In addition to the theme property on the AlchemistConfig, CiGoldensConfig and PlatformGoldensConfig classes, Alchemist also supports inherited theming. This means that any theme provided through a custom pumpWidget callback given to goldenTest will be used instead of the theme property on the AlchemistConfig.

The theme resolver works as follows:

  1. If a theme is given to the platform-specific test (using CiGoldensConfig or PlatformGoldensConfig), it is used.
  2. Otherwise, if an inherited theme is provided by the pumpWidget callback (for example, through a MaterialApp), it is used.
  3. Otherwise, if a theme is provided in the AlchemistConfig, it is used.
  4. Otherwise, a default ThemeData.fallback() is used.

Using a custom config

The current AlchemistConfig can be retrieved at any time using AlchemistConfig.current().

A custom can be set by using AlchemistConfig.runWithConfig. Any code executed within this function will cause AlchemistConfig.current() to return the provided config. This is achieved using Dart's zoning system.

void main() {
  print(AlchemistConfig.current().forceUpdateGoldenFiles);
  // > false

  AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      forceUpdateGoldenFiles: true,
    ),
    run: () {
      print(AlchemistConfig.current().forceUpdateGoldenFiles);
      // > true
    },
  );
}
For all tests

A common way to use this mechanic to configure tests for all your tests in a particular package is by using a flutter_test_config.dart file.

Create a flutter_test_config.dart file in the root of your project's test/ directory. This file should have the following contents by default:

import 'dart:async';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  await testMain();
}

This file is executed every time a test file is about to be run. To set a global config, simply wrap the testMain function in a AlchemistConfig.runWithConfig call, like so:

import 'dart:async';

import 'package:alchemist/alchemist.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  return AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      // Configure the config here.
    ),
    run: testMain,
  );
}

Any test executed in the package will now use the provided config.

For single tests or groups

A config can also be set for a single test or test group, which will override the default for those tests. This can be achieved by wrapping that group or test in a AlchemistConfig.runWithConfig call, like so:

void main() {
  group('with default config', () {
    test('test', () {
      expect(
        AlchemistConfig.current().forceUpdateGoldenFiles,
        isFalse,
      );
    });
  });

  AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      forceUpdateGoldenFiles: true,
    ),
    run: () {
      group('with overridden config', () {
        test('test', () {
          expect(
            AlchemistConfig.current().forceUpdateGoldenFiles,
            isTrue,
          );
        });
      });
    },
  );
}
Merging and copying configs

Additionally, settings for a given code block can be partially overridden by using AlchemistConfig.copyWith or, more commonly, AlchemistConfig.merge. The copyWith method will create a copy of the config it is called on, and then override the settings passed in. The merge is slightly more flexible, allowing a second AlchemistConfig (or null) to be passed in, after which a copy will be created of the instance, and all settings defined on the provided config will replace ones on the instance.

Fortunately, the replacement mechanic of merge makes it possible to replace deep/nested values easily, like this:

Click to open AlchemistConfig.merge example
void main() {
  // The top level config is defined here.
  AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      forceUpdateGoldenFiles: true,
      platformGoldensConfig: PlatformGoldensConfig(
        renderShadows: false,
        fileNameResolver: (String name) => 'top_level_config/goldens/$name.png',
      ),
    ),
    run: () {
      final currentConfig = AlchemistConfig.current();

      print(currentConfig.forceUpdateGoldenFiles);
      // > true
      print(currentConfig.platformGoldensConfig.renderShadows);
      // > false
      print(currentConfig.platformGoldensConfig.fileNameResolver('my_widget'));
      // > top_level_config/goldens/my_widget.png

      AlchemistConfig.runWithConfig(
        // Here, the current config (defined above) is merged
        // with a new config, where only the defined options are
        // replaced, preserving the rest.
        config: AlchemistConfig.current().merge(
            AlchemistConfig(
              platformGoldensConfig: PlatformGoldensConfig(
                renderShadows: true,
              ),
            ),
          ),
        ),
        run: () {
          // AlchemistConfig.current() will now return the merged config.
          final currentConfig = AlchemistConfig.current();

          print(currentConfig.forceUpdateGoldenFiles);
          // > true (preserved from the top level config)
          print(currentConfig.platformGoldensConfig.renderShadows);
          // > true (changed by the newly merged config)
          print(currentConfig.platformGoldensConfig.fileNameResolver('my_widget'));
          // > top_level_config/goldens/my_widget.png (preserved from the top level config)
        },
      );
    },
  );
}

Simulating gestures

Some golden tests may require some form of user input to be performed. For example, to make sure a button shows the right color when being pressed, a test may require a tap gesture to be performed while the golden test image is being generated.

These kinds of gestures can be performed by providing the goldenTest function with a whilePerforming argument. This parameter takes a function that will be used to find the widget that should be pressed. There are some default interactions already provided, such as press and longPress.

void main() {
  goldenTest(
    'ElevatedButton renders tap indicator when pressed',
    fileName: 'elevated_button_pressed',
    whilePerforming: press(find.byType(ElevatedButton)),
    builder: () => GoldenTestGroup(
      children: [
        GoldenTestScenario(
          name: 'pressed',
          child: ElevatedButton(
            onPressed: () {},
            child: Text('Pressed'),
          ),
        ),
      ],
    ),
  );
}

Automatic/custom image sizing

By default, Alchemist will automatically find the smallest possible size for the generated golden image and the widgets it contains, and will resize the image accordingly.

The default size and this scaling behavior are configurable, and fully encapsulated in the constraints argument to the goldenTest function.

The constraints are set to const BoxConstraints() by default, meaning no minimum or maximum size will be enforced.

If a minimum width or height is set, the image will be resized to that size as long as it would not clip the widgets it contains. The same is true for a maximum width or height.

If the passed in constraints are tight, meaning the minimum width and height are equal to the maximum width and height, no resizing will be performed and the image will be generated at the exact size specified.

Custom pumping behavior

Before tests

Before running every golden test, the goldenTest function will call its pumpBeforeTest function. This function is used to prime the widget tree prior to generating the golden test image. By default, the tree is pumped and settled (using tester.pumpAndSettle()), but in some scenarios, custom pumping behavior may be required.

In these cases, a different pumpBeforeTest function can be provided to the goldenTest function. A set of predefined functions are included in this package, including pumpOnce, pumpNTimes(n), and onlyPumpAndSettle, but custom functions can be created as well.

Additionally, there is a precacheImages function, which can be passed to pumpBeforeTest in order to preload all images in the tree, so that they will appear in the generated golden files.

Pumping widgets

If desired, a custom pumpWidget function can be provided to any goldenTest call. This will override the default behavior and allow the widget being tested to be wrapped in any number of widgets, and then pumped.

By default, Alchemist will simply pump the widget being tested using tester.pumpWidget. Note that the widget under test will always be wrapped in a set of bootstrapping widgets, regardless of the pumpWidget callback provided.

Custom text scale factor

The GoldenTestScenario.withTextScaleFactor constructor allows a custom text scale factor value to be provided for a single scenario. This can be used to test text rendering at different sizes.

To set a default scale factor for all scenarios within a test, the goldenTest function allows a default textScaleFactor to be provided, which defaults to 1.0.

Resources

alchemist's People

Contributors

abausg avatar brainyoo avatar btrautmann avatar celticmajora avatar definitelyokay avatar defuncart avatar firentistfw avatar giuspepe avatar hawkbee1 avatar hevawu avatar jeroen-meijer avatar kirpal avatar marcossevilla avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

alchemist's Issues

request: Generate goldens files with same structure as test files

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Generate goldens files with same structure as test files

Description

I have a test file with path like test/widgets/helpers/simple_html_test.dart.
I want to generate golden test image in test/goldens/widgets/helpers/simple_html_test.dart.
Is it possible to achieve this without creating separate configuration for every directory or setting filename with path as goldenTest argument?

Reasoning

Many packages that use build_runner allows you to configure this, so
I would like to stay consistent with generated files in my project and don't mix test files with images.

fix: Flutter 3 `ImageCache` warning

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.3.3

Description

Since Flutter 3 update, when running a goldenTest it outputs the following warning:

/opt/hostedtoolcache/flutter/3.0.4-stable/x64/.pub-cache/hosted/pub.dartlang.org/alchemist-0.3.3/lib/src/alchemist_test_variant.dart:39:5: Warning: Operand of null-aware operation '?.' has type 'ImageCache' which excludes null.
 - 'ImageCache' is from 'package:flutter/src/painting/image_cache.dart' ('/opt/hostedtoolcache/flutter/3.0.4-stable/x64/packages/flutter/lib/src/painting/image_cache.dart').
    imageCache?.clear();
    ^

Steps to reproduce

  1. Write a goldenTest
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';

void main() {
  goldenTest(
    'image_cache_warning_test',
    fileName: 'image_cache_warning_test',
    builder: () => GoldenTestScenario(
      name: '',
      child: Container(
        height: 50.0,
        width: 50.0,
        color: Colors.green,
      ),
    ),
  );
}
  1. Run the test
$> flutter test .\test\image_cache_warning_test.dart --update-goldens
00:01 +0: loading D:\Documents\Projects\flutter_dsfr\test\image_cache_warning_test.dart                                                                                                                                                     /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/alchemist-0.3.3/lib/src/alchemist_test_variant.dart:39:5: Warning: Operand of null-aware operation '?.' has type 'ImageCache' which excludes null.
 - 'ImageCache' is from 'package:flutter/src/painting/image_cache.dart' ('/C:/src/flutter/packages/flutter/lib/src/painting/image_cache.dart').
    imageCache?.clear();
    ^
00:02 +2: All tests passed!
$> flutter test .\test\image_cache_warning_test.dart                 
00:01 +0: loading D:\Documents\Projects\flutter_dsfr\test\image_cache_warning_test.dart                                                                                                                                                     /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/alchemist-0.3.3/lib/src/alchemist_test_variant.dart:39:5: Warning: Operand of null-aware operation '?.' has type 'ImageCache' which excludes null.
 - 'ImageCache' is from 'package:flutter/src/painting/image_cache.dart' ('/C:/src/flutter/packages/flutter/lib/src/painting/image_cache.dart').
    imageCache?.clear();
    ^
00:02 +2: All tests passed!

Expected behavior

When running it should not display any warning to keep the log clear in case of a failed test.

Screenshots

No response

Additional context and comments

No response

fix: unable to run multiple GoldenTestDeviceScenario when router is required

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.4.1

Description

When running tests that require MaterialApp.router i am unable to run more than one due to duplicate global keys

โ•โ•โ•ก EXCEPTION CAUGHT BY WIDGETS LIBRARY โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
The following assertion was thrown while finalizing the widget tree:
Multiple widgets used the same GlobalKey.
The key [LabeledGlobalKey<NavigatorState>#e3a60] was used by multiple widgets. The parents of those
widgets were:
- AutoRouteNavigator(state: _AutoRouteNavigatorState#659c4)
- AutoRouteNavigator(state: _AutoRouteNavigatorState#f4c64)
A GlobalKey can only be specified on one widget at a time in the widget tree

Steps to reproduce

  1.  Widget buildWidgetUnderTest() {
       router.push(SearchedSongsRoute(
         searchTerm: 'query',
       ));
    
       return ProviderScope(
         overrides: [
           userNotifierProvider.overrideWithValue(
             fakeUserNotifier,
           ),
           authNotifierProvider.overrideWithValue(
             mockAuthNotifier,
           ),
           searchedSongsNotifierProvider.overrideWithProvider(mockSearchedSongsNotifierProvider),
           searchHistoryNotifierProvider.overrideWithValue(mockSearchHistoryProvider),
         ],
         child: MaterialApp.router(
           routerDelegate: AutoRouterDelegate(
             router,
             navigatorObservers: () => [mockObserver],
             initialDeepLink: DashboardRoute.name,
           ),
           routeInformationParser: AppRouter().defaultRouteParser(),
         ),
       );
     }
    
     goldenTest(
      'renders correctly on mobile',
      fileName: 'SearchedSongsPage',
      builder: () => GoldenTestGroup(
        children: [
          GoldenTestDeviceScenario(
            key: GlobalKey(debugLabel: 'scaffoldKey'),
            device: Device.smallPhone,
            name: 'golden test SearchedSongsPage on small phone',
            builder: buildWidgetUnderTest,
          ),
          GoldenTestDeviceScenario(
            key: GlobalKey(debugLabel: 'scaffoldKeysss'),
            device: Device.tabletLandscape,
            name: 'golden test SearchedSongsPage on tablet landscape',
            builder: buildWidgetUnderTest,
          ),
          // GoldenTestDeviceScenario(
          //   device: Device.tabletPortrait,
          //   name: 'golden test SearchedSongsPage on tablet Portrait',
          //   builder: buildWidgetUnderTest,
          // ),
          // GoldenTestDeviceScenario(
          //   name: 'golden test SearchedSongsPage on iphone11',
          //   builder: buildWidgetUnderTest,
          // ),
        ],
      ),
    );
    

Expected behavior

When running it should update the goldens as it does when running one.

Screenshots

No response

Additional context and comments

The test is located https://github.com/jeremiahlukus/guitar_tabs/blob/jlp-search-bar/test/backend/songs/searched_songs/presentation/searched_song_page_test.dart#L230

using flutter test test/backend/songs/searched_songs/presentation/searched_song_page_test.dart --update-goldens

request: Golden Test on web

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Golden test support for web

Description

While testing for the web I couldnโ€™t test for the web so I wonder if the web will be supported in the future?

Reasoning

Web is an important platform and it has a different features like canvas vs HTML rendering and it allows ci to test on chrome

Additional context and comments

The command that I tried is
flutter test --update-goldens --platform chrome test/image_picker_golden_test.dart and I got the following result
Unsupported operation: Platform._operatingSystem

request: ensure that error messages are always legible

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would like to be able to read the error message from the golden file.

Description

As a developer, I would like to be able to read the error message from a golden file.

Reasoning

Allowing to view the error message from the golden file facilitates debugging and, hence makes the DX much nicer.

For example, the following image should have the error shown in a legible font (not Ahem):
Screenshot 2022-06-06 at 11 20 12

Additional context and comments

No response

fix: Flutter 3.16 make some goldens fail on CI platform

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.7.0

Description

Since recent Flutter upgrade to 3.16 I now have issues after regenerating goldens on my machine, some of them fail on CI due to slight differences.

My process:

  1. I run the tests and update the goldens on my MacBook Pro using default config PlatformGoldensConfig(enabled: true). All my golden files are generated correctly.
  2. If I run a test locally on my MacBook Pro without updating the goldens, everything is fine, tests all succeed.
  3. When I push to my CI (running Ubuntu) and goldens tests are run using config PlatformGoldensConfig(enabled: false), then I get a lot (but not all) golden tests failing with a very slight diff percentage.

Now I don't understand what is happening but what I can reproduce for sure is that all golden tests that contain multiple scenarii and hence multiple lines or columns, FAIL.

Multiple columns goldens

Take this golden file as an example that contains a dumb container with a border:

simple-container-one-column

Code:

  goldenTest(
    'Simple container',
    fileName: 'simple-container-one-column',
    builder: () => GoldenTestGroup(
      columns: 1,
      scenarioConstraints: const BoxConstraints(
        minWidth: 100,
        maxWidth: 100,
        minHeight: 50,
        maxHeight: 50,
      ),
      children: [
        GoldenTestScenario(
          name: '100x50 container with blue border',
          child: _separator(Colors.blue),
        ),
      ],
    ),
  );

โœ… This golden file is correctly generated and asserted during CI tests.

Now take the same test that just duplicates previous scenario:

  goldenTest(
    'Simple container, two columns',
    fileName: 'simple-containers-two-columns',
    builder: () => GoldenTestGroup(
      columns: 2,
      scenarioConstraints: const BoxConstraints(
        minWidth: 100,
        maxWidth: 100,
        minHeight: 50,
        maxHeight: 50,
      ),
      children: [
        GoldenTestScenario(
          name: '100x50 container with blue border',
          child: _separator(Colors.blue),
        ),
        GoldenTestScenario(
          name: '100x50 container with blue border',
          child: _separator(Colors.blue),
        ),
      ],
    ),
  );

โœ…On my machine, everything runs as expected:

simple-containers-two-columns

โŒ But this time on the CI, I have a slight difference showing at t

he left border of my container for the second scenario (second column) as shown in the following golden failure masked & isolated diffs:

simple-containers-two-columns_maskedDiff

simple-containers-two-columns_isolatedDiff

Multiple lines goldens

Same thing happens for goldens tests that contain a single column but multiple scenarii.

Take this example that runs smoothly on my machine :

checkboxes

โŒ Now on the CI, I would get a very subtle one pixel diff at the end of the scenarii separator.

Here is the isolated diff:

checkboxes_isolatedDiff

I've pointed the actual 1 pixel difference with a big red arrow because the diff is very subtle ๐Ÿ˜…

Conclusion

What is happening here ? I would expect the CI test, that explicitly doesn't display fonts, would work well.

There seems to be an issue with how alchemist package separate the different scenarii in multiple lines or columns after upgrading to Flutter 3.16.

Steps to reproduce

  1. Add the following code:
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';

Widget _separator(
  Color color, [
  String? text,
]) =>
    Container(
      decoration: BoxDecoration(
        border: Border.all(color: color, width: 2),
      ),
      height: 25,
      child: text != null
          ? Text(
              text,
              textAlign: TextAlign.center,
            )
          : null,
    );

void main() {
  goldenTest(
    'Simple container',
    fileName: 'simple-container-one-column',
    builder: () => GoldenTestGroup(
      columns: 1,
      scenarioConstraints: const BoxConstraints(
        minWidth: 100,
        maxWidth: 100,
        minHeight: 50,
        maxHeight: 50,
      ),
      children: [
        GoldenTestScenario(
          name: '100x50 container with blue border',
          child: _separator(Colors.blue),
        ),
      ],
    ),
  );

  goldenTest(
    'Simple container, two columns',
    fileName: 'simple-containers-two-columns',
    builder: () => GoldenTestGroup(
      columns: 2,
      scenarioConstraints: const BoxConstraints(
        minWidth: 100,
        maxWidth: 100,
        minHeight: 50,
        maxHeight: 50,
      ),
      children: [
        GoldenTestScenario(
          name: '100x50 container with blue border',
          child: _separator(Colors.blue),
        ),
        GoldenTestScenario(
          name: '100x50 container with blue border',
          child: _separator(Colors.blue),
        ),
      ],
    ),
  );
}
  1. Generate the golden tests (flutter test update-goldens) on a first platform (e.g. a MacBook Pro or Windows machine)
  2. Run the tests on a CI platform (e.g. Ubuntu)
  3. Check for failures

Expected behavior

Golden tests should pass and we shouldn't have diffs that are introduced by alchemist library itself (on its separators).

Screenshots

No response

Additional context and comments

No response

fix: CI tests containing CompositedTransformFollower fail

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.2.1

Description

An error is thrown when a CI test is run with a widget which contains a CompositedTransformFollower.

Exception and stack trace
โ•โ•โ•ก EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
The following _CastError was thrown while running async test code:
Null check operator used on a null value

When the exception was thrown, this was the stack:
#0      FollowerLayer.addToScene (package:flutter[/src/rendering/layer.dart:2554:22]())
#1      Layer._addToSceneWithRetainedRendering (package:flutter[/src/rendering/layer.dart:545:5]())
#2      ContainerLayer.addChildrenToScene (package:flutter[/src/rendering/layer.dart:1105:13]())
#3      PhysicalModelLayer.addToScene (package:flutter[/src/rendering/layer.dart:2085:5]())
#4      Layer._addToSceneWithRetainedRendering (package:flutter[/src/rendering/layer.dart:545:5]())
#5      ContainerLayer.addChildrenToScene (package:flutter[/src/rendering/layer.dart:1105:13]())
#6      OpacityLayer.addToScene (package:flutter[/src/rendering/layer.dart:1799:5]())
#7      Layer._addToSceneWithRetainedRendering (package:flutter[/src/rendering/layer.dart:545:5]())
#8      ContainerLayer.addChildrenToScene (package:flutter[/src/rendering/layer.dart:1105:13]())
#9      OffsetLayer.addToScene (package:flutter[/src/rendering/layer.dart:1239:5]())
#10     ContainerLayer.buildScene (package:flutter[/src/rendering/layer.dart:937:5]())
#11     OffsetLayer.toImage (package:flutter[/src/rendering/layer.dart:1276:28]())
#12     BlockedTextImageWidgetTesterExtensions.getBlockedTextImage (package:alchemist[/src/blocked_text_image.dart:33:18]())
#13     _generateAndCompare (package:alchemist[/src/golden_test.dart:338:48]())
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
Test failed. See exception logs above.

Steps to reproduce

  1. Use the sample main.dart with a widget MyApp containing a CompositedTransformFollower (used to display an introduction, example gif is given in the main.dart section)
  2. Run the given broken_golden_test.dart
  3. Exception given above will be thrown
main.dart

Example gif showing the use case of CompositedTransformFollower to show an introduction overlay:
sample_gif

main.dart:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  final bool initialShowIntroductionEnabled;
  final int initialIntroIndex;

  const MyApp({
    this.initialIntroIndex = 0,
    this.initialShowIntroductionEnabled = true,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  MyHomePage(
        initialIntroIndex: initialIntroIndex,
        initialShowIntroductionEnabled: initialShowIntroductionEnabled,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final bool initialShowIntroductionEnabled;
  final int initialIntroIndex;

  const MyHomePage({
     this.initialIntroIndex = 0,
     this.initialShowIntroductionEnabled = false,
    Key? key,
  }) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late bool showIntroduction;
  late int introIndex;

  @override
  void initState() {
    showIntroduction = widget.initialShowIntroductionEnabled;
    introIndex = widget.initialIntroIndex;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final myHomeBody = MyHomeBody();

    void showNextStepOverlay() {
      setState(() {
        if (++introIndex >= myHomeBody.layerLinks.length) {
          introIndex = 0;
        }
      });
    }

    return Scaffold(
      appBar: AppBar(title: const Text('Demo')),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.help),
        onPressed: () => setState(() => showIntroduction = !showIntroduction),
      ),
      body: Stack(children: [
        myHomeBody,
        if (showIntroduction) ...[
          MyIntroductionOverlay(layerLink: myHomeBody.layerLinks[introIndex]),
          Positioned(
            left: 0,
            right: 0,
            bottom: 0,
            child: SafeArea(
              child: Center(
                  child: ElevatedButton(
                child: const Text('Next step'),
                onPressed: showNextStepOverlay,
              )),
            ),
          )
        ]
      ]),
    );
  }
}

class MyHomeBody extends StatelessWidget {
  MyHomeBody({Key? key}) : super(key: key);

  final layerLinkFlutterLogo = LayerLink();
  final layerLinkText = LayerLink();
  final layerLinkButton = LayerLink();
  final layerLinkIcon = LayerLink();

  List<LayerLink> get layerLinks => [
        layerLinkFlutterLogo,
        layerLinkText,
        layerLinkButton,
        layerLinkIcon,
      ];

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          CompositedTransformTarget(
            link: layerLinkFlutterLogo,
            child: const FlutterLogo(),
          ),
          CompositedTransformTarget(
            link: layerLinkText,
            child: const Text('hello world'),
          ),
          CompositedTransformTarget(
            link: layerLinkButton,
            child: const ElevatedButton(
              onPressed: null,
              child: Text('this is a button'),
            ),
          ),
          CompositedTransformTarget(
            link: layerLinkIcon,
            child: const Icon(Icons.help),
          )
        ],
      ),
    );
  }
}

class MyIntroductionOverlay extends StatelessWidget {
  final LayerLink layerLink;

  const MyIntroductionOverlay({required this.layerLink, Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CompositedTransformFollower(
      targetAnchor: Alignment.topCenter,
      followerAnchor: Alignment.topCenter,
      link: layerLink,
      child: Container(
        height: 65,
        color: Colors.red.withOpacity(0.5),
        alignment: Alignment.bottomCenter,
        child: Text("overlay with helpful explanation"),
      ),
    );
  }
}
broken_golden_test.dart
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:scratches/main.dart';

void main() {
 // This test is broken when both enableCiTests and enableIntroduction are true
 
  const enableCiTests = true;

  // when true, MyApp contains a CompositedTransformFollower
  const enableIntroduction = true;

  const phoneSize = Size(400, 900);

  AlchemistConfig.runWithConfig(
    config: const AlchemistConfig(
      forceUpdateGoldenFiles: true,
      ciGoldensConfig: CiGoldensConfig(enabled: enableCiTests),
      platformGoldensConfig: PlatformGoldensConfig(enabled: true),
    ),
    run: () => goldenTest(
      'demo',
      fileName: 'demo',
      widget: GoldenTestGroup(
        children: [
          GoldenTestScenario(
            name: 'introduction',
            child: ConstrainedBox(
              constraints: BoxConstraints.tight(phoneSize),
              child: const MyApp(
                initialShowIntroductionEnabled: enableIntroduction,
              ),
            ),
          ),
        ],
      ),
    ),
  );
}

Expected behavior

No exception should be thrown and the test should pass.

The test does work for platform tests. The problem only occurs for CI tests because only in that case BlockedTextImageWidgetTesterExtensions.getBlockedTextImage is called which throws when a widget contains a CompositedTransformTarget

Screenshots

No response

Additional context and comments

No response

request: add a Flutter application example

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Add a Flutter application example.

Description

Instead of a plain markdown file (https://github.com/Betterment/alchemist/blob/main/example/example.md), it would be great to have a real Flutter example application.

It would be ideal if this example showcased the functionality Alchemist provides. For example, showing a login page with goldens for different languages with a custom font.

Reasoning

  • Makes it easier for other developers to pick up the package.
  • Allows catching issues from the example (eg #55).
  • Easily allow testing changes (while developing) against a real codebase

Additional context and comments

No response

Flutter 3.16 is breaking goldens background color

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.7.0

Description

After upgrading from Flutter 3.13.8 to 3.16.0 my golden test is breaking because of the background color.

Steps to reproduce

Minimal reproducible example

main.dart

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: false),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('Hello World!'),
      ),
    );
  }
}

home_golden_test.dart

const iphoneSE = Size(320, 568);

void main() {
  goldenTest(
    'HomePage',
    fileName: 'home_page',
    builder: () {
      return GoldenTestGroup(children: [
        GoldenTestScenario(
          constraints: BoxConstraints.tight(iphoneSE),
          name: 'default',
          child: Theme(
            data: ThemeData(useMaterial3: false),
            child: const HomePage(),
          ),
        ),
      ]);
    },
  );
}

Steps to reproduce

  1. Run flutter test --update-goldens with Flutter 3.13.8
  2. Switch to Flutter 3.16.0
  3. Run flutter test -t golden

Expected behavior

The test should not fail.

Screenshots

3.13.8 3.16.0
home_page home_page_2

Additional context and comments

I must admit that I never understood why the background color was based off the theme data considering that it could break after any bump of the SDK like here ๐Ÿค”

fix: providing empty button styles causes unreadable goldens

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.3.3

Description

Reproducible example gist can be found here.

In platform tests (and I suspect CI tests as well), if a Text is built but no default text style font family is found, it looks like "Ahem" is used as a fallback value, instead of "Roboto".

Steps to reproduce

  1. Run a golden test and provide it a Theme with a textButtonTheme. Assign the style of this theme to TextButton.styleFrom. Provide this constructor an empty TextStyle.
  2. Set this as the default theme in the AlchemistConfig or PlatformGoldensConfig.
  3. Run the test.

Expected behavior

Default to "Roboto" when no font family is found.

Screenshots

image

Additional context and comments

No response

fix: Warning: Operand of null-aware operation '?.' has type 'ImageCache' which excludes null.

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.3.3

Description

Whenever running test I get a warning in the console

../.pub-cache/hosted/pub.dartlang.org/alchemist-0.3.3/lib/src/alchemist_test_variant.dart:39:5: Warning: Operand of null-aware operation '?.' has type 'ImageCache' which excludes null.
 - 'ImageCache' is from 'package:flutter/src/painting/image_cache.dart' ('../fvm/versions/3.0.1/packages/flutter/lib/src/painting/image_cache.dart').
    imageCache?.clear();
    ^

Tests still work correctly but warnings fill terminal.

Steps to reproduce

Use Alchemist 0.3.3 with Flutter > 3 and run tests.

flutter_test_config.dart for reference:

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  await loadAppFonts();

  return AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      platformGoldensConfig: PlatformGoldensConfig(platforms: <HostPlatform>{
        HostPlatform.macOS,
        HostPlatform.windows,
      }),
    ),
    run: testMain,
  );
}

Expected behavior

When running it shouldn't warn about ImageCache issues.

Screenshots

No response

Additional context and comments

No response

fix: Svg pictures aren't rendered in CI mode

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.4.1

Description

Svg pictures aren't rendered correctly in CI mode. They work fine in macOS, but once you fit them with widgets with size below them they appear weirdly.

I'm using flutter_svg: 1.1.4

Steps to reproduce

  1. Create a golden test with this widget:
Scaffold(
  body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        SvgPicture.asset(
          R.ILLUSTRATIONS_GENERIC_ERROR,
          fit: BoxFit.fitWidth,
        ),
        const SizedBox(height: 84),
      ],
  ),
);
  1. Run flutter test --update-goldens - when you open the generated image that's the effect:
    comments_page_error

I've also attached the svg picture I'm generating the svg with.
generic_error

Expected behavior

The generated golden should be identical to macOS one.
comments_page_error

Screenshots

No response

Additional context and comments

I'm using flutter 3.0.5

request: Disable scenario name in CI variant

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Ability to disable text widget for csenario name in CI env

Description

Config setting which when enabled removes Text widget with scenario.name parameter from CI image

Reasoning

Since in CI font is obscured for compativility, names doesn't make sense and just clutter image. Also, ahem font takes much more space and something like 30-35 long name is making CI image twice as wide
image
(this is 35 symbols including whitespaces)

Additional context and comments

No response

request: Create MacOS Folder for Intel and Apple Silicon

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Differ between Intel Macs & Apple Silicon Macs

Description

Having two MacOS folders, one for Intel and one for ARM.
File Structure could be like this:

.
โ””โ”€โ”€ macos/
    โ”œโ”€โ”€ intel
    โ””โ”€โ”€ arm

or

.
โ”œโ”€โ”€ macos-intel
โ””โ”€โ”€ macos-arm

Reasoning

If you run Golden Tests with an Intel Mac, it will produce different Golden Images than an Apple Silicon Mac and vice versa.
To be able to test on both platforms reliable, it would be great to have two folders. One for each architecture.

Additional context and comments

No response

Question regarding target platforms

Say I have a component that is re-used across mobile and desktop e.g. a button.

Hypothetically on desktop the button should be 32x32 and on mobile it should be 40x40, among other differences.

Assuming I make the distinction between platforms based on the foundation.dart property: defaultTargetPlatform in the button -- is there a way for me to run my golden tests/scenarios against both platforms?

I've looked into AlchemistConfig but can find any way to specific these type of variation.

I also tried to hack it manually but it didn't work (I have the same golden images for both):

import 'package:alchemist/alchemist.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('Golden Tests', () async {
    Future<void> runTests(String variant) async {
      await goldenTest(
        'should render correctly',
        fileName: 'component_$variant',
        constraints: const BoxConstraints(maxWidth: 800),
        builder: () {
          return GoldenTestGroup(
            columnWidthBuilder: (_) => const FlexColumnWidth(),
            children: [
              GoldenTestScenario(
                name: 'scenario 1',
                child: ...,
              ),
              GoldenTestScenario(
                name: 'scenario w',
                child: ...,
              ),
            ],
          );
        },
      );
    }

    // Desktop
    debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
    await runTests('desktop');
    debugDefaultTargetPlatformOverride = null;

    // Mobile
    debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
    await runTests('mobile');
    debugDefaultTargetPlatformOverride = null;
  });
}

Any advice on how to test goldens agains multiple target platforms in a nice way?

Or any idea why even my hack solution does not work?

Thanks in advance,
Cillian.

fix: the "loadFonts" function calling on every test causes longer test execution and performance issues

Is there an existing issue for this?

  • I have searched the existing issues.

Version

v0.7.0

Description

Hi ๐Ÿ‘‹

After using the โ€œalchemistโ€ library, I noticed that our test execution on CI takes more time, and sometimes it hangs on the tearDownAll function.

My use case of execution tests is a bit different. I run all the tests in one isolate, mainly to speed things up on CI. The testMain during test execution is called only once, along with the testExecutable at flutter_test_config.

Letโ€™s say I have 100 golden tests. For each of them, the loadFonts function is called before the test, which takes time to execute. It is called 100 times. But should only once.

I would like to call this function only once before my tests execution, at flutter_test_config.

I forked the library, removed the loadFonts function call on every test execution, and moved it to the flutter_test_config. The problem was resolved.

Steps to reproduce

  1. Create a test file with ten goldens tests.
  2. Execute the tests.

Actual behavior: The loadFonts function is called ten times.

Expected behavior

The loadFonts function is called only once.

Screenshots

No response

Additional context and comments

Thanks for the library ๐Ÿš€

fix: Tolerance is incompatible with --machine

Is there an existing issue for this?

  • I have searched the existing issues.

Version

/dev/feat/tolerance

Description

When I run flutter test --machine it throws an error. It works great on stable version.

Stacktrace
../AppData/Local/Pub/Cache/git/alchemist-d57c359ceadeae7ebde51639bcf9fbf37bb5f100/lib/src/golden_test_adapter.dart:53:38: Error: A value of type 'void Function(String, Future<void> Function(WidgetTester), {LeakTesting?
experimentalLeakTesting, int? retry, bool semanticsEnabled, bool? skip, dynamic tags, Timeout? timeout, TestVariant<Object?> variant})' can't be assigned to a variable of type 'FutureOr<void> Function(String, Future<void>
Function(WidgetTester), {Duration? initialTimeout, bool semanticsEnabled, bool? skip, dynamic tags, Timeout? timeout, TestVariant<Object?> variant})'.
 - 'Future' is from 'dart:async'.
 - 'WidgetTester' is from 'package:flutter_test/src/widget_tester.dart' ('.fvm/flutter_sdk/packages/flutter_test/lib/src/widget_tester.dart').
 - 'LeakTesting' is from 'package:leak_tracker_testing/src/leak_testing.dart' ('../AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-2.0.1/lib/src/leak_testing.dart').
 - 'Timeout' is from 'package:test_api/src/backend/configuration/timeout.dart' ('../AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.6.1/lib/src/backend/configuration/timeout.dart').
 - 'TestVariant' is from 'package:flutter_test/src/widget_tester.dart' ('.fvm/flutter_sdk/packages/flutter_test/lib/src/widget_tester.dart').
 - 'Object' is from 'dart:core'.
 - 'Duration' is from 'dart:core'.
TestWidgetsFn defaultTestWidgetsFn = testWidgets;
                                     ^

fix: missing implementation of BlockedTextCanvasAdapter.restoreToCount

Is there an existing issue for this?

  • I have searched the existing issues.

Version

3.4.0-34.1.pre

Description

After upgrade flutter to beta (3.4.0-34.1.pre) I recive

 Error: The non-abstract class 'BlockedTextCanvasAdapter' is missing implementations for these members:
 - Canvas.restoreToCount
Try to either
 - provide an implementation,
 - inherit an implementation from a superclass or mixin,
 - mark the class as abstract, or
 - provide a 'noSuchMethod' implementation.

on

class BlockedTextCanvasAdapter implements Canvas {

Steps to reproduce

  1. Setup new project
  2. Add alchemist
  3. Prepare simple test
  4. Run test

Expected behavior

Adding to the BlockedTextCanvasAdapter
method void restoreToCount(int count) => parent.restoreToCount(count);

Screenshots

CleanShot 2022-11-14 at 14 16 34@2x

Additional context and comments

No response

request: check whether text and icons are consistent in CI tests

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would love if CI tests would check texts and icons.

Description

It would be great if changing icons or texts would be caught by golden tests. I understand that currently text is not rendered as text to make CI tests stable between platforms, but maybe there is another solution to get the best of both worlds.

Take the following test file as an example. Note that both tests use the same file as golden image reference. The second golden test should fail because it contains a different icon than the first test, but the CI test passes because the icon (which is text as well) is rendered as a black box on both tests.

void main() {
  AlchemistConfig.runWithConfig(
    config: AlchemistConfig.current().merge(
      const AlchemistConfig(
        ciGoldensConfig: CiGoldensConfig(enabled: true),
        platformGoldensConfig: PlatformGoldensConfig(enabled: false),
      ),
    ),
    run: () {
      goldenTest(
        'should pass and passes',
        fileName: 'icon_test',
        builder: () => GoldenTestScenario(
          name: 'icon A',
          child: const Icon(Icons.thumb_up),
        ),
      );

      goldenTest(
        'should fail, but passes too!',
        // use same golden image as the previous test
        fileName: 'icon_test',
        builder: () => GoldenTestScenario(
          name: 'icon B',
          // but use a different icon
          child: const Icon(Icons.bug_report),
        ),
      );
    },
  );
}

Reasoning

This feature would make golden tests even more powerful by preventing more types of regressions.

Additional context and comments

My suggestion is the following:

During the replacement of text blocks with colored rectangles, write all blocked text blocks into a Map<Offset, List<String>> textBlocks. At the end of a test, compare not only the golden image file but also the map containing the text blocks.

Add something like textBlocks.putIfAbsent(offset, () => []).add(child.text.toPlainText()); to the if(child is RenderParagraph) block. Or maybe child.text.toStringDeep() would be better because that also contains information on font family, color, etc.

void paintChild(RenderObject child, Offset offset) {
if (child is RenderParagraph) {
final paint = Paint()
..color = child.text.style?.color ?? const Color(0xFF000000);
canvas.drawRect(offset & child.size, paint);
} else {

If a test is being run with --update-goldens or forceUpdateGoldens: true, save the Map<Offset, List> under goldens/ci/<golden_test_file_name>.texts. Else compare the current textBlocks map with the persisted golden textBlocks map.

The map should be a Map<Offset, List<String>> and not a Map<Offset, String> because multiple texts can be drawn at the same offset. Another advantage of List is that it also keeps track of the correct painting order.

request: Allow Mocking of Image.network

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would like to mock Image.network to support that in golden_tests

Description

I want to test widgets that use Image.network in golden tests. Right now if there is a widget trying to use Image.network the following exception is thrown:

When the exception was thrown, this was the stack:
#0      NetworkImage._loadAsync (package:flutter/src/painting/_network_image_io.dart:98:9)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

Image provider: NetworkImage("", scale: 1.0)
Image key: NetworkImage("", scale: 1.0)

Reasoning

Testing widgets with Network Images should be vital

Additional context and comments

I tried to wrap the goldenTest with await mockNetworkImages(() async { from https://github.com/felangel/mocktail/tree/main/packages/mocktail_image_network but that did not have the desired effect

request: mock network images

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would love to be able to mock network images in my tests

Description

As a developer, I would love to be able to mock network images in my tests but it seems like it is not yet possible with Alchemist or is there any way to do so ?

Reasoning

I tried to make pass a golden test with a NetworkImage widget used in it but I have an infinite loading test.

Additional context and comments

I found a similar issue but I did not find any response in it.

Does the library supports today network image mocking ?

Thanks a lot for this amazing package :)

request: update how Theme is propagated

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Update how Theme is propagated, especially when using a custom pumpWidget.

Description

I think theming in the golden test should be handled as follows t:

  1. Look for an inherited widget to see if there is an ancestor theme (if there is use it),
  2. If no ancestor theme is found, use the one provided via the configuration
  3. If no configuration is provided, don't inject any theme inherited widget

Reasoning

Achieve a better API that would make the library more Flutter friendly. It makes it easier to migrate from regular golden testing to Alchemist, since on regular golden test theming is handled by the Widget tree and is not overridden as in the case of Alchemist.

Additional context and comments

No response

fix: Huge memory consumption

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.4.1

Description

Our app have many, many golden test like following. The issue is about memory consumption, we are today at 40go every run. Do you know what in golden test took so much memory ? Any general good practice ?

 goldenTest(
      'renders texts correctly',
      fileName:
          'theme-texts-${flavor.runtimeType.toString().toLowerCase()}-${mode.name}',
      builder: () => GoldenTestGroup(
        columns: 1,
        children: <GoldenTestScenario>[
          GoldenTestScenario(
            name: 'body text 1',
            child: Text(
              'Sample text',
              style: textTheme.bodyText1,
            ),
          ),
          GoldenTestScenario(
            name: 'body text 2',
            child: Text(
              'Sample text',
              style: textTheme.bodyText2,
            ),
          ),
          GoldenTestScenario(
            name: 'headline 1',
            child: Text(
              'Sample text',
              style: textTheme.headline1,
            ),
          ),
          GoldenTestScenario(
            name: 'headline 2',
            child: Text(
              'Sample text',
              style: textTheme.headline2,
            ),
          ),
          GoldenTestScenario(
            name: 'headline 3',
            child: Text(
              'Sample text',
              style: textTheme.headline3,
            ),
          ),
          GoldenTestScenario(
            name: 'headline 4',
            child: Text(
              'Sample text',
              style: textTheme.headline4,
            ),
          ),
          GoldenTestScenario(
            name: 'headline 5',
            child: Text(
              'Sample text',
              style: textTheme.headline5,
            ),
          ),
          GoldenTestScenario(
            name: 'headline 6',
            child: Text(
              'Sample text',
              style: textTheme.headline6,
            ),
          ),
          GoldenTestScenario(
            name: 'subtitle 1',
            child: Text(
              'Sample text',
              style: textTheme.subtitle1,
            ),
          ),
          GoldenTestScenario(
            name: 'subtitle 2',
            child: Text(
              'Sample text',
              style: textTheme.subtitle2,
            ),
          ),
        ],
      ),
    );

Capture dโ€™eฬcran 2022-08-31 aฬ€ 15 54 02

Steps to reproduce

run many golden test

Expected behavior

As less memory as possible

Screenshots

No response

Additional context and comments

No response

fix: Flutter 3.13.1 - Custom RenderObjects are not visible anymore on golden images

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.6.1

Description

When updating to the newest flutter version 3.13.1, the golden tests are failing when widgets with custom RenderObjects are tested.
Previously the text and icons were visible in the golden images. Now nothing is displayed (see the screenshots).

Steps to reproduce

import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';

void main() {
// ignore: discarded_futures,
  goldenTest(
    'golden test',
    fileName: 'h4u_adaptive_text',
    builder: () => const SizedBox(
      height: 800,
      width: 400,
      child: CustomExampleRenderObject(),
    ),
  );
}

class CustomExampleRenderObject extends LeafRenderObjectWidget {
  const CustomExampleRenderObject({super.key});

  @override
  CustomExampleRenderBox createRenderObject(BuildContext context) {
    return CustomExampleRenderBox();
  }
}

class CustomExampleRenderBox extends RenderBox {
  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;
    canvas.save();
    canvas.translate(offset.dx, offset.dy);

    final textPainter = TextPainter(
      text: const TextSpan(
        text: 'text',
        style: TextStyle(),
      ),
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(canvas, Offset.zero);

    canvas.restore();
  }

  @override
  void performLayout() {
    this.size = this.computeDryLayout(this.constraints);
  }

  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.constrain(Size(
      200.0,
      constraints.maxHeight,
    ));
  }
}

Expected behavior

In the example, I would expect that my text is still shown in the images and in general I would expect that it still works with Custom RenderObjects.

Screenshots

CI Image with Flutter Version 3.10.6:
Bildschirmfoto 2023-08-30 um 07 42 26

CI Image with Flutter Version 3.13.1:
Bildschirmfoto 2023-08-30 um 07 30 30

Additional context and comments

No response

fix: runZoned / withClock not working

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.5.1

Description

tl;dr What is the proper way to run a goldenTest in a Zone?

I want to mock DateTime.now() and for this I use the offical "clock" package.

Steps to reproduce

withClock uses runZoned under the hood and can return the callback/ result so I thought it should be possible to put it in somewhere in "goldenTest" - I tried:

builder: () => withClock<Widget>(
        Clock.fixed(DateTime(2022, 11, 17)),
        () => MyFancyWidget)
pumpWidget: (tester, widget) async => withClock(
        Clock.fixed(DateTime(2022, 11, 17)),
        () async {
          await tester.pumpWidget(widget);
        },
      ),

both did not work and the current dateTime was used instead, suggesting that for some reason the Zone was not used.

Expected behavior

I would expect both of the above examples to be valid.

This is an example of how withClock is used in a basic unit test:

test('returns a proper greeting on a Tuesday', () {
    final greeting = withClock(
      Clock.fixed(DateTime(2020, 09, 01)),
      () => Greeter().greet(),
    );

    expect(greeting, 'Happy Tuesday, you!');
# example from: https://iiro.dev/controlling-time-with-package-clock/    

Screenshots

No response

Additional context and comments

So this all turns out to be a question of "What is the proper way to run a goldenTest in a Zone?"

fix: platform test are not displaying text.

On my locals, when running the example test on fresh install (flutter create or very_good create), the linux and macos goldens don't display text.
image
The CI font is not used because the result is different if you look at the icon and the spaces.
image

the result should be:
image

I tried different PlatformGoldensConfig with no success.
Adding Roboto fonts from alchemist package into the project is solving the issue.

flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[โœ“] Flutter (Channel stable, 2.10.4, on Ubuntu 21.10 5.13.0-37-generic, locale en_US.UTF-8)
[โœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[โœ“] Chrome - develop for the web
[โœ“] Android Studio (version 2020.3)
[โœ“] VS Code
[โœ“] Connected device (2 available)
[โœ“] HTTP Host Availability

fix: 'BlockedTextCanvasAdapter' is missing implementations for these members

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.4.1

Description

class BlockedTextCanvasAdapter implements Canvas, but I guess in Flutter 3.3 Canvas got some new members. I'm getting the following error:

../../../../../.pub-cache/hosted/pub.dartlang.org/alchemist-0.4.1/lib/src/blocked_text_image.dart:65:7: Error: The non-abstract class 'BlockedTextCanvasAdapter' is missing implementations for these members:
 - Canvas.getDestinationClipBounds

- Canvas.getLocalClipBounds
 - Canvas.getTransform
Try to either
 - provide an implementation,
 - inherit an implementation from a superclass or mixin,
 - mark the class as abstract, or
 - provide a 'noSuchMethod' implementation.

class BlockedTextCanvasAdapter implements Canvas {
      ^^^^^^^^^^^^^^^^^^^^^^^^
org-dartlang-sdk:///flutter/lib/ui/painting.dart:4588:8: Context: 'Canvas.getDestinationClipBounds' is defined here.
  Rect getDestinationClipBounds() {
       ^^^^^^^^^^^^^^^^^^^^^^^^
org-dartlang-sdk:///flutter/lib/ui/painting.dart:4567:8: Context: 'Canvas.getLocalClipBounds' is defined here.
  Rect getLocalClipBounds() {
       ^^^^^^^^^^^^^^^^^^
org-dartlang-sdk:///flutter/lib/ui/painting.dart:4457:15: Context: 'Canvas.getTransform' is defined here.
  Float64List getTransform() {
              ^^^^^^^^^^^^

Steps to reproduce

Just try to run golden tests in Flutter 3.3

Expected behavior

Tests run

Screenshots

No response

Additional context and comments

No response

request: Add diff tolerance

Is there an existing feature request for this?

  • I have searched the existing issues.

Description

Since for the golden tests, you can add a comparator and make some tolerance: flutter/flutter#77014
This option should also be available for Alchemist, ideally in AlchemistConfig.

Reasoning

I wanted to do this as a workaround because our golden test are failing with 0.00% diff (there can be like 1 pixel of difference) since Flutter 3.7.0.

I don't know what's the reason for this failure, I'm not sure it's related to Alchemist so I didn't want to open a bug here.

I don't see how else I can fix this since on my local machine all tests work fine.

Additional context and comments

No response

fix: Build errors during screenshot-table of tested widgets creation

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.6.1

Description

During the creation of golden tests for our existing app, I've encountered following error executing said tests:

โ•โ•โ•ก EXCEPTION CAUGHT BY RENDERING LIBRARY โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
The following assertion was thrown during performLayout():
LayoutBuilder does not support returning intrinsic dimensions.
Calculating the intrinsic dimensions would require running the layout callback speculatively, which
might mutate the live render object tree.

The relevant error-causing widget was:
  Table
  Table:~/Pub/Cache/hosted/pub.dev/alchemist-0.6.1/lib/src/golden_test_group.dart:117:14

When the exception was thrown, this was the stack:
#0      _RenderLayoutBuilder._debugThrowIfNotCheckingIntrinsics.<anonymous closure> (package:flutter/src/widgets/layout_builder.dart:345:9)
#1      _RenderLayoutBuilder._debugThrowIfNotCheckingIntrinsics (package:flutter/src/widgets/layout_builder.dart:352:6)
#2      _RenderLayoutBuilder.computeMaxIntrinsicHeight (package:flutter/src/widgets/layout_builder.dart:297:12)
#3      RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#4      _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#5      RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#6      RenderBox.getMaxIntrinsicHeight (package:flutter/src/rendering/box.dart:1746:12)
#7      RenderFlex.computeMaxIntrinsicHeight.<anonymous closure> (package:flutter/src/rendering/flex.dart:615:60)
#8      RenderFlex._getIntrinsicSize (package:flutter/src/rendering/flex.dart:573:58)
#9      RenderFlex.computeMaxIntrinsicHeight (package:flutter/src/rendering/flex.dart:612:12)
#10     RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#11     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#12     RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#13     RenderBox.getMaxIntrinsicHeight (package:flutter/src/rendering/box.dart:1746:12)
#14     RenderFlex._getIntrinsicSize (package:flutter/src/rendering/flex.dart:554:32)
#15     RenderFlex.computeMaxIntrinsicWidth (package:flutter/src/rendering/flex.dart:594:12)
#16     RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#17     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#18     RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#19     RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1595:12)
#20     RenderProxyBoxMixin.computeMaxIntrinsicWidth (package:flutter/src/rendering/proxy_box.dart:82:21)
#21     RenderConstrainedBox.computeMaxIntrinsicWidth (package:flutter/src/rendering/proxy_box.dart:259:32)
#22     RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#23     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#24     RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#25     RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1595:12)
#26     RenderProxyBoxMixin.computeMaxIntrinsicWidth (package:flutter/src/rendering/proxy_box.dart:82:21)
#27     RenderConstrainedBox.computeMaxIntrinsicWidth (package:flutter/src/rendering/proxy_box.dart:259:32)
#28     RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#29     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#30     RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#31     RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1595:12)
#32     RenderFlex.computeMaxIntrinsicWidth.<anonymous closure> (package:flutter/src/rendering/flex.dart:597:60)
#33     RenderFlex._getIntrinsicSize (package:flutter/src/rendering/flex.dart:555:36)
#34     RenderFlex.computeMaxIntrinsicWidth (package:flutter/src/rendering/flex.dart:594:12)
#35     RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#36     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#37     RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#38     RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1595:12)
#39     RenderPadding.computeMaxIntrinsicWidth (package:flutter/src/rendering/shifted_box.dart:178:21)
#40     RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1409:23)
#41     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#42     RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1407:57)
#43     RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1595:12)
#44     IntrinsicColumnWidth.maxIntrinsicWidth (package:flutter/src/rendering/table.dart:116:38)
#45     RenderTable._computeColumnWidths (package:flutter/src/rendering/table.dart:877:52)
#46     RenderTable.performLayout (package:flutter/src/rendering/table.dart:1085:33)
#47     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#48     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#49     RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:238:12)
#50     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#51     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#52     RenderPositionedBox.performLayout (package:flutter/src/rendering/shifted_box.dart:438:14)
#53     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#54     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#55     RenderConstrainedOverflowBox.performLayout (package:flutter/src/rendering/shifted_box.dart:631:14)
#56     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#57     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#58     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#59     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#60     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#61     RenderPositionedBox.performLayout (package:flutter/src/rendering/shifted_box.dart:438:14)
#62     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#63     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#64     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#65     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#66     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#67     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#68     RenderCustomPaint.performLayout (package:flutter/src/rendering/custom_paint.dart:554:11)
#69     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#70     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#71     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#72     _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1449:11)
#73     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#74     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#75     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#76     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#77     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#78     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#79     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#80     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#81     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#82     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#83     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#84     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#85     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#86     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#87     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#88     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#89     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#90     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#91     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#92     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#93     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#94     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#95     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#96     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#97     RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#98     RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#99     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#100    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#101    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#102    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#103    RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:3751:13)
#104    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#105    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#106    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#107    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#108    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#109    _RenderTheaterMixin.performLayout (package:flutter/src/widgets/overlay.dart:832:15)
#110    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#111    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#112    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#113    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#114    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#115    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#116    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#117    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#118    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:122:14)
#119    RenderObject.layout (package:flutter/src/rendering/object.dart:2395:7)
#120    RenderBox.layout (package:flutter/src/rendering/box.dart:2386:11)
#121    RenderView.performLayout (package:flutter/src/rendering/view.dart:173:14)
#122    RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2234:7)
#123    PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1016:18)
#124    AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1388:23)
#125    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:358:5)
#126    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1284:15)
#127    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1214:9)
#128    AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:1236:9)
#131    TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:68:41)
#132    AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1222:27)
#133    WidgetTester._pumpWidget (package:flutter_test/src/widget_tester.dart:592:20)
#134    WidgetTester.pumpWidget.<anonymous closure> (package:flutter_test/src/widget_tester.dart:577:14)
#137    TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:68:41)
#138    WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:576:27)
#139    onlyPumpWidget (package:alchemist/src/pumps.dart:74:17)
#140    FlutterGoldenTestAdapter.pumpGoldenTest (package:alchemist/src/golden_test_adapter.dart:238:21)
#141    FlutterGoldenTestRunner.run (package:alchemist/src/golden_test_runner.dart:78:31)
#142    goldenTest.<anonymous closure> (package:alchemist/src/golden_test.dart:169:30)
<asynchronous suspension>
<asynchronous suspension>
(elided 5 frames from dart:async and package:stack_trace)

The following RenderObject was being processed when the exception was fired: RenderTable#1ea5e relayoutBoundary=up3 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:
  creator: Table โ† GoldenTestScenarioConstraints โ† GoldenTestGroup โ† Padding-[#04929] โ† Center โ†
    OverflowBox โ† ColoredBox โ† Builder โ† Align โ† DefaultTextStyle โ† AnimatedDefaultTextStyle โ†
    _InkFeatures-[GlobalKey#ae861 ink renderer] โ† โ‹ฏ
  parentData: offset=Offset(8.0, 8.0) (can use size)
  constraints: BoxConstraints(unconstrained)
  size: MISSING
  border: TableBorder(BorderSide(width: 0.0, style: none), BorderSide(width: 0.0, style: none),
    BorderSide(width: 0.0, style: none), BorderSide(width: 0.0, style: none), BorderSide(color:
    Color(0x4d000000)), BorderSide(color: Color(0x4d000000)), BorderRadius.zero)
  default column width: IntrinsicColumnWidth(flex: null)
  table size: 1ร—1
  column offsets: unknown
  row offsets: []
This RenderObject had the following descendants (showing up to depth 5):
    child (0, 0): RenderPadding#950d5 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
      child: RenderFlex#f9517 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
        child 1: RenderParagraph#f1a93 NEEDS-LAYOUT NEEDS-PAINT
          text: TextSpan
        child 2: RenderConstrainedBox#7de36 NEEDS-LAYOUT NEEDS-PAINT
        child 3: RenderConstrainedBox#eaeab NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
          child: RenderConstrainedBox#aaa9b NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
            child: RenderFlex#64667 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

I've tried going down the tree of the tested components and identifying the root cause of these errors, but after isolating the affected widgets in widget-tests without using alchemist, I don't encounter them anymore. Also, the affected RenderObject seems to be within the alchemist source.

I've tried to produce a POC that enables the reproduction of the error, the possibility of a fault within my test structure I didn't notice is also very real.

Alchemist golden test:

void main() {
  group('LegendItem Golden Tests', () {
    goldenTest(
      'LegendItem scenarios render correctly',
      fileName: 'legend_item',
      tags: ['golden'],
      builder: () => GoldenTestGroup(
        children: [
          GoldenTestScenario(
            name: 'scenario 1',
            child: const ProviderScope(
              child: LegendItem(
                color: Colors.red,
                text: 'alchemist text',
                valueLabel: 'betterment value',
                percentLabel: 'percentLabel',
              ),
            ),
          ),
        ],
      ),
    );
  });
}

Code for the tested widget:

class LegendItem extends StatelessWidget {
  const LegendItem({
    super.key,
    required this.color,
    required this.text,
    this.size = 16,
    this.textColor = const Color(0xff505050),
    required this.valueLabel,
    required this.percentLabel,
  });

  /// Legend item color
  final Color color;

  /// Legend indicator size
  final double size;

  /// Item description
  final String text;

  /// Color of [text]
  final Color textColor;

  /// Value label
  final String valueLabel;

  /// Label describing the percentage of the total value
  final String percentLabel;

  static const kSpacing = 2.0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Container(
              width: size,
              height: size,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: color,
              ),
            ),
            const SizedBox(width: kSpacing),
            Flexible(
              child: AutoSizeText(
                text,
                style: Theme.of(context).textTheme.labelSmall,
                textAlign: TextAlign.start,
              ),
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Flexible(child: AutoSizeText(percentLabel)),
            const SizedBox(width: kSpacing),
            Flexible(
              child: AutoSizeText(
                valueLabel,
                style: Theme.of(context).textTheme.titleSmall,
                textAlign: TextAlign.start,
              ),
            ),
          ],
        ),
      ],
    );
  }
}

Steps to reproduce

  1. Wrap given LegendItem in alchemist golden test
  2. Try updating goldens using said test
  3. Encounter compile-time errors

Expected behavior

Expected behavior would include a successful compilation of the test as well as rendering of goldens for the current host platform.

Screenshots

No response

Additional context and comments

I can provide more information about the projects/my current setup if necessary. If the error is actually within alchemist, I would be happy to provide help regarding the identification and fixing of the bug.

fix: Dialog and dropdowns are no longer visible on the golden files

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.5.0

Description

After upgrading from version 0.3.3 to 0.5.0 the golden test stopped showing dialogs and opened dropdown menus. I have changed nothing with the pre test pumps, but still

Steps to reproduce

This is a test I wrote, trying to reproduce it:

import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  goldenTest(
    'drop down',
    fileName: 'dropdown',
    constraints: const BoxConstraints(
      maxWidth: 500,
      maxHeight: 500,
    ),
    pumpBeforeTest: (tester) async {
      await tester.pumpAndSettle();
      await tester.tap(find.byType(DropdownButton<String>));
      await tester.pumpAndSettle();
    },
    builder: () {
      return const TestApp();
    },
  );

  goldenTest(
    'dialog',
    fileName: 'dialog',
    constraints: const BoxConstraints(
      maxWidth: 500,
      maxHeight: 500,
    ),
    pumpBeforeTest: (tester) async {
      await tester.pumpAndSettle();
      await tester.tap(find.byType(TextButton));
      await tester.pumpAndSettle();
    },
    builder: () {
      return const TestApp();
    },
  );
}

class TestApp extends StatelessWidget {
  const TestApp({super.key});

  @override
  Widget build(BuildContext context) => Material(
        child: Center(
          child: Column(
            children: [
              DropdownButton<String>(
                value: '0',
                items: const [
                  DropdownMenuItem<String>(
                    value: '0',
                    child: Text('0'),
                  ),
                  DropdownMenuItem<String>(
                    value: '1',
                    child: Text('1'),
                  ),
                  DropdownMenuItem<String>(
                    value: '2',
                    child: Text('2'),
                  ),
                ],
                onChanged: null,
              ),
              TextButton(
                onPressed: () {
                  showDialog(
                    context: context,
                    builder: (context) => const AlertDialog(
                      content: Text('popup'),
                    ),
                  );
                },
                child: const Text('button'),
              )
            ],
          ),
        ),
      );
}

This is the config setup:

// flutter_test_config.dart

import 'dart:async';
import 'dart:io';

import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  // This is used to detect if it is running in a github actions workflow
  // For other CI Systems this might look different
  final isRunningInCi = Platform.environment['CI'] == 'true';

  return AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      theme: baseTheme.copyWith(textTheme: strippedTextTheme),
      platformGoldensConfig: PlatformGoldensConfig(
        enabled: !isRunningInCi,
      ),
    ),
    run: testMain,
  );
}

final baseTheme = ThemeData.light();
final baseTextTheme = baseTheme.textTheme;

final strippedTextTheme = baseTextTheme.copyWith(
  displayLarge: baseTextTheme.displayLarge?.forGoldens,
  displayMedium: baseTextTheme.displayMedium?.forGoldens,
  displaySmall: baseTextTheme.displaySmall?.forGoldens,
  headlineLarge: baseTextTheme.headlineLarge?.forGoldens,
  headlineMedium: baseTextTheme.headlineMedium?.forGoldens,
  headlineSmall: baseTextTheme.headlineSmall?.forGoldens,
  bodyLarge: baseTextTheme.bodyLarge?.forGoldens,
  bodyMedium: baseTextTheme.bodyMedium?.forGoldens,
  bodySmall: baseTextTheme.bodySmall?.forGoldens,
  titleLarge: baseTextTheme.titleLarge?.forGoldens,
  titleMedium: baseTextTheme.titleMedium?.forGoldens,
  titleSmall: baseTextTheme.titleSmall?.forGoldens,
  labelLarge: baseTextTheme.labelLarge?.forGoldens,
  labelMedium: baseTextTheme.labelMedium?.forGoldens,
  labelSmall: baseTextTheme.labelSmall?.forGoldens,
);

extension _GoldenTextStyle on TextStyle {
  TextStyle get forGoldens {
    final familySplit = fontFamily?.split('/');
    final rawFamily = familySplit?.last;

    return TextStyle(
      inherit: inherit,
      color: color,
      backgroundColor: backgroundColor,
      fontSize: fontSize,
      fontWeight: fontWeight,
      fontStyle: fontStyle,
      letterSpacing: letterSpacing,
      wordSpacing: wordSpacing,
      textBaseline: textBaseline,
      height: height,
      locale: locale,
      foreground: foreground,
      background: background,
      shadows: shadows,
      fontFeatures: fontFeatures,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
      decorationThickness: decorationThickness,
      debugLabel: debugLabel,
      package: null,
      fontFamily: rawFamily,
      fontFamilyFallback: const ['Roboto'],
    );
  }
}

And these are the resulting images (for the ci):

dialog
dropdown

Expected behavior

The dropdown menu and dialog should be visable like in these screenshots generated with the 0.3.3 version (sadly due to the The non-abstract class 'BlockedTextCanvasAdapter' is missing implementations for these members error I can't produce these with the provided code, so I took the screenshot from our app):

app-project-dialog
projects-dropdown

Screenshots

No response

Additional context and comments

No response

request: support multiple themes

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I'd like to run a golden test with multiple themes, not just a single theme

Description

I would like to be able to pass AlchemistConfig a List<ThemeData>? instead of just a ThemeData?.
The goldenTest method should then run the test with all given themes in the current AlchemistConfig.
This would remove redundancy when testing a widget with multiple themes. Currently you would need a separate goldenTest for each theme that should be tested, even if the widget under test is the same.

So instead of

void main() {
  AlchemistConfig.runWithConfig(
      config: AlchemistConfig(
        theme: MyLightTheme()
      ),
      run: () => goldenTest('widget should look good with light theme', ..., widget: MyWidget());
  );

  AlchemistConfig.runWithConfig(
      config: AlchemistConfig(
        theme: MyDarkTheme()
      ),
      run: () => goldenTest('widget should look good with dark theme', ..., widget: MyWidget());
  );

  // etc. for all other themes
}

I would like to be able to write the following

void main() {
  AlchemistConfig.runWithConfig(
      config: AlchemistConfig(
        themes: [MyLightTheme(), MyDarkTheme(), ...]
      ),
      run: () => goldenTest('widget should look good with all themes', ..., widget: MyWidget());
  );

}

Reasoning

Many apps have multiple themes. E.g. a default light theme and a dark theme. If your app cares about accessibility it may even have a high contrast light theme and a high contrast dark theme.

If you want to write a golden test for a widget and ensure that it looks good on each theme, you would have to write a separate test for each theme because right now you can only pass a single theme to AlchemistConfig. If you could pass a list of themes as suggested, you would have to write only one golden test and pass it all the themes the widget should be tested with.

Additional context and comments

No response

fix: Reset pixel ratio and textScaleFactor values on tearDown

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.4.1

Description

The method FlutterGoldenTestAdapter.pumpGoldenTest sets a new pixelRatio and textScalorFactor test values that are not reset to the original values on tear down. This makes the test that run after a golden test to have modified properties that can alter the result of the test.

tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.platformDispatcher.textScaleFactorTestValue =
textScaleFactor;

We should clear values on tearDown:

addTearDown(() {
   tester.binding.window.clearDevicePixelRatioTestValue();
   tester.binding.platformDispatcher.clearTextScaleFactorTestValue();
});

Steps to reproduce

When running the following code, goldenTest sets up a new devicePixelRatio and the test after it fail.

import 'package:alchemist/alchemist.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('before golden devicePixelRatio', (tester) async {
    expect(tester.binding.window.devicePixelRatio, 3);
  });

  goldenTest(
    'A',
    fileName: 'a',
    builder: () => Container(height: 300),
  );

  testWidgets('after golden devicePixelRatio', (tester) async {
    expect(tester.binding.window.devicePixelRatio, 3);  // Fails with actual value 1
  });
}

Expected behavior

When clearing devicePixelRatio on tearDown, the test work as expected

import 'package:alchemist/alchemist.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('before golden devicePixelRatio', (tester) async {
    expect(tester.binding.window.devicePixelRatio, 3);
  });

  goldenTest(
    'A',
    fileName: 'a',
    builder: () => Container(height: 300),
    whilePerforming: (tester) async {
      addTearDown(() {
        tester.binding.window.clearDevicePixelRatioTestValue();
      });
      return;
    },
  );

  testWidgets('after golden devicePixelRatio', (tester) async {
    expect(tester.binding.window.devicePixelRatio, 3);
  });
}

Screenshots

Screenshot 2022-08-02 at 10 42 10

Additional context and comments

No response

fix: Alchemist doesn't run on Flutter 3.13.0

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.6.1

Description

Running Alchemist golden tests in Flutter 3.13.0 throws an error:

: Error: A value of type 'void Function(String, Future<void> Function(WidgetTester), {int? retry, bool semanticsEnabled, bool? skip, dynamic tags, Timeout? timeout, TestVariant<Object?> variant})' can't be assigned to a variable of type 'FutureOr<void> Function(String, Future<void> Function(WidgetTester), {Duration? initialTimeout, bool semanticsEnabled, bool? skip, dynamic tags, Timeout? timeout, TestVariant<Object?> variant})'.
golden_test_adapter.dart:53
 - 'Future' is from 'dart:async'.
- 'WidgetTester' is from 'package:flutter_test/src/widget_tester.dart' ('../../../../Development/flutter/packages/flutter_test/lib/src/widget_tester.dart').
widget_tester.dart:1
- 'Timeout' is from 'package:test_api/src/backend/configuration/timeout.dart' ('../../../../.pub-cache/hosted/pub.dev/test_api-0.6.0/lib/src/backend/configuration/timeout.dart').
timeout.dart:1
- 'TestVariant' is from 'package:flutter_test/src/widget_tester.dart' ('../../../../Development/flutter/packages/flutter_test/lib/src/widget_tester.dart').
widget_tester.dart:1
 - 'Object' is from 'dart:core'.
 - 'Duration' is from 'dart:core'.
TestWidgetsFn defaultTestWidgetsFn = testWidgets;

Steps to reproduce

  1. start an empty template project
  2. add the Alchemist dev dependency
  3. add a test from the examples
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('ListTile Golden Tests', () {
    goldenTest(
      'renders correctly',
      fileName: 'list_tile',
      builder: () => GoldenTestGroup(
        scenarioConstraints: const BoxConstraints(maxWidth: 600),
        children: [
          GoldenTestScenario(
            name: 'with title',
            child: ListTile(
              title: Text('ListTile.title'),
            ),
          ),
          GoldenTestScenario(
            name: 'with title and subtitle',
            child: ListTile(
              title: Text('ListTile.title'),
              subtitle: Text('ListTile.subtitle'),
            ),
          ),
          GoldenTestScenario(
            name: 'with trailing icon',
            child: ListTile(
              title: Text('ListTile.title'),
              trailing: Icon(Icons.chevron_right_rounded),
            ),
          ),
        ],
      ),
    );
  });
}
  1. Run

Expected behavior

Tests should run (and fail without adding the golden files first)

Screenshots

Screenshot 2023-08-17 at 10 34 30

Additional context and comments

No response

Assets image displayed for CI snapshot but not for platform

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.2.1

Description

When I'm generating golden files using an asset image it is not rendered while it is rendered correctly for its ci equivalent.

Steps to reproduce

  1. Create a TestAssetBundle to load your assets
import 'dart:convert';

import 'package:flutter/services.dart';

class TestAssetBundle extends CachingAssetBundle {
  @override
  Future<String> loadString(String key, {bool cache = true}) async {
    final data = await load(key);
    return utf8.decode(data.buffer.asUint8List());
  }

  @override
  Future<ByteData> load(String key) => rootBundle.load(key);
}
  1. Write a goldenTest with an Image.asset widget (wrapped inside a DefaultAssetBundle)
import 'package:alchemist/alchemist.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import '../../utils/test_asset_bundle.dart';

void main() {
  setUpAll(TestWidgetsFlutterBinding.ensureInitialized);

  group('Image.asset: Golden Tests', () {
    goldenTest(
      'renders correctly',
      fileName: 'image_asset',
      widget: GoldenTestGroup(
        children: [
          GoldenTestScenario(
            name: 'XYZ Monster.jpg asset',
            child: DefaultAssetBundle(
              bundle: TestAssetBundle(),
              child: Image.asset('assets/type/XYZ Monster.jpg'),
            ),
          ),
        ],
      ),
    );
  });
}
  1. Run the command flutter test --update-goldens

Expected behavior

The golden file generated should display the asset image.

Screenshots

Asset Image

XYZ Monster

CI Golden

image_asset

Platform Golden

image_asset

Additional context and comments

  • flutter doctor -v:
[โˆš] Flutter (Channel stable, 2.10.2, on Microsoft Windows [version 10.0.22000.527], locale fr-FR)
    โ€ข Flutter version 2.10.2 at C:\src\flutter
    โ€ข Upstream repository https://github.com/flutter/flutter.git
    โ€ข Framework revision 097d3313d8 (5 days ago), 2022-02-18 19:33:08 -0600
    โ€ข Engine revision a83ed0e5e3
    โ€ข Dart version 2.16.1
    โ€ข DevTools version 2.9.2

[โˆš] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    โ€ข Android SDK at C:\Users\Guillaume\AppData\Local\Android\sdk
    โ€ข Platform android-30, build-tools 30.0.3
    โ€ข Java binary at: D:\Android Studio\jre\bin\java
    โ€ข Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    โ€ข All Android licenses accepted.

[โˆš] Chrome - develop for the web
    โ€ข Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[!] Visual Studio - develop for Windows (Visual Studio Build Tools 2017 15.9.35)
    โ€ข Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools
    โ€ข Visual Studio Build Tools 2017 version 15.9.28307.1500
    โ€ข Windows 10 SDK version 10.0.17763.0
    X Visual Studio 2019 or later is required.
      Download at https://visualstudio.microsoft.com/downloads/.
      Please install the "Desktop development with C++" workload, including all of its default components

[โˆš] Android Studio (version 4.1)
    โ€ข Android Studio at D:\Android Studio
    โ€ข Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    โ€ข Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    โ€ข Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)

[โˆš] Connected device (3 available)
    โ€ข Windows (desktop) โ€ข windows โ€ข windows-x64    โ€ข Microsoft Windows [version 10.0.22000.527]
    โ€ข Chrome (web)      โ€ข chrome  โ€ข web-javascript โ€ข Google Chrome 98.0.4758.102
    โ€ข Edge (web)        โ€ข edge    โ€ข web-javascript โ€ข Microsoft Edge 97.0.1072.76

[โˆš] HTTP Host Availability
    โ€ข All required HTTP hosts are available

! Doctor found issues in 1 category.

fix: Dead link for recommended setup example

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.2.0

Description

In example/example.md, there is a reference to a file example/RECOMMENDED_SETUP_GUIDE.md. Because that file does not yet exist, tapping the link results in a 404

Steps to reproduce

  1. Navigate to the example page in pub.dev
  2. Tap the Recommended Setup Guide link
  3. Get a 404

Expected behavior

When navigating to Recommended Setup Guide, the file should exist.

Screenshots

No response

Additional context and comments

Lmk if this is already on a board somewhere since idk where this work is being tracked ๐Ÿ‘

request: whilePerforming for individual tests

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Ability to perform actions for every test individually

Description

GoldenTestScenario should have whilePerforming parameter, so every test could have different action. Currently, to create the same scenario but different actions, you have to create a second file.

Reasoning

I want all test to be in single file, because they all corresponds to the same widget.

fix: Clarify setup guide

Is there an existing issue for this?

  • I have searched the existing issues.

Version

alchemist: ^0.3.3

Description

Hi, great package, solves an important problem! However, I am not sure two aspects are clear.

  1. The setup guide suggests checking for environment variables in a different fashion the what is used in the example. For me, only the example way works, perhaps it's worth it update it?

  2. I am not sure I understand how you are handling the platform tests across developers. Since platform goldens are not tracked in CI, what happens when someone adds a new test? Wouldn't I have to first always run flutter test --update-goldens on my local machine?

Steps to reproduce

Expected behavior

The setup guide, especially in terms of handling the platform tests, should be clear.

Screenshots

No response

Additional context and comments

No response

Golden Testing on iOS and Android

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

Golden Testing on iOS and Android

Description

As a developer, I would like to do golden testing on iOS and Android

Reasoning

These are the platforms we target

Additional context and comments

We currently run golden testing on our pipelines but we cannot do golden testing on integration tests so we cannot test goldens on actual phones.

We currently produce goldens with lots of different sizes with a Linux desktop. That's a problem because the pipeline goldens don't match our local goldens (Mac / windows). We have to do eleborate hacks just to incorporate goldens in our flow.

We've done plenty of research on this and the only other way we can so this is to take literal screenshot and then do image compares ourself.

If this library would allow us to do golden testing on integration tests, especially with Android and iOS, it would be a quantum leap forward for us and basically every flutter developer on the planet that does golden testing.

Thanks

Call setUp Method between GoldenTestScenario

Is there a way to call the setUp method between GoldenTestScenario inside the same GoldenTestGroup?

Example:

 group('Golden Tests', () {
     setUp(() {
           ... some stuff
     });

   goldenTest(
      'name',
      fileName: 'fileanme',
      pumpBeforeTest:  precacheImages,
      builder: () {
        return GoldenTestGroup(
          children: [
            GoldenTestScenario(
              name: 'Secenario 1',
              child: Container(),
            ),       
          GoldenTestScenario(
              name: 'Secenario 2,
              child: Container(),
            ),
          ],
        );
      },
    );
});

I would like to have the following calls:

  • setUp
  • Scenario 1
  • setUp
  • Scenario 2

Thanks in advance and thanks for the library :)

request: different screen size

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would love if I could run my tests with a lot of screen sizes (iPhone / iPad / desktop)

Description

I would love if I could generate file the same way golden toolkit is doing

Screenshot 2022-03-22 at 16 17 53

Reasoning

Since variant is used for the platform, I cannot find a way to keep the device type properly organized with Alchemist.
Variants are useful to get also a nice overview in IDEs.

Screenshot 2022-03-22 at 16 25 39

Additional context and comments

No response

Localization support

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

It would be awesome to add a support for the localisation in the library

Description

First of all, thanks for an awesome library and your work!

The problem is, I haven't found a proper way to add localisations delegate to the golden test, and this feature is extremely important to have in order to test any widget with localisation.

I've added localizationsDelegates param in my fork to enable localisation, and it actually worked. But the problem is, after adding an AppLocalizationDelegate to the MaterialApp, alchemist has stopped rendering any kind of images. Any asset or file image would become transparent. However, after removing the delegate, images appear again. I've tested adding the same AppLocalizationDelegate to the MaterialApp with a golden_toolkit library, and there was no such problem (images were rendered and localisation worked as well).

Maybe you have any ideas on how to add a localizationsDelegates to the test in a way that prevents this issue with rendering images?

Reasoning

I think that supporting localisation in the library would greatly increase the usability

Additional context and comments

You can refer to my closed PR, where I've added the coreWrapper: #43

request: run goldens with multiple configurations

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would love if we could specify a configuration (or more) per test

Description

As a developer, I would like to be able to only have to write a single goldenTest and be able to specify multiple configuration where the test would run.

For example:

goldenTest('foo', configuration: config1, ...);

I would expect that if the configuration parameter is not required the global configuration is used (if any) instead. Hence, once the test finishes the global configuration is restored. I would also expect, to be able to give the Configuration some kind of parameter in order if the null properties should be inherited.

The above is just an example. I'm not sure if it is to be considered the best API. Alternatives, could be, for example, to have a configurations be a List and avoid extracting the test method in order to reuse it. Or another possibility is to have a GoldenTestConfiguration widget:

class GoldenTestConfiguration extends StatelessWidget {
  const GoldenTestConfiguration({
    super.key,
    required this.config,
    required this.child,
    this.inheritConfig = true,
  });

  final AlchemistConfig config;

  final bool inheritConfig;

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return AlchemistConfig.runWithConfig<Widget>(
      config: inheritConfig ? AlchemistConfig.current().merge(config) : config,
      run: () => child,
    );
  }
}

// Or with a widget builder
class GoldenTestConfiguration extends StatelessWidget {
  const GoldenTestConfiguration({
    super.key,
    required this.config,
    required this.builder,
    this.inheritConfig = true,
  });

  final AlchemistConfig config;

  final bool inheritConfig;

  final WidgetBuilder builder;

  @override
  Widget build(BuildContext context) {
    return AlchemistConfig.runWithConfig<Widget>(
      config: inheritConfig ? AlchemistConfig.current().merge(config) : config,
      run: () => builder(context),
    );
  }
}

--

I'm unsure if having logic within setUp and tearDown that alters the config (similar to the snippet below) would work; but if so, I think it feels very verbose in comparison with just providing the configuration to goldenTest.

  AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      forceUpdateGoldenFiles: true,
    ),
    run: () {
      // test here.
    },
  );

Reasoning

In some scenarios, a developer would like to specify more than a single configuration. This could be motivated, for example, when there are different functionalities to be run when a given flavour or target is given. Hence, as a developer you would be interested on running the same test with different configurations.

Example 1
You have different themes for different platforms and you wish to have a config with each theme. Currently the flutter_test_config.dart file looks like the above snippet. This is similar to #21 .

// flutter_test_config.dart
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  final enablePlatformTests =
      !Platform.environment.containsKey('GITHUB_ACTIONS');

  return AlchemistConfig.runWithConfig(
    config: AlchemistConfig(
      theme: AppTheme(platform: TargetPlatform.android).themeData,
      platformGoldensConfig:
          AlchemistConfig.current().platformGoldensConfig.copyWith(
                enabled: enablePlatformTests,
              ),
    ),
    run: testMain,
  );
}

Additional context and comments

N/A

fix: duplicate VGV logos in README on pub dev

Is there an existing issue for this?

  • I have searched the existing issues.

Version

0.2.1

Description

Screen Shot 2022-02-23 at 10 28 49

โ˜๏ธ

Steps to reproduce

  1. Go to pub dev
  2. See duplicate images

Expected behavior

No duplicate logo should appear

Screenshots

No response

Additional context and comments

No response

request: Add a possibility to remove the artificial border.

Is there an existing feature request for this?

  • I have searched the existing issues.

Command

I would like a possibility to remove the artificial border

Description

Hi, the FlutterGoldenTestAdapter adds an 8 pixel border inside FlutterGoldenTestAdapter.pumpGoldenTest. What is the reason for it, and how can I disable it?

Screenshot 2023-11-21 at 12 06 45

Reasoning

This would be useful, because I want my goldens matching my app as close as possible.

Additional context and comments

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.