pickle_parser 1.1.2
pickle_parser: ^1.1.2 copied to clipboard
A Flutter package for parsing pickle files and executing Cucumber steps in widget tests.
Pickle Parser #
A comprehensive Flutter package for parsing pickle/Gherkin files and executing Cucumber steps in widget tests. This package allows you to write human-readable test scenarios using Gherkin syntax and automatically execute them as Flutter widget tests.
📋 Table of Contents #
- Overview
- Features
- Installation
- Dependencies
- Quick Start
- Supported Gherkin Syntax
- Element Selectors
- Custom Steps
- Complete Step Reference
- Usage Examples
- Limitations
- Best Practices
- Troubleshooting
- Contributing
🔍 Overview #
The Pickle Parser package bridges the gap between business-readable Gherkin scenarios and Flutter widget tests. It automatically parses pickle/feature files and executes corresponding test actions, making it easier to maintain test scenarios and improve collaboration between developers and non-technical stakeholders.
✨ Features #
- 🥒 Full Gherkin Support: Parse and execute Given/When/Then steps
- 🎯 Multiple Element Selectors: Find widgets by key, type, text, or icon
- 🔍 Advanced Text Matching: Supports exact, contains, startsWith, endsWith, and regex patterns
- 💫 Rich Interactions: Tap, long press, double tap, swipe, scroll, and keyboard input
- ⌨️ Text Input: Support for various text field interactions
- 🕒 Wait Strategies: Time-based waits and conditional waiting
- 📱 Navigation Support: Back navigation and dialog dismissal
- 🎨 Gesture Support: Swipe, scroll, refresh, and custom gestures
- 📖 Clear Error Messages: Detailed logging and error reporting
- 🛠️ CLI Tools: Validate feature files and generate test skeletons
- 🔧 Custom Steps: Register your own step implementations for app-specific actions
📦 Installation #
Add pickle_parser
to your pubspec.yaml
file:
dev_dependencies:
flutter_test:
sdk: flutter
pickle_parser: ^1.1.0
Then run:
flutter pub get
🛠️ CLI Tools #
The package includes powerful command-line tools for validating feature files and generating test skeletons, making it easier to maintain and debug your Gherkin scenarios.
Installation #
The CLI tools are included with the package. After installing pickle_parser
, you can run them directly:
# Run from your project directory
dart run pickle_parser:cli --help
Or run the tool file directly:
# Navigate to the package location and run
dart tool/cli/bin/pickle_parser_cli.dart --help
CLI Commands #
Validate Feature Files
# Validate all feature files in default directory (assets/features)
dart run pickle_parser:cli --validate
# Validate with verbose output
dart run pickle_parser:cli --validate --verbose
# Validate specific directory
dart run pickle_parser:cli --validate --input assets/features
Generate Test Skeletons
# Generate test files from feature files
dart run pickle_parser:cli --generate
# Specify input and output directories
dart run pickle_parser:cli --generate --input assets/features --output test/integration
# Generate with verbose output
dart run pickle_parser:cli --generate --verbose
Combined Operations
# Validate and generate in one command
dart run pickle_parser:cli --validate --generate --verbose
# Full workflow with custom directories
dart run pickle_parser:cli --validate --generate --input assets/scenarios --output test/generated --verbose
CLI Features #
- ✅ Feature File Validation: Checks syntax and supported step patterns
- 🏗️ Test Skeleton Generation: Creates ready-to-use test files
- 📊 Detailed Reporting: Shows validation results and statistics
- 🎯 Smart Error Detection: Identifies unsupported steps and syntax issues
- 📁 Batch Processing: Handles multiple feature files at once
- 🔍 Step Pattern Recognition: Validates against all supported Gherkin patterns
- 📋 Summary Statistics: Reports total files, steps, and validation status
CLI Usage Examples #
Basic Validation
$ dart run pickle_parser:cli --validate
🔍 Validating feature files in: assets/features
📁 Found 3 feature file(s)
📊 Validation Summary:
✅ Valid files: 3/3
📋 Total steps: 45
🎉 All feature files are valid!
Validation with Errors
$ dart run pickle_parser:cli --validate --verbose
🔍 Validating feature files in: assets/features
📁 Found 2 feature file(s)
📄 Validating: login.feature
✅ Valid (12 steps)
📄 Validating: checkout.feature
❌ Invalid:
- Line 15: Unsupported step - "I click the mysterious button"
- Line 23: Missing Feature declaration
📊 Validation Summary:
✅ Valid files: 1/2
📋 Total steps: 12
❌ Errors found: 2
Test Generation
$ dart run pickle_parser:cli --generate --verbose
🏗️ Generating test skeletons from: assets/features to: test/generated
📁 Created output directory: test/generated
✅ Generated: login_test.dart
✅ Generated: checkout_test.dart
📊 Generation Summary:
✅ Generated 2 test file(s)
📁 Output directory: test/generated
💡 Next steps:
1. Review generated test files
2. Add imports for your app widgets
3. Customize the test setup as needed
4. Run: flutter test test/generated
🔗 Dependencies #
Required Dependencies #
- Flutter SDK:
>=2.19.5 <4.0.0
- flutter: Core Flutter framework
- flutter_test: Flutter testing framework
What's Included #
The package automatically provides:
- Gherkin step parsing and execution
- Widget finding capabilities
- Gesture simulation
- Text input handling
- Wait and timing utilities
Asset Configuration #
To use pickle files, add them to your pubspec.yaml
:
flutter:
assets:
- assets/features/
- test/features/
🚀 Quick Start #
1. Create a Feature File #
Create a .feature
file in your assets folder:
# assets/features/login.feature
Feature: User Login
Scenario: Successful login
When I see Welcome to MyApp
When I enter john@example.com in field with key:email_field
When I enter password123 in field with key:password_field
When I tap key:login_button
Then I see Dashboard
Then I do not see Login Failed
2. Create a Test File #
// test/login_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:pickle_parser/pickle_parser.dart';
import 'package:myapp/main.dart'; // Your app
void main() {
testWidgets('Login flow from pickle file', (WidgetTester tester) async {
// Build your app
await tester.pumpWidget(MyApp());
// Execute the pickle file
await pickleParser(tester, 'assets/features/login.feature');
});
}
📝 Supported Gherkin Syntax #
Keywords #
Given
- Setup steps (currently treated as When steps)When
- Action stepsThen
- Assertion steps#
- Comments (ignored)
Step Structure #
When I [action] [element]
Then I [assertion] [element]
🎯 Element Selectors #
The package supports multiple ways to find widgets:
1. By Key #
When I tap key:submit_button
Then I see key:success_message
2. By Widget Type #
When I tap type:ElevatedButton
Then I see type:Text
Supported Widget Types:
Text
,TextField
,TextFormField
ElevatedButton
,TextButton
,OutlinedButton
Container
,Column
,Row
,Expanded
AppBar
,Scaffold
,Material
Icon
,IconButton
,FloatingActionButton
ListView
,GridView
,Card
Checkbox
,Switch
,Slider
,Radio
DropdownButton
,AlertDialog
,SnackBar
Drawer
,BottomSheet
,Tab
,TabBar
NavigationBar
,BottomNavigationBar
RefreshIndicator
,CupertinoNavigationBar
3. By Text Content #
# Exact match (default)
When I tap Submit
Then I see Welcome!
# Enhanced text matching
When I tap contains:Submit
Then I see startsWith:Welcome
When I see endsWith:Message
Then I see regex:^Error.*occurred$
Text Selector Formats:
text:exact
- Exact text match (explicit)contains:partial
- Text contains substringstartsWith:prefix
- Text starts with prefixendsWith:suffix
- Text ends with suffixregex:pattern
- Text matches regex pattern
4. By Icon #
When I tap icon:add
Then I see icon:check
Supported Icons:
add
,delete
,close
,menu
🔧 Custom Steps #
The package supports registering custom step implementations for app-specific actions. This allows you to extend the built-in step library with your own business logic.
How Custom Steps Work #
- Priority: Custom steps are checked first, before built-in implementations
- Fallback: If a custom step returns
false
or throws an error, built-in steps are tried - Flexibility: Support for exact matching, regex patterns, and templates
Registration Methods #
Exact Text Matching
Register a step handler for exact text matches:
import 'package:pickle_parser/pickle_parser.dart';
// Register before running tests
registerCustomStep(
'I login with default credentials',
(step, tester) async {
await tester.enterText(find.byKey(Key('username')), 'test@example.com');
await tester.enterText(find.byKey(Key('password')), 'password123');
await tester.tap(find.byKey(Key('login_button')));
await tester.pumpAndSettle();
return true; // Step handled successfully
},
);
Pattern Matching with Regex
Register steps that match regex patterns:
registerCustomStepPattern(
RegExp(r'I wait for (\d+) milliseconds'),
(step, tester) async {
final match = RegExp(r'I wait for (\d+) milliseconds').firstMatch(step);
if (match != null) {
final ms = int.parse(match.group(1)!);
await tester.pump(Duration(milliseconds: ms));
return true;
}
return false;
},
);
Template Matching
Register steps using simple templates with placeholders:
registerCustomStepTemplate(
'I wait for {} seconds and then tap {}',
(step, tester) async {
// Parse step manually to extract values
final parts = step.split(' ');
final seconds = int.parse(parts[3]);
final elementKey = parts[7];
await Future.delayed(Duration(seconds: seconds));
await tester.tap(find.byKey(Key(elementKey)));
return true;
},
);
Advanced Custom Steps #
Complex Business Logic
registerCustomStep(
'I complete the checkout process',
(step, tester) async {
// Multi-step business process
await tester.enterText(find.byKey(Key('credit_card')), '4111111111111111');
await tester.enterText(find.byKey(Key('expiry')), '12/25');
await tester.enterText(find.byKey(Key('cvv')), '123');
await tester.tap(find.byKey(Key('submit_payment')));
await tester.pumpAndSettle();
// Verify success
expect(find.text('Payment Successful'), findsOneWidget);
return true;
},
);
Custom Assertions
registerCustomStepPattern(
RegExp(r'I verify that (.+) contains (.+)'),
(step, tester) async {
final match = RegExp(r'I verify that (.+) contains (.+)').firstMatch(step);
if (match != null) {
final elementName = match.group(1)!;
final expectedText = match.group(2)!;
Finder finder = elementName.startsWith('key:')
? find.byKey(Key(elementName.substring(4)))
: find.text(elementName);
final widget = tester.widget(finder);
String actualText = '';
if (widget is Text) {
actualText = widget.data ?? '';
} else if (widget is TextField) {
actualText = (widget as dynamic).controller?.text ?? '';
}
if (!actualText.contains(expectedText)) {
throw TestFailure('Text "$actualText" does not contain "$expectedText"');
}
return true;
}
return false;
},
);
Using the Registry Directly #
For more control, use the CustomStepRegistry
directly:
void setupCustomSteps() {
final registry = CustomStepRegistry();
// Register multiple patterns
registry.registerPattern(RegExp(r'I scroll to (.+)'), myScrollHandler);
registry.registerExact('I perform cleanup', myCleanupHandler);
// Check registration
print('Registered ${registry.handlerCount} custom steps');
// Clear all (useful for testing)
registry.clear();
}
Custom Step Handler Signature #
typedef CustomStepHandler = Future<bool> Function(String step, WidgetTester tester);
Parameters:
step
: The full step text including Gherkin keywordtester
: TheWidgetTester
instance for widget interactions
Return Value:
true
: Step was handled successfullyfalse
: Step couldn't be handled, try built-in stepsException
: Step failed with error
Best Practices #
1. Return false
for Unhandled Cases
registerCustomStepPattern(
RegExp(r'I wait for (\d+) seconds'),
(step, tester) async {
final match = RegExp(r'I wait for (\d+) seconds').firstMatch(step);
if (match == null) {
return false; // Let built-in handlers try
}
final seconds = int.tryParse(match.group(1)!);
if (seconds == null) {
return false; // Invalid format, let others handle
}
await Future.delayed(Duration(seconds: seconds));
return true;
},
);
2. Use Descriptive Step Names
// Good - clear and specific
registerCustomStep('I login as admin user', adminLoginHandler);
registerCustomStep('I verify shopping cart total', cartTotalHandler);
// Avoid - too generic
registerCustomStep('I do something', genericHandler);
3. Setup in Test Suite
void main() {
setUpAll(() {
// Register custom steps before all tests
registerCustomStep('I setup test data', setupTestDataHandler);
registerCustomStep('I cleanup test data', cleanupTestDataHandler);
});
tearDownAll(() {
// Clean up if needed
CustomStepRegistry().clear();
});
testWidgets('My test', (tester) async {
// Use custom steps in feature files or directly
await getCucumberStepTestCode('When I setup test data', tester);
// ... test logic ...
await getCucumberStepTestCode('When I cleanup test data', tester);
});
}
4. Error Handling
registerCustomStep(
'I verify complex state',
(step, tester) async {
try {
// Complex verification logic
await verifyComplexState(tester);
return true;
} catch (e) {
// Log error and let built-in steps try, or rethrow if critical
print('Custom step failed: $e');
return false; // Or rethrow if you want the test to fail
}
},
);
Feature File Examples #
With custom steps registered, you can use them in feature files:
Feature: Shopping Cart
Scenario: Complete purchase
Given I am on the product page
When I add item to cart
And I login with default credentials # Custom step
And I complete the checkout process # Custom step
Then I verify that receipt contains "Success" # Custom step
And I cleanup test data # Custom step
Custom Steps in CLI Validation #
The CLI tool recognizes custom steps when they are registered:
# Run validation with custom steps
dart run pickle_parser:cli --validate
# The validator will check:
# 1. Built-in step patterns
# 2. Registered custom step patterns
# 3. Report any unmatched steps
Note: For CLI validation to recognize custom steps, you need to register them in a setup file that the CLI can access.
☕ Support This Project #
If you find this package helpful and want to support its development, consider buying me a coffee! Your support helps maintain and improve this open-source project.
📄 License #
This project is licensed under the MIT License - see the LICENSE file for details.
Need help? Check the example directory for complete working examples or create an issue on GitHub.