LogoWidgetbook

Widgetbook Cloud Review

Widgetbook Cloud Review currently works with GitHub only. We are planning to add more Git providers in the future.

Widgetbook Cloud Review has two main features that can help you improve your development workflow:

  1. UI Regression Detection: to automatically detect UI regressions.
  2. Figma x Flutter Comparison: to verify if the developer meets the design requirements.

Pre-requisites#

  1. Connect your Widgetbook Project to a GitHub repo from the project settings page.
  2. Make sure to you have a Widgetbook Build on pushes of new commits via CI (e.g. GitHub Actions).

UI Regression Detection#

Widgetbook Cloud provides a zero-config UI regression detection system. It automatically compares the visual differences between different builds and highlights the changes in the UI.

It works similarly like GitHub PRs. Instead of only comparing code changes, with Widgetbook Cloud Reviews, you can now also compare visual differences and detect UI regressions between different branches or commits.

GitHub PRsWidgetbook Cloud Reviews
Compare code changes between base commit and head commitCompare visual differences between base build and head build

Guide#

  1. Finish the pre-requisites set up.
  2. Create a new branch (e.g. feat/new-button) and commit your changes to it.
  3. Create a pull request from your head branch (e.g. feat/new-button) to the base branch (e.g. main).
  4. After your build upload finishes (i.e. the CI job from Pre-requisites), a commit status will be added to your PR once the review is ready.

Reviewing#

After your review is ready, you can start comparing the visual differences between the base and head builds. Here are some things that can help improving your workflow:

  1. Validate if your visual changes are intentional or not.
  2. Ask team mates for review if all changes were intentional.
  3. Provide feedback by copying the link to the Widgetbook Cloud Review Story and leaving a comment in the GitHub PR.

If the reviewer doesn't have access to GitHub (e.g. a designer), we suggest using messaging tools (e.g. Slack). Please reach out to us in case this doesn't suit you and you want to use a dedicated comment feature in Widgetbook Cloud.

Review Statuses#

StatusDescription
OpenAwaiting assessment.
MergedLinked with merged PRs, thus considered merged.
ClosedAssociated with closed PRs, hence viewed as closed.
UpdatingMissing crucial information for an open classification.

Figma x Flutter Comparison#

Widgetbook Cloud Review can also help with verifying if the developer meets the design requirements by comparing the Flutter widgets to their corresponding Figma designs.

Guide#

To have "View in Figma" button in your reviews, you need to add the Figma URL to your use-cases:

  1. In your Figma design file, navigate to the component that resembles your Widget.

  2. Copy the link to the component by right-mouse clicking on the component and selecting Copy/Paste as > Copy Link.

  3. Set the designLink property of the @UseCase annotation by pasting the copied link.

    // Example from https://github.com/widgetbook/groceries-demo/blob/main/widgetbook/lib/core/app_bar.dart
    @UseCase(
      designLink: 'https://www.figma.com/file/EXuEpwiyksLAejYX1qr1v4/Fluttercon-Berlin-2023-Demo?type=design&node-id=277-3056&mode=design&t=nVL8hLmc1jlcilZL-4',
      name: 'Default',
      type: AppBar
    )
    
  4. Re-run build_runner to update the use-cases metadata.

    dart run build_runner build -d
    
  5. Commit your changes and push them to your repository; to create a new build.

Limitations#

Animations#

If your use-cases have any animations (e.g. CircularProgressIndication), then the visual comparison might not work as expected. This is because the visual comparison algorithm is not able to compare animations.

We suggest using one of the following workarounds:

  1. Starting animations on-demand: you can use a boolean knob to start your animations. This way, you can disable the animations during the visual comparison but you can still see the animations in the Widgetbook.

    @UseCase(name: 'Default', type: CircularProgressIndicator)
    Widget onDemandAnimationUseCase(BuildContext context) {
      return CircularProgressIndicator(
        value: context.knobs.boolean(label: 'Animate') ? null : 0.5,
      );
    }
    
  2. Disable animations: by providing a constant value to the parameter that controls the animation, you can disable the animations during the visual comparison.

    @UseCase(name: 'Default', type: CircularProgressIndicator)
    Widget disabledAnimationUseCase(BuildContext context) {
      return CircularProgressIndicator(
        value: 0.5,
      );
    }
    

Random values#

If your use-case displays random values (e.g. dates), that can be different across different builds, the visual comparison might not work as expected.

We suggest using one of the following workarounds:

  1. Use a constant value for the random value.

    @UseCase(name: 'Default', type: Text)
    Widget constantValueUseCase(BuildContext context) {
    -  return Text(Random().nextInt(10).toString());
    +  return Text('10');
    }
    
  2. Use Knobs with a default value.

    @UseCase(name: 'Default', type: Text)
    Widget constantValueUseCase(BuildContext context) {
    -  return Text(Random().nextInt(10).toString());
    +  return Text(
    +    context.knobs.string(label: 'value', defaultValue: '10'),
    +  );
    }
    

Addons and Knobs#

Currently, only the default use-case configuration is considered for the visual comparison.

If you have multiple addons/knobs configurations, we have good news for you! We are working on supporting this in the near future.

Until the first-class support is available, we suggest splitting your variants in different use-cases instead of using knobs as follows:

Before
@UseCase(name: 'Default', type: Button)
Widget buttonUseCase(BuildContext context) {
  return Button(
    type: context.knobs.list(
      label: 'Type',
      options: [ButtonType.primary, ButtonType.secondary]
    ),
  );
}
After
@UseCase(name: 'Primary', type: Button)
Widget primaryButtonUseCase(BuildContext context) {
  return Button(
    type: ButtonType.primary,
  );
}

@UseCase(name: 'Secondary', type: Button)
Widget secondaryButtonUseCase(BuildContext context) {
  return Button(
    type: ButtonType.secondary,
  );
}