automation_keys_gen 0.1.5 copy "automation_keys_gen: ^0.1.5" to clipboard
automation_keys_gen: ^0.1.5 copied to clipboard

Enforces a consistent automation key contract for Flutter UI tests, with CLI validation and generated Markdown documentation.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/onboarding/welcome',
      routes: {
        '/onboarding/welcome': (_) => const WelcomePage(),
        '/onboarding/intro': (_) => const IntroPage(),
        '/profile/settings': (_) => const SettingsPage(),
        '/catalog/list': (_) => const CatalogListPage(),
        '/catalog/detail': (_) => const CatalogDetailPage(),
      },
    );
  }
}

/// ONBOARDING / welcome
///
/// Demonstrates a simple page without screen variants.
@AutomationKeyDescription('Onboarding welcome screen (entry point).')
class WelcomePage extends StatelessWidget with AutomationKeyHelper {
  const WelcomePage({super.key});

  @override
  String get moduleName => 'onboarding';

  @override
  String get featureName => 'welcome';

  @override
  Widget build(BuildContext context) {
    @AutomationKeyDescription('Page title text in the AppBar.')
    final pageTitleKey = textKey('page_title');

    @AutomationKeyDescription('Main headline text on the welcome page.')
    final titleKey = textKey('headline');

    @AutomationKeyDescription('Supporting subtitle text below headline.')
    final subtitleKey = textKey('subtitle');

    @AutomationKeyDescription('Root content container for the welcome page.')
    final contentContainerKey = containerKey('content');

    @AutomationKeyDescription('Primary CTA button to continue onboarding.')
    final continueKey = buttonKey('continue');

    return Scaffold(
      key: pageKey,
      appBar: AppBar(
        title: Text('Welcome', key: pageTitleKey),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          key: contentContainerKey,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Welcome!', key: titleKey, style: Theme.of(context).textTheme.headlineMedium),
            const SizedBox(height: 12),
            Text('This example shows how to build stable automation keys.', key: subtitleKey),
            const Spacer(),
            ElevatedButton(
              key: continueKey,
              onPressed: () => Navigator.of(context).pushNamed('/onboarding/intro'),
              child: const Text('Continue'),
            ),
          ],
        ),
      ),
    );
  }
}

/// ONBOARDING / intro
///
/// Demonstrates a screen variant via [screenName] and an overlay key.
@AutomationKeyDescription('Onboarding intro flow (step one). Demonstrates screen variant + overlays.')
class IntroPage extends StatelessWidget with AutomationKeyHelper {
  const IntroPage({super.key});

  @override
  String get moduleName => 'onboarding';

  @override
  String get featureName => 'intro';

  @override
  String? get screenName => 'step_one';

