Flutter Readiness Checklist

Functionize Studio now tests Flutter web applications end to end. Flutter draws its interface onto a canvas instead of standard HTML, so Studio reads your app through Flutter's semantics tree, the same layer screen readers use.

The four steps below make that tree complete and stable. The first one is required. The others make your tests more reliable, and most of them also make your app more accessible.

1. Turn on semantics in your test builds

Required. One change to main().

Flutter web leaves its semantics tree off until a screen reader asks for it. Without it, Studio sees a blank canvas. Studio's side is already on by default, but the switch for the tree lives inside your app's code, so this is the one change only you can make. Turn it on with SemanticsBinding.instance.ensureSemantics(), behind a build flag so your production build is unchanged.

// main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/widgets.dart';

const bool testBuild = bool.fromEnvironment('FUNCTIONIZE_TEST');

void main() {
  runApp(const MyApp());
  if (kIsWeb && testBuild) {
    SemanticsBinding.instance.ensureSemantics();
  }
}

Then build the environment Studio tests against with the flag set:

flutter build web --dart-define=FUNCTIONIZE_TEST=true

Builds without the flag behave exactly as they do today.

2. Label your custom widgets

Recommended. Only for widgets you built yourself.

Standard Material and Cupertino widgets already describe themselves: buttons, text fields, checkboxes and menus need nothing extra. Widgets you draw yourself with CustomPaint, or make tappable with a bare GestureDetector, have no label, so they don't appear in the tree at all. Wrap them in Semantics and say what they are.

Semantics(
  label: 'Add to cart',
  button: true,
  child: GestureDetector(
    onTap: addToCart,
    child: CustomPaint(painter: CartIconPainter()),
  ),
)

3. Give key controls a stable identifier

Recommended. Start with login, primary actions, form fields and navigation.

Recent Flutter releases add an identifier to Semantics. It's the Flutter equivalent of a data-testid: a name that stays the same when the label text, layout or language changes, which gives Studio the strongest signal for finding the right element.

Semantics(
  identifier: 'checkout-button',
  child: FilledButton(
    onPressed: checkout,
    child: const Text('Checkout'),
  ),
)

On web, Flutter writes it to the page as a flt-semantics-identifier attribute. Check that your Flutter version supports Semantics.identifier before you adopt it (see the Flutter API reference).

4. Watch for merged semantics in lists and tables

Check. Matters where tests read values inside rows or cells.

MergeSemantics, and some composite widgets such as list tiles, combine their children into one node with one long label. That works for clicking a row. It doesn't work when a test needs to check one value inside it, such as a price in a table cell.

Where your tests need to read individual values, keep those values out of merged groups, so each one stays its own node in the tree.

At a glance

Step What you change Status
Turn on semantics ensureSemantics() in main(), behind a test-build flag Required
Label custom widgets Semantics(label:, button:) around custom-drawn or gesture-only widgets Recommended
Stable identifiers Semantics(identifier:) on key controls Recommended
Merged semantics Keep values your tests check out of merged rows and cells Check

Check your build in two minutes

  1. Open your test build in Chrome.
  2. Open DevTools and inspect the page.
  3. Look for flt-semantics elements. If there are none, step 1 isn't active in this build.
  4. Find a few key controls and confirm each has a readable label and, where you added one, its flt-semantics-identifier.

Questions about getting your Flutter app ready? Email support@functionize.com.

New to Functionize? Start a free trial at https://www.functionize.com/free-trial