{"id":29285727,"url":"https://github.com/moinsen-dev/moinsen_voyage","last_synced_at":"2025-07-05T23:09:55.521Z","repository":{"id":301847118,"uuid":"1010457732","full_name":"moinsen-dev/moinsen_voyage","owner":"moinsen-dev","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-29T07:09:10.000Z","size":88,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2025-06-29T07:24:24.509Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moinsen-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-29T05:37:18.000Z","updated_at":"2025-06-29T07:09:13.000Z","dependencies_parsed_at":"2025-06-29T07:34:44.205Z","dependency_job_id":null,"html_url":"https://github.com/moinsen-dev/moinsen_voyage","commit_stats":null,"previous_names":["moinsen-dev/moinsen_voyage"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/moinsen-dev/moinsen_voyage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fmoinsen_voyage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fmoinsen_voyage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fmoinsen_voyage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fmoinsen_voyage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moinsen-dev","download_url":"https://codeload.github.com/moinsen-dev/moinsen_voyage/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moinsen-dev%2Fmoinsen_voyage/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263819240,"owners_count":23516123,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-05T23:09:45.713Z","updated_at":"2025-07-05T23:09:55.504Z","avatar_url":"https://github.com/moinsen-dev.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🚢 Moinsen Voyage\n[![pub package](https://img.shields.io/pub/v/moinsen_voyage.svg)](https://pub.dev/packages/moinsen_voyage)\n[![Flutter](https://img.shields.io/badge/Flutter-%3E%3D3.0.0-blue)](https://flutter.dev)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003e **Journey-based testing for Flutter that makes sense**\n\u003e\n\u003e Transform how you test Flutter apps by recording real user journeys and replaying them with intelligent variations. Features exception handling, comprehensive UI management, automated analysis, and production-ready reporting.\n\n![Moinsen Voyage](moinsen_voyage.png)\n\n## 🌟 Why Moinsen Voyage?\n\nPicture this: You're manually testing your Flutter app, navigating through screens, tapping buttons, entering text. Everything works perfectly. But then you ask yourself - \"Now I have to write all of this as a test?\"\n\nWhat if you didn't have to?\n\n**Moinsen Voyage** records your manual testing sessions and transforms them into comprehensive, intelligent test suites. But it goes far beyond simple record-and-replay. It understands your app's state management, generates test data variations, monitors performance, provides perceptual golden testing, and even generates app store screenshots.\n\n### The Problem with Traditional Testing\n\nTraditional Flutter testing often feels like describing a journey in a foreign language. You write abstract commands that attempt to recreate user behavior, but the connection between what users actually do and what your tests check can be tenuous. Consider this traditional test:\n\n```dart\n// Traditional approach - disconnected from real usage\ntestWidgets('User can complete purchase', (tester) async {\n  await tester.pumpWidget(MyApp());\n  await tester.tap(find.byKey(Key('product_1')));\n  await tester.pump();\n  await tester.tap(find.byKey(Key('add_to_cart')));\n  await tester.pump();\n  // ... many more manual steps\n});\n```\n\nThis approach has several pain points that developers face daily:\n- You write the same interaction twice (once manually, once in code)\n- State management requires complex mocking and setup\n- Edge cases are often missed\n- Visual regressions require pixel-perfect matching that breaks easily\n- Performance degradation goes unnoticed\n- App store screenshots need separate tooling and manual updates\n\n### The Voyage Approach\n\nMoinsen Voyage reimagines testing as a journey. Instead of writing tests, you demonstrate them. The framework then intelligently transforms your demonstrations into comprehensive test coverage:\n\n```dart\n// The Voyage way - natural and comprehensive\ntestVoyage('User can complete purchase', (voyage) async {\n  // Record once, test everywhere\n  final journey = await voyage.loadJourney('purchase_flow');\n\n  // Automatically test with variations\n  await journey.replayWith([\n    // Different locales\n    VoyageContext.locale('de_DE'),\n    VoyageContext.locale('en_US'),\n\n    // Different data conditions\n    VoyageContext.outOfStock(),\n    VoyageContext.slowNetwork(),\n\n    // Different user types\n    VoyageContext.premiumUser(),\n    VoyageContext.guestUser(),\n  ]);\n});\n```\n## 🎯 Core Features\n\n### 1. 🎬 Journey Recording \u0026 Replay\n\nAt the heart of Moinsen Voyage is a simple but powerful idea: your manual testing sessions are valuable data. The framework provides special widgets that act as observers, silently recording every interaction while you use your app naturally.\n\nThese aren't just macro recordings - they're intelligent captures that understand the semantic meaning of your actions. When you tap a \"Buy Now\" button, Voyage doesn't just record \"tap at coordinates 150,300\". It records \"user initiated purchase action on product_detail_screen\". This semantic understanding makes your tests resilient to UI changes.\n\n**Enhanced Event Capture**: Our advanced capture system handles rapid user interactions flawlessly, ensuring no events are lost even during intensive user sessions.\n\n### 2. 🛡️ Exception Handling \u0026 Recovery\n\nMoinsen Voyage includes a comprehensive exception handling system that catches errors gracefully without crashing your app. When exceptions occur, they're automatically recorded with full context and displayed with helpful error UIs.\n\n```dart\n// Automatic exception boundary with graceful fallback\nVoyageCapture(\n  id: 'risky_widget',\n  captureExceptions: true,  // Enabled by default\n  child: ProblematicWidget(),\n  errorBuilder: (exception) =\u003e CustomErrorWidget(exception),\n)\n```\n\n**Exception Features**:\n- **Automatic Recording**: All exceptions are captured with severity levels (warning, error, critical)\n- **Graceful Fallback**: Shows helpful error UI instead of app crashes\n- **Journey Integration**: Exceptions become part of your journey data for analysis\n- **Automated Analysis**: Built-in exception analysis provides quality assessment and recommendations\n\n### 3. 🎛️ Comprehensive UI Management\n\nBuilt-in UI components provide complete control over your testing experience without any setup complexity:\n\n```dart\nvoid main() {\n  runApp(\n    VoyageWrapper(  // One wrapper gives you everything\n      child: MyApp(),\n    ),\n  );\n}\n```\n\n**UI Features**:\n- **Journey Dashboard**: Visual journey management and inspection\n- **Debug Badge**: Quick access to recording controls and settings\n- **Test Integration**: Seamless replay functionality for Flutter tests\n- **Settings Panel**: Configure all aspects of Voyage behavior\n- **Journey Viewer**: Browse, search, and analyze recorded journeys\n\n### 4. State Management Intelligence\n\nOne of the biggest challenges in testing stateful applications is ensuring your tests start from a known state and can verify state changes correctly. Moinsen Voyage speaks fluently with all major state management solutions:\n\n**For Riverpod users**, Voyage automatically tracks provider dependencies and state evolution:\n```dart\n// Your normal Riverpod code\nclass CartScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final cart = ref.watch(cartProvider);\n    return VoyageRecordable(  // Just wrap your widget\n      journeyName: 'shopping_cart',\n      child: YourNormalWidget(cart: cart),\n    );\n  }\n}\n```\n\n**For Bloc enthusiasts**, Voyage captures the complete event-state dance:\n```dart\n// Voyage automatically records bloc events and resulting states\nVoyageBlocBuilder\u003cCartBloc, CartState\u003e(\n  voyageId: 'cart_management',\n  builder: (context, state) {\n    // Your normal bloc builder code\n  },\n)\n```\n\n**For Hooks practitioners**, every hook's lifecycle is tracked:\n```dart\n// Voyage-aware hooks that record their evolution\nWidget build(BuildContext context) {\n  final controller = useVoyageTextController('user_input');\n  final animationState = useVoyageAnimationController('drawer_animation');\n  // Your normal hooks code\n}\n```\n\n### 3. Intelligent Test Data Generation\n\nReal-world testing requires real-world data. Voyage includes a sophisticated data generation system that understands context and relationships:\n\n```dart\n// Generate realistic test data for your domain\nfinal testUser = await VoyageDataDock.generateUser(\n  locale: 'de_DE',\n  profile: UserProfile.casualShopper,\n  withPurchaseHistory: true,\n);\n\n// Voyage maintains data relationships\nfinal testOrder = await VoyageDataDock.generateOrder(\n  forUser: testUser,\n  products: ProductCategory.electronics,\n  shipping: ShippingType.express,\n);\n```\n\nThe data generator isn't random - it's intelligent. It knows that German phone numbers follow different patterns than US ones, that email addresses should be deliverable for testing email flows, and that credit card numbers should pass Luhn validation while still being test-safe.\n\n### 4. Golden Testing with Perceptual Comparison\n\nMoinsen Voyage introduces a revolutionary approach to golden testing that uses perceptual image comparison. Instead of pixel-perfect matching that breaks with minor changes, our golden testing understands how humans perceive visual differences:\n\n```dart\n// Configure golden testing with human-like perception\nVoyageRecordable(\n  journeyName: 'checkout_flow',\n  goldenConfig: GoldenConfig(\n    enabled: true,\n    captureStrategy: CaptureStrategy.smart,\n    tolerance: GoldenTolerance(\n      global: 0.01,  // 1% difference allowed globally\n      regions: {\n        'dynamic_content': 0.05,  // 5% for areas with dynamic content\n      },\n    ),\n  ),\n  child: CheckoutScreen(),\n)\n\n// During testing, golden comparison uses SSIM algorithm\ntestVoyage('Checkout UI remains visually consistent', (voyage) async {\n  final journey = await voyage.loadJourney('checkout_flow');\n  final result = await GoldenTestRunner.runGoldenTests(journey);\n  \n  expect(result.passed, isTrue);\n  // If failed, approve changes with: GoldenApprovalWorkflow.reviewPendingUpdates()\n});\n```\n\nThe golden testing system features:\n- **Perceptual Comparison**: Uses SSIM (Structural Similarity Index) for human-like comparison\n- **Version Control**: Golden images are versioned with branch support\n- **Approval Workflow**: Review and approve visual changes with side-by-side comparison\n- **Smart Capture**: Automatically captures at optimal moments during journey replay\n\n### 5. 📊 Comprehensive Reporting \u0026 Analysis\n\nEvery journey generates detailed reports in multiple formats (JSON, Markdown, HTML) with comprehensive analysis:\n\n```dart\n// Generate detailed journey report\nfinal report = await JourneyReporter.instance.generateReport(\n  journey: journey,\n  includeExceptionAnalysis: true,\n  includePerformanceMetrics: true,\n  includeVisualRegression: true,\n);\n\n// Save in multiple formats\nawait JourneyReporter.instance.saveReport(report, 'reports/journey.html');\nawait JourneyReporter.instance.saveReport(report, 'reports/journey.md');\n```\n\n**Reporting Features**:\n- **Exception Analysis**: Automated quality assessment with recommendations\n- **Performance Monitoring**: Frame times, memory usage, network metrics\n- **Golden Testing**: Perceptual comparison results with approval workflow\n- **Trend Analysis**: Track quality improvements/degradations over time\n- **Multi-format Output**: HTML with graphs, Markdown for docs, JSON for CI/CD\n\n### 6. Performance Lighthouse\n\nPerformance is a feature, and Voyage treats it as such. Every journey automatically includes performance profiling:\n\n```dart\n// Performance is monitored automatically during replay\nfinal report = await journey.replay();\n\nprint(report.performance.summary);\n// Average frame time: 12.3ms (81 FPS)\n// Jank frames: 2 (0.5%)\n// Memory growth: +2.1MB\n// Network requests: 15 (avg 127ms)\n```\n\n### 7. App Store Screenshot Generation\n\nTransform your test journeys into professional app store screenshots with automatic device framing, localization, and layout optimization:\n\n```dart\n// Mark screenshot points during journey recording\nawait VoyageCapture.appStoreScreenshot(\n  name: 'premium_features',\n  title: 'Unlock Premium Features',\n  subtitle: 'Get the most out of your app',\n  priority: 100,  // Higher priority appears first in app stores\n  category: AppStoreCategory.mainFeature,\n);\n\n// Generate screenshots for all app stores\nfinal result = await VoyageAppStore.generateScreenshots(\n  journey: 'complete_user_flow',\n  config: AppStoreGenerationConfig(\n    platforms: [AppStorePlatform.iOS, AppStorePlatform.android],\n    addDeviceFrames: true,  // Adds realistic device bezels\n    statusBar: StatusBarStyles.promotional,  // Perfect status bar\n    locales: [Locale('en'), Locale('de'), Locale('ja')],  // Multi-language\n    devices: AppStoreDevices.required,  // All required sizes\n    exportPath: 'app_store_assets/',\n  ),\n);\n\nprint('Generated ${result.totalGenerated} screenshots');\n// Output structure:\n// app_store_assets/\n//   ios/\n//     en/\n//       01_mainfeature_iphone14promax.png\n//       02_premium_iphone14promax.png\n//     de/\n//       01_mainfeature_iphone14promax.png\n//   android/\n//     en/\n//       01_mainfeature_pixel6.png\n```\n\n**App Store Features**:\n- **Device Frames**: Automatic device frame rendering with accurate bezels and notches\n- **Multi-locale Support**: Generate screenshots in all your supported languages\n- **Smart Organization**: Exports organized by platform, locale, and priority\n- **Status Bar Customization**: Professional status bars with full battery and signal\n- **Batch Generation**: Generate hundreds of screenshots from a single journey\n\n### Test-Driven App Store Screenshots\n\nTake app store screenshot generation to the next level with Flutter test integration. Instead of relying on manual recordings, generate perfect screenshots programmatically in your test environment:\n\n```dart\nimport 'package:moinsen_voyage/moinsen_voyage_test.dart';\n\ntestWidgets('generate app store screenshots', (tester) async {\n  await tester.pumpWidget(MyApp());\n  \n  // Capture individual screenshots during your test\n  await tester.captureAppStoreScreenshot(\n    name: 'welcome_screen',\n    title: 'Welcome to My App',\n    subtitle: 'Get started in seconds',\n    category: AppStoreCategory.onboarding,\n  );\n  \n  // Navigate through your app\n  await tester.tap(find.text('Get Started'));\n  await tester.pumpAndSettle();\n  \n  // Capture another screenshot\n  await tester.captureAppStoreScreenshot(\n    name: 'main_features',\n    title: 'Powerful Features',\n    subtitle: 'Everything you need in one place',\n    category: AppStoreCategory.mainFeature,\n  );\n  \n  // Or replay entire journeys with automatic screenshot generation\n  final result = await tester.replayJourneyWithScreenshots(\n    journeyId: 'user_onboarding_flow',\n    devices: [DeviceConfiguration.iPhone14ProMax, DeviceConfiguration.pixel6],\n    locales: [Locale('en'), Locale('de'), Locale('fr')],\n    config: TestAppStoreConfig.complete(),\n  );\n  \n  expect(result.success, isTrue);\n  expect(result.screenshots.length, greaterThan(6)); // 2 devices × 3 locales\n});\n```\n\n**Test-Driven Benefits**:\n- **Perfect Timing**: Screenshots captured at exactly the right moment\n- **Consistent Quality**: No manual timing or coordination issues\n- **Automation Ready**: Generate screenshots in CI/CD pipelines\n- **Multi-Configuration**: Test multiple devices, locales, and scenarios automatically\n- **Version Control**: Track screenshot changes alongside code changes\n## 🚀 Getting Started\n\n### Installation\n\nAdding Moinsen Voyage to your Flutter project is straightforward. Add the dependency to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n  moinsen_voyage: ^0.1.0\n```\n\nRun `flutter pub get` to fetch the package.\n\n### Setup in 30 Seconds\n\nGetting started with Moinsen Voyage is incredibly simple. Just wrap your app with `VoyageWrapper`:\n\n```dart\nimport 'package:flutter/foundation.dart';\nimport 'package:moinsen_voyage/moinsen_voyage.dart';\n\nvoid main() {\n  // Initialize Voyage (debug/profile only)\n  VoyageConfig.initialize(\n    enabled: !kReleaseMode,\n    captureExceptions: true,    // Handle exceptions gracefully\n    throttleTapEvents: true,    // Handle rapid interactions\n  );\n\n  runApp(\n    VoyageWrapper(  // This gives you everything!\n      child: MyApp(),\n    ),\n  );\n}\n```\n\nThat's it! Your app now has:\n- ✅ Journey recording capabilities\n- ✅ Exception handling and recovery\n- ✅ Built-in UI for managing recordings\n- ✅ Debug badge for quick access\n- ✅ Test-driven replay functionality\n\n\u003e **Architecture Note**: Moinsen Voyage maintains a clean separation between recording (available in debug/profile builds) and replay (test-only). This ensures your production app stays lean while your tests remain comprehensive.\n\n### Record Your First Journey\n\nStart recording by wrapping key screens with `VoyageRecordable`:\n\n```dart\nclass LoginScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return VoyageRecordable(\n      journeyName: 'user_login',\n      child: Scaffold(\n        appBar: AppBar(title: Text('Login')),\n        body: LoginForm(),\n      ),\n    );\n  }\n}\n```\n\nFor individual widgets, use `VoyageCapture`:\n\n```dart\nVoyageCapture(\n  id: 'login_button',\n  child: ElevatedButton(\n    onPressed: _handleLogin,\n    child: Text('Login'),\n  ),\n)\n```\n\nThe debug badge (visible in debug mode) gives you instant access to:\n- Start/stop recording\n- View recorded journeys\n- Access settings\n- Replay journeys in tests\n\n## 🔧 Enhanced Widget Support\n\nMoinsen Voyage provides specialized widgets optimized for common UI patterns:\n\n### VoyageTextField \u0026 VoyageTextFormField\n\nEnhanced text input widgets with built-in recording and exception handling:\n\n```dart\nVoyageTextField(\n  id: 'search_field',\n  semanticLabel: 'Search products',\n  controller: _controller,\n  decoration: InputDecoration(labelText: 'Search'),\n  // Automatically records text changes and handles exceptions\n)\n\nVoyageTextFormField(\n  id: 'email_field',\n  validator: (value) =\u003e EmailValidator.validate(value),\n  // Captures validation errors as warning-level exceptions\n)\n```\n\n### Advanced VoyageCapture Features\n\n```dart\nVoyageCapture(\n  id: 'complex_widget',\n\n  // Exception handling\n  captureExceptions: true,\n  errorBuilder: (exception) =\u003e ErrorWidget(exception),\n\n  // Interaction filtering\n  captureTaps: true,\n  captureTextInput: true,\n  captureScroll: false,\n\n  // Metadata for context\n  metadata: {'feature': 'checkout', 'priority': 'critical'},\n\n  child: ComplexWidget(),\n)\n```\n\n### Test-Only Replay\n\nVoyage replay functionality is designed exclusively for use in Flutter tests, ensuring clean separation between recording (production) and replay (testing):\n\n```dart\n// In your test files\nimport 'package:moinsen_voyage/moinsen_voyage_test.dart';\n\ntestWidgets('replay user journey', (tester) async {\n  await tester.pumpWidget(MyApp());\n  \n  // Simple replay using extension method\n  await tester.replayJourney('journey_id');\n  \n  // Or use the test driver for more control\n  final driver = VoyageTestDriver(tester: tester);\n  await driver.replayJourney('journey_id', \n    speedMultiplier: 10.0,\n    onError: (error) =\u003e debugPrint('Error: $error'),\n  );\n});\n```\n\n## 🔍 Exception Analysis \u0026 Quality Assessment\n\nMoinsen Voyage includes sophisticated tools for analyzing exceptions and assessing journey quality:\n\n### Automated Exception Analysis\n\n```dart\n// Analyze a journey for exception patterns\nfinal analysis = JourneyExceptionAnalyzer.instance.analyzeJourney(journey);\n\nprint('Quality Level: ${analysis.qualityLevel}');  // excellent, good, fair, poor, critical\nprint('Passed: ${analysis.passed}');               // true/false\nprint('Issues Found: ${analysis.issues.length}');\n\n// Get actionable recommendations\nfinal recommendations = JourneyExceptionAnalyzer.instance\n    .generateRecommendations(analysis);\n\nfor (final rec in recommendations) {\n  print('📋 $rec');\n}\n// Output: 🚨 URGENT: Fix critical exceptions immediately\n//         🛠️ Focus on error-prone widgets: login_form, checkout_button\n```\n\n### Exception Quality Rules\n\nConfigure analysis rules for different environments:\n\n```dart\n// Strict rules for production\nfinal strictRules = ExceptionAnalysisRules.strict();\n\n// Standard rules for development\nfinal standardRules = ExceptionAnalysisRules.standard();\n\n// Lenient rules for experimental features\nfinal lenientRules = ExceptionAnalysisRules.lenient();\n\nfinal analysis = JourneyExceptionAnalyzer.instance.analyzeJourney(\n  journey,\n  rules: strictRules,\n);\n```\n\n### Trend Analysis\n\nTrack exception trends across multiple journeys:\n\n```dart\nfinal journeys = await JourneyManager.loadRecentJourneys(30);\nfinal trends = JourneyExceptionAnalyzer.instance.analyzeTrends(journeys);\n\nprint('Trend: ${trends.trend}');  // improving, stable, worsening\nprint('Description: ${trends.trendDescription}');\n```\n\n### Your First Recording\n\nNow comes the exciting part - recording your first journey. Let's walk through a complete example that demonstrates the core concepts:\n\n```dart\n// Wrap your screen with VoyageRecordable\nclass LoginScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return VoyageRecordable(\n      journeyName: 'user_login_flow',\n      metadata: {\n        'feature': 'authentication',\n        'priority': 'critical',\n      },\n      child: Scaffold(\n        appBar: AppBar(title: Text('Login')),\n        body: LoginForm(),\n      ),\n    );\n  }\n}\n\n// For individual widgets, use VoyageCapture\nclass LoginForm extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      children: [\n        VoyageCapture(\n          id: 'email_field',\n          semantic: 'user_email_input',\n          child: TextField(\n            decoration: InputDecoration(labelText: 'Email'),\n          ),\n        ),\n        VoyageCapture(\n          id: 'password_field',\n          semantic: 'user_password_input',\n          child: TextField(\n            decoration: InputDecoration(labelText: 'Password'),\n            obscureText: true,\n          ),\n        ),\n        VoyageCapture(\n          id: 'login_button',\n          semantic: 'submit_login',\n          child: ElevatedButton(\n            onPressed: () =\u003e _performLogin(context),\n            child: Text('Login'),\n          ),\n        ),\n      ],\n    );\n  }\n}\n```\n\nNotice how each `VoyageCapture` has both an `id` and a `semantic` property. The `id` helps Voyage find the widget during replay, while `semantic` describes what the widget does in user terms. This dual identification makes your tests both reliable and readable.\n### Recording Your Journey\n\nOnce your widgets are wrapped, recording happens automatically when you use your app. But let's understand what's happening behind the scenes, as this knowledge will help you create better tests.\n\nWhen you interact with a VoyageRecordable widget, the framework creates a timeline of events. Think of it like a ship's log, but instead of \"10:00 - Spotted land\", it records \"10:00:00.123 - User tapped login_button while app was in state: not_authenticated\". Here's how to control and monitor your recordings:\n\n```dart\n// Start a new recording session programmatically\nVoyageRecorder.startSession(\n  name: 'complete_purchase_flow',\n  description: 'Happy path from product browse to order confirmation',\n  tags: ['e-commerce', 'critical-path', 'payment'],\n);\n\n// Your manual interactions are now being recorded...\n\n// You can add contextual checkpoints\nVoyageRecorder.addCheckpoint('cart_loaded', {\n  'item_count': 3,\n  'total_value': 156.99,\n  'currency': 'EUR',\n});\n\n// End the recording\nawait VoyageRecorder.endSession();\n```\n\nThe recording captures multiple layers of information simultaneously. At the interaction layer, it records every tap, swipe, and text entry. At the state layer, it captures how your providers, blocs, or hooks change in response. At the visual layer, it takes intelligent snapshots at key moments. And at the performance layer, it monitors frame times and memory usage.\n\n### Replaying Journeys in Tests\n\nNow comes the powerful part - turning your recordings into comprehensive tests. The beauty of Moinsen Voyage is that one recording can generate dozens of test scenarios. Let's explore how this works:\n\n```dart\nimport 'package:moinsen_voyage/moinsen_voyage_test.dart';\n\nvoid main() {\n  group('User Authentication Flows', () {\n    testVoyage('Login works with valid credentials', (voyage) async {\n      // Load the recorded journey\n      final journey = await voyage.loadJourney('user_login_flow');\n\n      // Replay with original data - this should always pass\n      final result = await journey.replay();\n      expect(result.success, isTrue);\n      expect(result.finalRoute, equals('/home'));\n    });\n\n    testVoyage('Login handles various user scenarios', (voyage) async {\n      final journey = await voyage.loadJourney('user_login_flow');\n\n      // Here's where the magic happens - test variations\n      final results = await journey.replayWithVariations([\n        // Test with different locales\n        VoyageVariation.locale(['de_DE', 'fr_FR', 'ja_JP']),\n\n        // Test with edge case data\n        VoyageVariation.textInput(\n          'email_field',\n          ['test@example.com', 'user+tag@domain.com', 'véry.ünusual@email.com'],\n        ),\n\n        // Test with network conditions\n        VoyageVariation.network([\n          NetworkCondition.fast3G(),\n          NetworkCondition.slow3G(),\n          NetworkCondition.offline(),\n        ]),\n\n        // Test with different device sizes\n        VoyageVariation.deviceSize([\n          DeviceSize.iPhone13Mini(),\n          DeviceSize.iPadPro(),\n          DeviceSize.pixel5(),\n        ]),\n      ]);\n\n      // Analyze results across all variations\n      expect(results.where((r) =\u003e r.success).length,\n             greaterThan(results.length * 0.9),\n             reason: 'At least 90% of variations should succeed');\n    });\n  });\n}\n```\n\nNotice how one recording becomes a comprehensive test suite. The framework intelligently combines variations, so if you specify 3 locales, 3 email formats, 3 network conditions, and 3 device sizes, you're actually running 81 different test scenarios - all from one recording!\n## 🎓 Understanding State Management Integration\n\nOne of the most challenging aspects of testing Flutter applications is dealing with state management. Each state management solution has its own patterns, and traditionally, this means learning different testing approaches for each one. Moinsen Voyage changes this by providing a unified abstraction layer that understands the unique characteristics of each state management approach while presenting a consistent testing interface.\n\nLet's explore how this works by examining each supported state management solution in detail.\n\n### Riverpod Integration\n\nRiverpod's power lies in its compile-safe dependency injection and reactive state management. When testing Riverpod applications, the challenge is often in setting up the right provider overrides and ensuring state consistency. Moinsen Voyage handles this complexity for you:\n\n```dart\n// First, let's see how to make your Riverpod app voyage-aware\nclass MyApp extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    return VoyageProviderScope(  // Wrap your ProviderScope\n      child: ProviderScope(\n        child: MaterialApp(\n          home: HomeScreen(),\n        ),\n      ),\n    );\n  }\n}\n\n// In your widgets, use VoyageConsumer instead of Consumer\nclass ProductListScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return VoyageConsumer(\n      voyageId: 'product_list',\n      builder: (context, ref, child) {\n        final products = ref.watch(productsProvider);\n        final cartItemCount = ref.watch(cartProvider.select((cart) =\u003e cart.items.length));\n\n        // Voyage automatically records:\n        // 1. Which providers are being watched\n        // 2. Their values at each render\n        // 3. What triggers re-renders\n\n        return ListView.builder(\n          itemCount: products.length,\n          itemBuilder: (context, index) =\u003e ProductTile(products[index]),\n        );\n      },\n    );\n  }\n}\n\n// During replay, Voyage can inject different provider states\ntestVoyage('Products load correctly in various states', (voyage) async {\n  final journey = await voyage.loadJourney('browse_products');\n\n  await journey.replayWithProviderStates([\n    // Test with empty product list\n    ProviderState(productsProvider, []),\n\n    // Test with single product\n    ProviderState(productsProvider, [testProduct]),\n\n    // Test with many products (pagination)\n    ProviderState(productsProvider, generateProducts(100)),\n\n    // Test with error state\n    ProviderState(productsProvider, AsyncError('Network error')),\n  ]);\n});\n```\n\nThe key insight here is that Voyage doesn't just record what providers were accessed, but also captures the entire dependency graph. If `productsProvider` depends on `userProvider` and `settingsProvider`, Voyage records those relationships, ensuring your tests accurately reflect the real behavior of your app.\n\n### Bloc Integration\n\nBloc's event-driven architecture presents different testing challenges. With Bloc, you need to ensure that the right events produce the right states in the right order. Moinsen Voyage embraces Bloc's patterns while simplifying the testing process:\n\n```dart\n// Make your Bloc widgets voyage-aware\nclass CartScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return VoyageBlocBuilder\u003cCartBloc, CartState\u003e(\n      voyageId: 'shopping_cart',\n      builder: (context, state) {\n        // Voyage records:\n        // 1. The sequence of events dispatched\n        // 2. The resulting states\n        // 3. The timing between events and state changes\n\n        if (state is CartLoading) {\n          return CircularProgressIndicator();\n        } else if (state is CartLoaded) {\n          return CartItemsList(items: state.items);\n        } else if (state is CartError) {\n          return ErrorMessage(state.message);\n        }\n        return EmptyCart();\n      },\n    );\n  }\n}\n\n// During testing, replay bloc event sequences\ntestVoyage('Cart behaves correctly with various event sequences', (voyage) async {\n  final journey = await voyage.loadJourney('shopping_cart_flow');\n\n  // Test different event sequences\n  await journey.replayWithBlocEvents\u003cCartBloc\u003e([\n    // Normal flow\n    [AddToCart(productId: '123'), RemoveFromCart(productId: '123')],\n\n    // Rapid additions\n    List.generate(10, (i) =\u003e AddToCart(productId: 'product_$i')),\n\n    // Error scenarios\n    [AddToCart(productId: 'invalid'), ClearCart()],\n  ]);\n});\n```\n\nWhat makes this powerful is that Voyage understands Bloc's async nature. It knows that events don't immediately produce states, and it captures the complete timeline of when events are dispatched and when states change in response.\n### Flutter Hooks Integration\n\nFlutter Hooks brings functional programming patterns to widget state management. The challenge with testing hooks is that they encapsulate state in a way that's typically invisible to the outside world. Moinsen Voyage provides specialized hooks that maintain the same API while adding recording capabilities:\n\n```dart\n// Use Voyage-aware hooks in your widgets\nclass SearchScreen extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    // These hooks work exactly like regular hooks but with recording capabilities\n    final searchQuery = useVoyageState\u003cString\u003e('', stateId: 'search_query');\n    final searchResults = useVoyageFuture(\n      searchApi(searchQuery.value),\n      futureId: 'search_results',\n    );\n    final isLoading = useVoyageState\u003cbool\u003e(false, stateId: 'loading_state');\n\n    return Column(\n      children: [\n        VoyageCapture(\n          id: 'search_input',\n          child: TextField(\n            onChanged: (value) {\n              searchQuery.value = value;\n              isLoading.value = true;\n            },\n            decoration: InputDecoration(\n              labelText: 'Search products',\n              suffixIcon: isLoading.value ? CircularProgressIndicator() : null,\n            ),\n          ),\n        ),\n        Expanded(\n          child: searchResults.when(\n            data: (results) =\u003e ResultsList(results),\n            loading: () =\u003e LoadingIndicator(),\n            error: (error, stack) =\u003e ErrorDisplay(error),\n          ),\n        ),\n      ],\n    );\n  }\n}\n```\n\nThe beauty of this approach is that hook state, which is normally ephemeral and local to the widget, becomes observable and replayable without changing how you write your components.\n\n## 🔬 Advanced Features\n\nNow that we've covered the basics, let's explore some of the more sophisticated capabilities that make Moinsen Voyage a comprehensive testing solution. These features address real-world testing challenges that teams face when building production Flutter applications.\n\n### Intelligent Test Data Generation\n\nReal applications deal with real data, and testing with oversimplified data often misses critical edge cases. Moinsen Voyage includes a sophisticated data generation system that understands your domain and generates realistic test scenarios. Let's explore how this works:\n\n```dart\n// Define your domain models with generation hints\n@VoyageDataModel()\nclass User {\n  final String id;\n  final String name;\n  final String email;\n  final DateTime birthDate;\n  final Address address;\n  final List\u003cPaymentMethod\u003e paymentMethods;\n\n  User({\n    required this.id,\n    required this.name,\n    required this.email,\n    required this.birthDate,\n    required this.address,\n    required this.paymentMethods,\n  });\n}\n\n// Generate test data that makes sense\nfinal testUsers = await VoyageDataDock.generateUsers(\n  count: 100,\n  constraints: UserConstraints(\n    locales: ['de_DE', 'en_US', 'fr_FR'],\n    ageRange: AgeRange(18, 65),\n    includeEdgeCases: true,  // Very old, very young, special characters in names\n  ),\n);\n\n// The generator understands relationships\nfinal orders = await VoyageDataDock.generateOrderHistory(\n  forUsers: testUsers,\n  ordersPerUser: Range(0, 20),  // Some users have no orders\n  dateRange: LastDays(365),\n  productCategories: ['Electronics', 'Books', 'Clothing'],\n);\n\n// Use in your tests to ensure comprehensive coverage\ntestVoyage('Checkout works for diverse user profiles', (voyage) async {\n  final journey = await voyage.loadJourney('checkout_flow');\n\n  for (final user in testUsers.take(10)) {  // Test with 10 random users\n    await journey.replayWithContext(\n      user: user,\n      cart: VoyageDataDock.generateCart(forUser: user),\n    );\n  }\n});\n```\n\nWhat makes this powerful is that the data generator understands context. German users get German addresses with proper postal codes. Email addresses follow realistic patterns. Credit card numbers pass validation but are clearly test data. This attention to detail catches bugs that simple test data would miss.\n\n### Performance Monitoring and Baselines\n\nPerformance is a feature, not an afterthought. Moinsen Voyage treats performance testing as a first-class citizen, automatically collecting metrics during every test run. Here's how to leverage this for maintaining app performance:\n\n```dart\n// Set performance baselines for critical journeys\nawait VoyagePerformance.setBaseline(\n  journeyName: 'app_startup',\n  metrics: PerformanceMetrics(\n    maxStartupTime: Duration(seconds: 2),\n    maxMemoryGrowth: 50 * 1024 * 1024,  // 50MB\n    maxJankFrames: 5,\n    minFPS: 55,\n  ),\n);\n\n// During testing, performance is automatically monitored\ntestVoyage('App maintains performance standards', (voyage) async {\n  final journey = await voyage.loadJourney('app_startup');\n  final result = await journey.replay();\n\n  // Voyage automatically compares against baselines\n  expect(result.performance.meetsBaseline, isTrue);\n\n  // Get detailed performance insights\n  print('Startup time: ${result.performance.startupTime}');\n  print('Memory usage: ${result.performance.peakMemory}');\n  print('Frame performance: ${result.performance.frameStats}');\n\n  // Generate performance report\n  await voyage.generatePerformanceReport(\n    outputPath: 'reports/performance.html',\n    includeGraphs: true,\n    compareWithPrevious: 5,  // Compare with last 5 runs\n  );\n});\n```\n### Golden Testing in Practice\n\nOur golden testing system uses perceptual comparison to understand visual changes the way humans do. This eliminates false positives while catching real issues:\n\n```dart\n// Configure golden testing with perceptual understanding\nfinal goldenConfig = GoldenConfig(\n  enabled: true,\n  captureStrategy: CaptureStrategy.smart,  // Captures at optimal moments\n  comparisonConfig: GoldenComparisonConfig(\n    algorithm: ComparisonAlgorithm.ssim,    // Perceptual comparison\n    tolerance: GoldenTolerance(\n      global: 0.01,                         // 1% global difference allowed\n      regions: {\n        'dynamic_content': 0.05,            // 5% for changing content\n        'animations': 0.10,                 // 10% for animated areas\n      },\n    ),\n    ignoreBoundingBoxes: [\n      'timestamp', 'loading_indicator',     // Ignore specific areas\n    ],\n  ),\n  // Multi-device and theme variations\n  deviceVariations: [\n    DeviceConfiguration.iPhone14ProMax,\n    DeviceConfiguration.pixel6,\n  ],\n  themeVariations: [ThemeMode.light, ThemeMode.dark],\n  localeVariations: ['en', 'de', 'ja'],\n);\n\n// Run golden tests with approval workflow\ntestVoyage('UI remains visually consistent', (voyage) async {\n  final journey = await voyage.loadJourney('critical_user_flows');\n  \n  // Run golden tests across all variations\n  final result = await GoldenTestRunner.runGoldenTests(\n    journey,\n    config: goldenConfig,\n  );\n  \n  if (!result.passed) {\n    // Review and approve changes interactively\n    final approval = await GoldenApprovalWorkflow.reviewPendingUpdates(\n      result.failures,\n      showSideBySide: true,\n      highlightDifferences: true,\n    );\n    \n    if (approval.approved) {\n      await GoldenStorage.instance.promoteToBaseline(approval.updates);\n      print('Golden images updated successfully');\n    }\n  }\n});\n```\n\nThe system uses SSIM (Structural Similarity Index) to compare images the way humans perceive them. A slight color shift that's imperceptible to users won't fail your tests, but a button moving to a different location will be caught immediately.\n\n## 🌍 Real-World Example: E-Commerce Checkout\n\nLet's put it all together with a comprehensive example that shows how Moinsen Voyage transforms testing for a complex feature like e-commerce checkout. This example demonstrates how the various features work together to provide comprehensive test coverage:\n\n```dart\n// First, set up your checkout screen with voyage awareness\nclass CheckoutScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final cart = ref.watch(cartProvider);\n    final user = ref.watch(currentUserProvider);\n    final paymentMethod = ref.watch(selectedPaymentProvider);\n\n    return VoyageRecordable(\n      journeyName: 'checkout_flow',\n      metadata: {\n        'feature': 'ecommerce',\n        'criticalPath': true,\n        'estimatedDuration': '2-3 minutes',\n      },\n      child: Scaffold(\n        appBar: AppBar(title: Text('Checkout')),\n        body: CheckoutFlow(\n          cart: cart,\n          user: user,\n          paymentMethod: paymentMethod,\n        ),\n      ),\n    );\n  }\n}\n\n// Now, create a comprehensive test suite from a single recording\nvoid main() {\n  group('E-Commerce Checkout Journey', () {\n    setUpAll(() async {\n      // Define performance baselines\n      await VoyagePerformance.setBaseline(\n        'checkout_flow',\n        maxDuration: Duration(seconds: 3),\n        maxMemoryGrowth: 20 * 1024 * 1024,\n      );\n    });\n\n    testVoyage('Checkout works across all scenarios', (voyage) async {\n      final journey = await voyage.loadJourney('checkout_flow');\n\n      // Generate comprehensive test scenarios\n      final scenarios = await VoyageScenarioGenerator.generate(\n        journey: journey,\n        dimensions: [\n          // User dimensions\n          TestDimension.users([\n            UserType.guest,\n            UserType.firstTime,\n            UserType.returning,\n            UserType.premium,\n          ]),\n\n          // Cart dimensions\n          TestDimension.cartSizes([1, 5, 20, 100]),  // Edge case: large cart\n          TestDimension.productTypes([\n            'physical',\n            'digital',\n            'mixed',\n            'subscription',\n          ]),\n\n          // Payment dimensions\n          TestDimension.paymentMethods([\n            'credit_card',\n            'paypal',\n            'bank_transfer',\n            'crypto',  // Edge case\n          ]),\n\n          // Environmental dimensions\n          TestDimension.locales(['de_DE', 'en_US', 'ja_JP']),\n          TestDimension.devices([\n            DeviceProfile.iPhone13,\n            DeviceProfile.iPadPro,\n            DeviceProfile.pixel5,\n          ]),\n          TestDimension.networkConditions([\n            NetworkProfile.wifi,\n            NetworkProfile.mobile3G,\n            NetworkProfile.flaky,  // Intermittent connection\n          ]),\n        ],\n      );\n\n      // Run all scenarios with intelligent batching\n      final results = await voyage.runScenarios(\n        scenarios,\n        parallel: 4,  // Run 4 scenarios simultaneously\n        stopOnFailure: false,  // Continue even if some fail\n        generateReport: true,\n      );\n\n      // Analyze results\n      expect(results.successRate, greaterThan(0.95),\n        reason: 'At least 95% of scenarios should succeed');\n\n      // Check performance didn't degrade\n      expect(results.performanceRegressions, isEmpty);\n\n      // Ensure golden tests pass for critical UI elements\n      expect(results.goldenTestFailures, isEmpty);\n\n      // Generate comprehensive report\n      await voyage.generateReport(\n        'reports/checkout_analysis.html',\n        includeSuccesses: false,  // Focus on failures\n        includePerformanceGraphs: true,\n        includeVisualDiffs: true,\n        groupBy: ReportGrouping.byFailureType,\n      );\n    });\n  });\n}\n```\n\nThis example showcases how one manual test recording becomes hundreds of automated test scenarios, each checking different aspects of your application. The framework handles the complexity of combining dimensions, managing test data, and analyzing results, letting you focus on what matters: ensuring your app works flawlessly for all users.\n\n## 🛠️ Troubleshooting\n\n### Recording Not Starting\n\nIf your journeys aren't being recorded, check these common causes:\n\n```dart\n// Ensure Voyage is initialized and enabled\nvoid main() {\n  VoyageConfig.initialize(\n    enabled: !kReleaseMode,  // Must be true for recording\n    captureExceptions: true,\n  );\n\n  runApp(VoyageWrapper(child: MyApp()));  // VoyageWrapper required\n}\n\n// Debug recording status\nprint('Voyage enabled: ${VoyageConfig.isEnabled}');\nprint('Exception capture: ${VoyageConfig.captureExceptions}');\n```\n\n### Fast Interactions Not Captured\n\nIf rapid taps aren't being recorded:\n\n```dart\nVoyageConfig.initialize(\n  throttleTapEvents: false,  // Disable if you need every single tap\n  tapThrottleDurationMs: 100,  // Reduce throttle duration\n);\n```\n\n### Exceptions Not Being Handled\n\nEnsure exception handling is enabled:\n\n```dart\n// Global configuration\nVoyageConfig.initialize(captureExceptions: true);\n\n// Per-widget override\nVoyageCapture(\n  captureExceptions: true,  // Override global setting\n  child: YourWidget(),\n)\n```\n\n### UI Not Appearing\n\nIf the Voyage UI (badge, dashboard) doesn't appear:\n\n```dart\n// Ensure you're using VoyageWrapper\nrunApp(VoyageWrapper(child: MyApp()));\n\n// And running in debug mode\nassert(() {\n  print('Debug mode confirmed');\n  return true;\n}());\n```\n\n### Performance Issues\n\nIf you experience performance problems:\n\n```dart\nVoyageConfig.initialize(\n  throttleTapEvents: true,     // Reduce event frequency\n  captureExceptions: false,    // Disable if not needed\n);\n```\n\n## 🤝 Contributing\n\nWe believe that great testing tools are built by the community, for the community. Moinsen Voyage is designed to be extensible, and we welcome contributions that make Flutter testing better for everyone.\n\n### How to Contribute\n\nThe best contributions come from real-world usage. If you're using Moinsen Voyage and encounter a scenario it doesn't handle well, that's valuable feedback. Here's how you can help:\n\n1. **Report Issues**: Found a bug? Let us know! Please include a minimal reproduction case.\n2. **Suggest Features**: Have an idea that would make testing easier? Open a discussion!\n3. **Submit PRs**: Fixed a bug or added a feature? We'd love to review your code.\n4. **Write Documentation**: Help others by improving our docs or writing tutorials.\n5. **Create Plugins**: Extend Voyage for your specific needs and share with the community.\n\n### Development Setup\n\nTo contribute to Moinsen Voyage, clone the repository and set up your development environment:\n\n```bash\ngit clone https://github.com/moinsen/moinsen_voyage.git\ncd moinsen_voyage\nflutter pub get\nflutter test  # Run the test suite\n```\n\nSee our [Implementation Guidelines](docs/IMPLEMENTATION_GUIDELINES.md) for detailed development practices.\n\n## 📜 License\n\nMoinsen Voyage is released under the MIT License. See the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\nMoinsen Voyage stands on the shoulders of giants. We're grateful to the Flutter team for creating such an amazing framework, and to the broader Flutter community for pushing the boundaries of what's possible in mobile development.\n\nSpecial thanks to the teams behind Riverpod, Bloc, and Flutter Hooks for creating state management solutions that make Flutter development a joy.\n\n---\n\n**Ready to transform your Flutter testing?** Start your voyage today and discover how natural testing can be when your tools understand your application as well as you do.\n\n_Moinsen!_ 👋","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fmoinsen_voyage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoinsen-dev%2Fmoinsen_voyage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoinsen-dev%2Fmoinsen_voyage/lists"}