flutter_beacon_widget_extension
Automatic E2E UI mapping for Flutter. Scans your presentation layer for interactive widgets and generates *.beacon.dart files — so AI tools (Claude + Maestro) can write stable E2E flows without brittle text-matching.
How it works
The build_runner builder scans every .dart file matching your configured glob and produces a *.beacon.dart alongside it. Each generated file lists every interactive widget found, with its stable key, semantic type, valid actions, and a pre-built Maestro YAML snippet.
Three ways a widget gets tracked
1. Native Flutter widgets — just add a ValueKey
No annotation needed. The builder recognizes 50+ native interactive widgets:
TextField(
key: const ValueKey('login_email_field'),
controller: _controller,
)
ElevatedButton(
key: const ValueKey('login_submit_button'),
onPressed: _submit,
child: const Text('Entrar'),
)
ListView(
key: const ValueKey('results_list'),
children: [...],
)
Supported: GestureDetector, InkWell, TextField, TextFormField, ElevatedButton, TextButton, OutlinedButton, FilledButton, IconButton, FloatingActionButton, ListView, GridView, PageView, Switch, Checkbox, Radio, Slider, ChoiceChip, FilterChip, and 30+ more.
2. BeaconWidget — for custom components
BeaconWidget(
id: 'radar_card',
description: 'Radar alert card — tap for details, swipe to dismiss',
type: BeaconType.card,
actions: [BeaconAction.tap, BeaconAction.swipe],
child: RadarCard(radar: radar),
)
3. @Beacon — screen-level documentation
@Beacon(
description: 'Login screen — email + password form, errors as SnackBar',
type: BeaconType.screen,
)
class LoginPage extends StatefulWidget { ... }
Setup
pubspec.yaml:
dependencies:
flutter_beacon_widget_extension: ^0.1.0
dev_dependencies:
build_runner: ^2.0.0
build.yaml (scope the generator to your UI layer):
targets:
$default:
builders:
flutter_beacon_widget_extension|flutter_beacon_widget_extension:
enabled: true
generate_for:
- lib/features/**/presentation/**/*.dart
.gitignore (generated files are reproducible — don't commit them):
*.beacon.dart
Generate:
flutter pub run build_runner build
Generated output
login_page.dart → login_page.beacon.dart:
// GENERATED BY flutter_beacon_widget_extension — DO NOT EDIT.
import 'package:flutter_beacon_widget_extension/flutter_beacon_widget_extension.dart';
const List<BeaconInfo> loginPageBeacons = [
BeaconInfo(
key: 'login_page',
description: 'Login screen — email + password form',
type: BeaconType.screen,
actions: [BeaconAction.assertVisible],
widgetClass: 'LoginPage',
sourcePath: 'lib/features/auth/presentation/pages/login_page.dart',
sourceLine: 42,
),
BeaconInfo(
key: 'login_email_field',
description: 'Login email field',
type: BeaconType.textField,
actions: [BeaconAction.tap, BeaconAction.input, BeaconAction.assertVisible],
widgetClass: 'TextField',
sourcePath: 'lib/features/auth/presentation/pages/login_page.dart',
sourceLine: 91,
),
...
];
Each BeaconInfo has a maestroHint getter that returns ready-to-paste Maestro YAML:
loginPageBeacons[1].maestroHint
// - tapOn:
// id: "login_email_field"
// - inputText:
// id: "login_email_field"
// text: "<value>"
// - assertVisible:
// id: "login_email_field"
Using with Maestro
Read the .beacon.dart file before writing a flow. Use the key as id: — never text-match:
# login_flow.yaml
appId: com.example.myapp
---
- assertVisible:
id: "login_page"
- tapOn:
id: "login_email_field"
- inputText: "user@example.com"
- tapOn:
id: "login_submit_button"
CLAUDE.md rule
Add this to your project's CLAUDE.md so Claude always reads beacon files before writing flows:
Before writing any Maestro flow for a screen, read the *.beacon.dart file
generated alongside the page file. Use the key field as id: in Maestro — never text-match.
Libraries
- builder
- flutter_beacon_widget_extension
- flutter_beacon — automatic E2E UI mapping for AI-assisted testing.