  @override
  Widget build(BuildContext context) {
    @AutomationKeyDescription('Page title text in the AppBar.')
    final pageTitleKey = textKey('page_title');

    @AutomationKeyDescription('Info icon in the AppBar. Opens info dialog.')
    final infoIconKey = iconKey('info');

    @AutomationKeyDescription('Info dialog container key (overlay).')
    final infoDialogKey = dialogKey('info');

    @AutomationKeyDescription('Info dialog title text.')
    final dialogTitleKey = textKey('dialog_title');

    @AutomationKeyDescription('Info dialog body text.')
    final dialogBodyKey = textKey('dialog_body');

    @AutomationKeyDescription('Info dialog close button.')
    final dialogCloseKey = buttonKey('dialog_close');

    @AutomationKeyDescription('Help floating action button on the intro page.')
    final helpFabKey = fabKey('help');

    @AutomationKeyDescription('Intro step title text.')
    final titleKey = textKey('title');

    @AutomationKeyDescription('Language dropdown control in this step.')
    final languageDropdownKey = dropdownKey('language');

    @AutomationKeyDescription('Next button in onboarding intro step one.')
    final nextKey = buttonKey('next');

    return Scaffold(
      key: pageKey,
      appBar: AppBar(
        title: Text('Intro', key: pageTitleKey),
        actions: [
          IconButton(
            key: infoIconKey,
            icon: const Icon(Icons.info_outline),
            onPressed: () {
              showDialog<void>(
                context: context,
                builder: (_) => AlertDialog(
                  key: infoDialogKey,
                  title: Text('Info', key: dialogTitleKey),
                  content: Text('This dialog demonstrates overlay keys.', key: dialogBodyKey),
                  actions: [
                    TextButton(
                      key: dialogCloseKey,
                      onPressed: () => Navigator.of(context).pop(),
                      child: const Text('Close'),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        key: helpFabKey,
        onPressed: () {},
        child: const Icon(Icons.help_outline),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Step 1', key: titleKey),
            const SizedBox(height: 12),
            DropdownButton<String>(
              key: languageDropdownKey,
              value: 'en',
              items: const [
                DropdownMenuItem(value: 'en', child: Text('English')),
                DropdownMenuItem(value: 'id', child: Text('Bahasa')),
              ],
              onChanged: (_) {},
            ),
            const Spacer(),
            ElevatedButton(
              key: nextKey,
              onPressed: () => Navigator.of(context).pushNamed('/profile/settings'),
              child: const Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

/// PROFILE / settings
///
/// Demonstrates appNameOverride (page-level override)
/// and common form controls.
@AutomationKeyDescription('User settings screen (profile module). Demonstrates appNameOverride + form controls.')
class SettingsPage extends StatelessWidget with AutomationKeyHelper {
  const SettingsPage({super.key});

  @override
  String? get appNameOverride => 'demo';

  @override
  String get moduleName => 'profile';

  @override
  String get featureName => 'settings';

  @override
  Widget build(BuildContext context) {
    @AutomationKeyDescription('Page title text in the AppBar.')
    final pageTitleKey = textKey('page_title');

    @AutomationKeyDescription('Main form container for settings.')
    final formContainerKey = containerKey('form');

    @AutomationKeyDescription('Section title: Preferences.')
    final sectionTitleKey = textKey('section_title');

    @AutomationKeyDescription('Display name input field.')
    final displayNameKey = inputKey('display_name');

    @AutomationKeyDescription('Notifications toggle switch.')
    final notificationsKey = toggleKey('notifications');

    @AutomationKeyDescription('Agree to terms checkbox.')
    final agreeTermsKey = checkboxKey('agree_terms');

    @AutomationKeyDescription('Text label for agree-to-terms checkbox.')
    final agreeTermsLabelKey = textKey('agree_terms_label');

    @AutomationKeyDescription('Save button. Opens confirmation bottom sheet.')
    final saveKey = buttonKey('save');

    @AutomationKeyDescription('Bottom sheet shown after saving settings.')
    final savedSheetKey = bottomSheetKey('saved_confirmation');

    @AutomationKeyDescription('Bottom sheet title text.')
    final savedSheetTitleKey = textKey('bottom_sheet_title');

    @AutomationKeyDescription('Info banner shown on settings page.')
    final infoBannerKey = bannerKey('info');

    return Scaffold(
      key: pageKey,
      appBar: AppBar(
        title: Text('Settings', key: pageTitleKey),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          key: formContainerKey,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Preferences', key: sectionTitleKey),
            const SizedBox(height: 12),
            TextField(
              key: displayNameKey,
              decoration: const InputDecoration(labelText: 'Display name'),
            ),
            const SizedBox(height: 12),
            SwitchListTile(
              key: notificationsKey,
              value: true,
              onChanged: (_) {},
              title: const Text('Enable notifications'),
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Checkbox(
                  key: agreeTermsKey,
                  value: true,
                  onChanged: (_) {},
                ),
                Expanded(
                  child: Text('I agree to terms', key: agreeTermsLabelKey),
                ),
              ],
            ),
            const SizedBox(height: 12),
            ElevatedButton(
              key: saveKey,
              onPressed: () {
                showModalBottomSheet<void>(
                  context: context,
                  builder: (_) => SizedBox(
                    key: savedSheetKey,
                    height: 140,
                    child: Center(
                      child: Text('Saved!', key: savedSheetTitleKey),
                    ),
                  ),
                );
              },
              child: const Text('Save'),
            ),
            const SizedBox(height: 12),
            Banner(
              key: infoBannerKey,
              message: 'Info',
              location: BannerLocation.topEnd,
              child: const SizedBox(height: 0),
            ),
          ],
        ),
      ),
    );
  }
}

/// CATALOG / list
///
/// Demonstrates stable list keys.
@AutomationKeyDescription('Catalog list screen. Demonstrates stable list keys for automation.')
class CatalogListPage extends StatelessWidget with AutomationKeyHelper {
  const CatalogListPage({super.key});

  @override
  String get moduleName => 'catalog';

  @override
  String get featureName => 'list';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: pageKey,
      appBar: AppBar(
        /// @AutomationKeyDescription: Page title text in the AppBar.
        title: Text('Catalog', key: textKey('page_title')),
      ),
      body: ListView(
        children: [
          /// @AutomationKeyDescription: Catalog list tile for item sku_1001 (stable id).
          ListTile(
            key: listItemKey('sku_1001'),
            /// @AutomationKeyDescription: Title text for item sku_1001.
            title: Text('Item A', key: textKey('item_title_sku_1001')),
            trailing: Card(
              /// @AutomationKeyDescription: Card container inside list tile for item sku_1001.
              key: listCardKey('sku_1001'),
              child: const Padding(
                padding: EdgeInsets.all(8),
                child: Icon(Icons.chevron_right),
              ),
            ),
            onTap: () => Navigator.of(context).pushNamed('/catalog/detail'),
          ),
          /// @AutomationKeyDescription: Catalog list tile for item sku_1002 (stable id).
          ListTile(
            key: listItemKey('sku_1002'),
            /// @AutomationKeyDescription: Title text for item sku_1002.
            title: Text('Item B', key: textKey('item_title_sku_1002')),
            trailing: Card(
              /// @AutomationKeyDescription: Card container inside list tile for item sku_1002.
              key: listCardKey('sku_1002'),
              child: const Padding(
                padding: EdgeInsets.all(8),
                child: Icon(Icons.chevron_right),
              ),
            ),
            onTap: () => Navigator.of(context).pushNamed('/catalog/detail'),
          ),
          /// @AutomationKeyDescription: Catalog list tile for item sku_1003 (stable id).
          ListTile(
            key: listItemKey('sku_1003'),
            /// @AutomationKeyDescription: Title text for item sku_1003.
            title: Text('Item C', key: textKey('item_title_sku_1003')),
            trailing: Card(
              /// @AutomationKeyDescription: Card container inside list tile for item sku_1003.
              key: listCardKey('sku_1003'),
              child: const Padding(
                padding: EdgeInsets.all(8),
                child: Icon(Icons.chevron_right),
              ),
            ),
            onTap: () => Navigator.of(context).pushNamed('/catalog/detail'),
          ),
        ],
      ),
    );
  }
}

/// CATALOG / detail
///
/// Demonstrates an error state container key.
@AutomationKeyDescription('Catalog detail screen (default variant). Demonstrates image + error state keys.')
class CatalogDetailPage extends StatelessWidget with AutomationKeyHelper {
  const CatalogDetailPage({super.key});

  @override
  String get moduleName => 'catalog';

  @override
  String get featureName => 'detail';

  @override
  String? get screenName => 'default';

  @override
  Widget build(BuildContext context) {
    @AutomationKeyDescription('Page title text in the AppBar.')
    final pageTitleKey = textKey('page_title');

    @AutomationKeyDescription('Hero image on the detail page.')
    final heroImageKey = imageKey('hero');

    @AutomationKeyDescription('Error state container shown when item is out of stock.')
    final outOfStockKey = errorStateKey('out_of_stock');

    @AutomationKeyDescription('Out-of-stock error message text.')
    final errorMessageKey = textKey('error_message');

    @AutomationKeyDescription('Back button to return to catalog list.')
    final backKey = buttonKey('back_to_list');

    return Scaffold(
      key: pageKey,
      appBar: AppBar(
        title: Text('Detail', key: pageTitleKey),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Image(
              key: heroImageKey,
              image: const AssetImage('assets/placeholder.png'),
              errorBuilder: (_, __, ___) => const SizedBox.shrink(),
            ),
            const SizedBox(height: 12),
            Container(
              key: outOfStockKey,
              padding: const EdgeInsets.all(12),
              color: Colors.red.shade50,
              child: Text('Out of stock', key: errorMessageKey),
            ),
            const Spacer(),
            ElevatedButton(
              key: backKey,
              onPressed: () => Navigator.of(context).pushNamed('/catalog/list'),
              child: const Text('Back'),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
246
downloads

Publisher

unverified uploader

Weekly Downloads

Enforces a consistent automation key contract for Flutter UI tests, with CLI validation and generated Markdown documentation.

Repository (GitHub)
View/report issues
Contributing

Topics

#flutter #testing #automation #key

Documentation

API reference

License

MIT (license)

Dependencies

analyzer, args, flutter, path, yaml

More

Packages that depend on automation_keys_gen