Formatting + Dropdown Widgets

This commit is contained in:
Sarah Jamie Lewis 2023-02-08 14:55:24 -08:00
parent 9b817676e4
commit f20a14f3d8
26 changed files with 100 additions and 133 deletions

View File

@ -130,8 +130,7 @@ abstract class AppDriverAdapter<TNativeAdapter, TFinderType, TWidgetBaseType> {
return Future.microtask( return Future.microtask(
() async { () async {
final completer = Completer<void>(); final completer = Completer<void>();
var maxAttempts = var maxAttempts = (timeout!.inMilliseconds / pollInterval!.inMilliseconds).round();
(timeout!.inMilliseconds / pollInterval!.inMilliseconds).round();
var attempts = 0; var attempts = 0;
while (attempts < maxAttempts) { while (attempts < maxAttempts) {

View File

@ -4,8 +4,7 @@ import 'package:flutter_driver/flutter_driver.dart';
import 'app_driver_adapter.dart'; import 'app_driver_adapter.dart';
class FlutterDriverAppDriverAdapter class FlutterDriverAppDriverAdapter extends AppDriverAdapter<FlutterDriver, SerializableFinder, dynamic> {
extends AppDriverAdapter<FlutterDriver, SerializableFinder, dynamic> {
FlutterDriverAppDriverAdapter(FlutterDriver rawAdapter) : super(rawAdapter); FlutterDriverAppDriverAdapter(FlutterDriver rawAdapter) : super(rawAdapter);
@override @override

View File

@ -4,5 +4,4 @@ import 'package:build/build.dart';
import 'package:flutter_gherkin/src/flutter/code_generation/generators/gherkin_suite_test_generator.dart'; import 'package:flutter_gherkin/src/flutter/code_generation/generators/gherkin_suite_test_generator.dart';
import 'package:source_gen/source_gen.dart'; import 'package:source_gen/source_gen.dart';
Builder gherkinTestSuiteBuilder(BuilderOptions options) => Builder gherkinTestSuiteBuilder(BuilderOptions options) => SharedPartBuilder([GherkinSuiteTestGenerator()], 'gherkin_tests');
SharedPartBuilder([GherkinSuiteTestGenerator()], 'gherkin_tests');

View File

@ -25,8 +25,7 @@ class NoOpReporter extends MessageReporter {
} }
} }
class GherkinSuiteTestGenerator class GherkinSuiteTestGenerator extends GeneratorForAnnotation<GherkinTestSuite> {
extends GeneratorForAnnotation<GherkinTestSuite> {
static const String template = ''' static const String template = '''
class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
_CustomGherkinIntegrationTestRunner({ _CustomGherkinIntegrationTestRunner({
@ -79,21 +78,15 @@ Future<void> executeTestSuite({
_languageService.initialise( _languageService.initialise(
annotation.read('featureDefaultLanguage').literalValue.toString(), annotation.read('featureDefaultLanguage').literalValue.toString(),
); );
final idx = annotation final idx = annotation.read('executionOrder').objectValue.getField('index')!.toIntValue()!;
.read('executionOrder')
.objectValue
.getField('index')!
.toIntValue()!;
final executionOrder = ExecutionOrder.values[idx]; final executionOrder = ExecutionOrder.values[idx];
final featureFiles = annotation final featureFiles = annotation
.read('featurePaths') .read('featurePaths')
.listValue .listValue
.map((path) => Glob(path.toStringValue()!)) .map((path) => Glob(path.toStringValue()!))
.map((glob) => .map((glob) => glob.listSync().map((entity) => File(entity.path)).toList())
glob.listSync().map((entity) => File(entity.path)).toList())
.reduce((value, element) => value..addAll(element)); .reduce((value, element) => value..addAll(element));
final useAbsolutePaths = final useAbsolutePaths = annotation.read('useAbsolutePaths').objectValue.toBoolValue();
annotation.read('useAbsolutePaths').objectValue.toBoolValue();
if (executionOrder == ExecutionOrder.random) { if (executionOrder == ExecutionOrder.random) {
featureFiles.shuffle(); featureFiles.shuffle();
@ -119,10 +112,7 @@ Future<void> executeTestSuite({
} }
} }
return template return template.replaceAll('{{feature_functions}}', featureExecutionFunctionsBuilder.toString()).replaceAll(
.replaceAll('{{feature_functions}}',
featureExecutionFunctionsBuilder.toString())
.replaceAll(
'{{features_to_execute}}', '{{features_to_execute}}',
featuresToExecute.toString(), featuresToExecute.toString(),
); );
@ -333,9 +323,7 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor {
code = _replaceVariable( code = _replaceVariable(
code, code,
'step_table', 'step_table',
table == null table == null ? 'null' : 'GherkinTable.fromJson(\'${_escapeText(table.toJson())}\')',
? 'null'
: 'GherkinTable.fromJson(\'${_escapeText(table.toJson())}\')',
); );
_stepBuffer.writeln(code); _stepBuffer.writeln(code);
@ -380,8 +368,5 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor {
return content.replaceAll('{{$property}}', value ?? 'null'); return content.replaceAll('{{$property}}', value ?? 'null');
} }
String? _escapeText(String? text) => text String? _escapeText(String? text) => text?.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll(r"$", r"\$");
?.replaceAll("\\", "\\\\")
.replaceAll("'", "\\'")
.replaceAll(r"$", r"\$");
} }

View File

@ -142,15 +142,13 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
/// Called after the successful connection of Flutter driver to the running application. Depending on your configuration this /// Called after the successful connection of Flutter driver to the running application. Depending on your configuration this
/// method will be called on each new connection usually before each scenario is run. /// method will be called on each new connection usually before each scenario is run.
final Future<void> Function(FlutterDriver driver)? final Future<void> Function(FlutterDriver driver)? onAfterFlutterDriverConnect;
onAfterFlutterDriverConnect;
void setObservatoryDebuggerUri(String uri) => _observatoryDebuggerUri = uri; void setObservatoryDebuggerUri(String uri) => _observatoryDebuggerUri = uri;
Future<FlutterDriver> createFlutterDriver([String? dartVmServiceUrl]) async { Future<FlutterDriver> createFlutterDriver([String? dartVmServiceUrl]) async {
final completer = Completer<FlutterDriver>(); final completer = Completer<FlutterDriver>();
dartVmServiceUrl = (dartVmServiceUrl ?? _observatoryDebuggerUri) ?? dartVmServiceUrl = (dartVmServiceUrl ?? _observatoryDebuggerUri) ?? Platform.environment['VM_SERVICE_URL'];
Platform.environment['VM_SERVICE_URL'];
await runZonedGuarded( await runZonedGuarded(
() async { () async {
@ -183,9 +181,7 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
world = world ?? FlutterDriverWorld(); world = world ?? FlutterDriverWorld();
final driver = await createFlutterDriver( final driver = await createFlutterDriver(
flutterConfig.runningAppProtocolEndpointUri?.isNotEmpty ?? false flutterConfig.runningAppProtocolEndpointUri?.isNotEmpty ?? false ? flutterConfig.runningAppProtocolEndpointUri : null,
? flutterConfig.runningAppProtocolEndpointUri
: null,
); );
(world as FlutterDriverWorld).setFlutterDriver(driver); (world as FlutterDriverWorld).setFlutterDriver(driver);

View File

@ -35,6 +35,8 @@ class FlutterTestConfiguration extends TestConfiguration {
tapWidgetOfTypeStep(), tapWidgetOfTypeStep(),
tapWidgetOfTypeWithinStep(), tapWidgetOfTypeWithinStep(),
tapWidgetWithTextStep(), tapWidgetWithTextStep(),
selectDropDownWithTextStep(),
scrollDropDown(),
textExistsStep(), textExistsStep(),
textExistsWithinStep(), textExistsWithinStep(),
waitUntilKeyExistsStep(), waitUntilKeyExistsStep(),

View File

@ -21,8 +21,7 @@ class FlutterAppRunnerHook extends Hook {
} }
@override @override
Future<void> onAfterRun(TestConfiguration config) async => Future<void> onAfterRun(TestConfiguration config) async => await _terminateApp();
await _terminateApp();
@override @override
Future<void> onBeforeScenario( Future<void> onBeforeScenario(
@ -45,8 +44,7 @@ class FlutterAppRunnerHook extends Hook {
}) async { }) async {
final flutterConfig = _castConfig(config); final flutterConfig = _castConfig(config);
haveRunFirstScenario = true; haveRunFirstScenario = true;
if (_flutterRunProcessHandler != null && if (_flutterRunProcessHandler != null && flutterConfig.restartAppBetweenScenarios) {
flutterConfig.restartAppBetweenScenarios) {
await _restartApp(); await _restartApp();
} }
} }
@ -86,8 +84,7 @@ class FlutterAppRunnerHook extends Hook {
"Starting Flutter app under test '${config.targetAppPath}', this might take a few moments", "Starting Flutter app under test '${config.targetAppPath}', this might take a few moments",
); );
await _flutterRunProcessHandler!.run(); await _flutterRunProcessHandler!.run();
final observatoryUri = await _flutterRunProcessHandler! final observatoryUri = await _flutterRunProcessHandler!.waitForObservatoryDebuggerUri(config.flutterBuildTimeout);
.waitForObservatoryDebuggerUri(config.flutterBuildTimeout);
config.setObservatoryDebuggerUri(observatoryUri); config.setObservatoryDebuggerUri(observatoryUri);
} }
} }
@ -107,8 +104,7 @@ class FlutterAppRunnerHook extends Hook {
} }
} }
FlutterDriverTestConfiguration _castConfig(TestConfiguration config) => FlutterDriverTestConfiguration _castConfig(TestConfiguration config) => config as FlutterDriverTestConfiguration;
config as FlutterDriverTestConfiguration;
void _log(String text) { void _log(String text) {
if (!kIsWeb) { if (!kIsWeb) {

View File

@ -10,9 +10,7 @@ class AttachScreenshotOnFailedStepHook extends Hook {
String step, String step,
StepResult stepResult, StepResult stepResult,
) async { ) async {
if (stepResult.result == StepExecutionResult.fail || if (stepResult.result == StepExecutionResult.fail || stepResult.result == StepExecutionResult.error || stepResult.result == StepExecutionResult.timeout) {
stepResult.result == StepExecutionResult.error ||
stepResult.result == StepExecutionResult.timeout) {
try { try {
final screenshotData = await _takeScreenshot(world); final screenshotData = await _takeScreenshot(world);
world.attach(screenshotData, 'image/png', step); world.attach(screenshotData, 'image/png', step);

View File

@ -17,8 +17,7 @@ class ExistenceParameter extends CustomParameter<Existence> {
case 'absent': case 'absent':
return Existence.absent; return Existence.absent;
default: default:
throw ArgumentError( throw ArgumentError('Value `$c` must be defined for this Existence parameter');
'Value `$c` must be defined for this Existence parameter');
} }
}, },
); );

View File

@ -18,8 +18,7 @@ class SwipeDirectionParameter extends CustomParameter<SwipeDirection> {
case 'up': case 'up':
return SwipeDirection.up; return SwipeDirection.up;
default: default:
throw ArgumentError( throw ArgumentError('"down", "left", "right", or "up" must be defined for this parameter');
'"down", "left", "right", or "up" must be defined for this parameter');
} }
}, },
); );

View File

@ -13,8 +13,7 @@ enum _FlutterDriverMessageLogLevel { info, warning, error }
/// This can cause problems with CI servers for example as they will mark a process as failed if it logs to the /// This can cause problems with CI servers for example as they will mark a process as failed if it logs to the
/// stderr stream. So Flutter driver will log a normal info message to the stderr and thus make /// stderr stream. So Flutter driver will log a normal info message to the stderr and thus make
/// the process fail from the perspective of a CI server. /// the process fail from the perspective of a CI server.
class FlutterDriverReporter extends Reporter class FlutterDriverReporter extends Reporter implements DisposableReporter, TestReporter {
implements DisposableReporter, TestReporter {
final bool logErrorMessages; final bool logErrorMessages;
final bool logWarningMessages; final bool logWarningMessages;
final bool logInfoMessages; final bool logInfoMessages;
@ -48,8 +47,7 @@ class FlutterDriverReporter extends Reporter
if (logWarningMessages && level == _FlutterDriverMessageLogLevel.warning) { if (logWarningMessages && level == _FlutterDriverMessageLogLevel.warning) {
stdout.writeln(log); stdout.writeln(log);
} else if (logErrorMessages && } else if (logErrorMessages && level == _FlutterDriverMessageLogLevel.error) {
level == _FlutterDriverMessageLogLevel.error) {
stderr.writeln(log); stderr.writeln(log);
} else { } else {
stdout.writeln(log); stdout.writeln(log);

View File

@ -132,13 +132,9 @@ class FlutterRunProcessHandler extends ProcessHandler {
runInShell: true, runInShell: true,
); );
_processStdoutStream = _processStdoutStream = _runningProcess!.stdout.transform(utf8.decoder).asBroadcastStream();
_runningProcess!.stdout.transform(utf8.decoder).asBroadcastStream();
_openSubscriptions.add(_runningProcess!.stderr _openSubscriptions.add(_runningProcess!.stderr.map((events) => String.fromCharCodes(events).trim()).where((event) => event.isNotEmpty).listen((event) {
.map((events) => String.fromCharCodes(events).trim())
.where((event) => event.isNotEmpty)
.listen((event) {
if (event.contains(_errorMessageRegex)) { if (event.contains(_errorMessageRegex)) {
stderr.writeln( stderr.writeln(
'${StdoutReporter.kFailColor}Flutter build error: $event${StdoutReporter.kResetColor}', '${StdoutReporter.kFailColor}Flutter build error: $event${StdoutReporter.kResetColor}',
@ -257,8 +253,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
void _ensureRunningProcess() { void _ensureRunningProcess() {
if (_runningProcess == null) { if (_runningProcess == null) {
throw Exception( throw Exception('FlutterRunProcessHandler: flutter run process is not active');
'FlutterRunProcessHandler: flutter run process is not active');
} }
} }
} }

View File

@ -32,8 +32,7 @@ class TestDependencies {
} }
abstract class GherkinIntegrationTestRunner { abstract class GherkinIntegrationTestRunner {
final TagExpressionEvaluator _tagExpressionEvaluator = final TagExpressionEvaluator _tagExpressionEvaluator = TagExpressionEvaluator();
TagExpressionEvaluator();
final FlutterTestConfiguration configuration; final FlutterTestConfiguration configuration;
final StartAppFn appMainFunction; final StartAppFn appMainFunction;
final AppLifecyclePumpHandlerFn? appLifecyclePumpHandler; final AppLifecyclePumpHandlerFn? appLifecyclePumpHandler;
@ -69,8 +68,7 @@ abstract class GherkinIntegrationTestRunner {
configuration.prepare(); configuration.prepare();
_registerReporters(configuration.reporters); _registerReporters(configuration.reporters);
_hook = _registerHooks(configuration.hooks); _hook = _registerHooks(configuration.hooks);
_customParameters = _customParameters = _registerCustomParameters(configuration.customStepParameterDefinitions);
_registerCustomParameters(configuration.customStepParameterDefinitions);
_executableSteps = _registerStepDefinitions( _executableSteps = _registerStepDefinitions(
configuration.stepDefinitions!, configuration.stepDefinitions!,
_customParameters!, _customParameters!,
@ -130,8 +128,7 @@ abstract class GherkinIntegrationTestRunner {
Iterable<String>? tags, Iterable<String>? tags,
}) async { }) async {
final debugInformation = RunnableDebugInformation(path, 0, name); final debugInformation = RunnableDebugInformation(path, 0, name);
final featureTags = final featureTags = (tags ?? const Iterable<Tag>.empty()).map((t) => Tag(t.toString(), 0));
(tags ?? const Iterable<Tag>.empty()).map((t) => Tag(t.toString(), 0));
await reporter.feature.onStarted.invoke( await reporter.feature.onStarted.invoke(
FeatureMessage( FeatureMessage(
name: name, name: name,
@ -291,8 +288,7 @@ abstract class GherkinIntegrationTestRunner {
WidgetTester tester, WidgetTester tester,
) async { ) async {
World? world; World? world;
final attachmentManager = final attachmentManager = await configuration.getAttachmentManager(configuration);
await configuration.getAttachmentManager(configuration);
if (configuration.createWorld != null) { if (configuration.createWorld != null) {
world = await configuration.createWorld!(configuration); world = await configuration.createWorld!(configuration);
@ -305,9 +301,7 @@ abstract class GherkinIntegrationTestRunner {
WidgetTesterAppDriverAdapter( WidgetTesterAppDriverAdapter(
rawAdapter: tester, rawAdapter: tester,
binding: _binding, binding: _binding,
waitImplicitlyAfterAction: configuration is FlutterTestConfiguration waitImplicitlyAfterAction: configuration is FlutterTestConfiguration ? (configuration).waitImplicitlyAfterAction : true,
? (configuration).waitImplicitlyAfterAction
: true,
), ),
); );
@ -366,8 +360,7 @@ abstract class GherkinIntegrationTestRunner {
parameters, parameters,
); );
if (!_isNegativeResult(result.result) || if (!_isNegativeResult(result.result) || configuration.stepMaxRetries == 0) {
configuration.stepMaxRetries == 0) {
break; break;
} else { } else {
await Future.delayed(configuration.retryDelay); await Future.delayed(configuration.retryDelay);
@ -486,9 +479,7 @@ abstract class GherkinIntegrationTestRunner {
name: step, name: step,
context: RunnableDebugInformation('', 0, step), context: RunnableDebugInformation('', 0, step),
result: result, result: result,
attachments: dependencies.attachmentManager attachments: dependencies.attachmentManager.getAttachmentsForContext(step).toList(),
.getAttachmentsForContext(step)
.toList(),
), ),
); );
} }
@ -505,8 +496,7 @@ abstract class GherkinIntegrationTestRunner {
name: step, name: step,
context: RunnableDebugInformation('', 0, step), context: RunnableDebugInformation('', 0, step),
table: table, table: table,
multilineString: multilineString: multiLineStrings.isNotEmpty ? multiLineStrings.first : null,
multiLineStrings.isNotEmpty ? multiLineStrings.first : null,
), ),
); );
} }
@ -524,9 +514,7 @@ abstract class GherkinIntegrationTestRunner {
} }
bool _isNegativeResult(StepExecutionResult result) { bool _isNegativeResult(StepExecutionResult result) {
return result == StepExecutionResult.error || return result == StepExecutionResult.error || result == StepExecutionResult.fail || result == StepExecutionResult.timeout;
result == StepExecutionResult.fail ||
result == StepExecutionResult.timeout;
} }
Future<void> _appLifecyclePhasePumper( Future<void> _appLifecyclePhasePumper(

View File

@ -5,16 +5,14 @@ import 'package:gherkin/gherkin.dart';
import '../parameters/swipe_direction_parameter.dart'; import '../parameters/swipe_direction_parameter.dart';
mixin _SwipeHelper mixin _SwipeHelper on When3WithWorld<SwipeDirection, int, String, FlutterWorld> {
on When3WithWorld<SwipeDirection, int, String, FlutterWorld> {
Future<void> swipeOnFinder( Future<void> swipeOnFinder(
dynamic finder, dynamic finder,
SwipeDirection direction, SwipeDirection direction,
int swipeAmount, int swipeAmount,
) async { ) async {
if (direction == SwipeDirection.left || direction == SwipeDirection.right) { if (direction == SwipeDirection.left || direction == SwipeDirection.right) {
final offset = final offset = direction == SwipeDirection.right ? swipeAmount : (swipeAmount * -1);
direction == SwipeDirection.right ? swipeAmount : (swipeAmount * -1);
await world.appDriver.scroll( await world.appDriver.scroll(
finder, finder,
@ -23,8 +21,7 @@ mixin _SwipeHelper
timeout: timeout, timeout: timeout,
); );
} else { } else {
final offset = final offset = direction == SwipeDirection.up ? swipeAmount : (swipeAmount * -1);
direction == SwipeDirection.up ? swipeAmount : (swipeAmount * -1);
await world.appDriver.scroll( await world.appDriver.scroll(
finder, finder,
@ -42,9 +39,7 @@ mixin _SwipeHelper
/// ///
/// `Then I swipe up by 800 pixels on the "login_screen"` /// `Then I swipe up by 800 pixels on the "login_screen"`
/// `Then I swipe left by 200 pixels on the "dismissible_list_item"` /// `Then I swipe left by 200 pixels on the "dismissible_list_item"`
class SwipeOnKeyStep class SwipeOnKeyStep extends When3WithWorld<SwipeDirection, int, String, FlutterWorld> with _SwipeHelper {
extends When3WithWorld<SwipeDirection, int, String, FlutterWorld>
with _SwipeHelper {
@override @override
Future<void> executeStep( Future<void> executeStep(
SwipeDirection direction, SwipeDirection direction,
@ -56,8 +51,7 @@ class SwipeOnKeyStep
} }
@override @override
RegExp get pattern => RegExp get pattern => RegExp(r'I swipe {swipe_direction} by {int} pixels on the {string}');
RegExp(r'I swipe {swipe_direction} by {int} pixels on the {string}');
} }
/// Swipes in a cardinal direction on a widget discovered by its test. /// Swipes in a cardinal direction on a widget discovered by its test.
@ -65,9 +59,7 @@ class SwipeOnKeyStep
/// Examples: /// Examples:
/// ///
/// `Then I swipe left by 400 pixels on the widget that contains the text "Dismiss Me"` /// `Then I swipe left by 400 pixels on the widget that contains the text "Dismiss Me"`
class SwipeOnTextStep class SwipeOnTextStep extends When3WithWorld<SwipeDirection, int, String, FlutterWorld> with _SwipeHelper {
extends When3WithWorld<SwipeDirection, int, String, FlutterWorld>
with _SwipeHelper {
@override @override
Future<void> executeStep( Future<void> executeStep(
SwipeDirection direction, SwipeDirection direction,
@ -79,6 +71,5 @@ class SwipeOnTextStep
} }
@override @override
RegExp get pattern => RegExp( RegExp get pattern => RegExp(r'I swipe {swipe_direction} by {int} pixels on the (?:button|element|label|field|text|widget|dialog|popup) that contains the text {string}');
r'I swipe {swipe_direction} by {int} pixels on the (?:button|element|label|field|text|widget|dialog|popup) that contains the text {string}');
} }

View File

@ -9,11 +9,9 @@ import 'package:gherkin/gherkin.dart';
/// `Then I tap the label that contains the text "Logout" within the "user_settings_list"` /// `Then I tap the label that contains the text "Logout" within the "user_settings_list"`
StepDefinitionGeneric tapTextWithinWidgetStep() { StepDefinitionGeneric tapTextWithinWidgetStep() {
return given2<String, String, FlutterWorld>( return given2<String, String, FlutterWorld>(
RegExp( RegExp(r'I tap the (?:button|element|label|field|text|widget) that contains the text {string} within the {string}'),
r'I tap the (?:button|element|label|field|text|widget) that contains the text {string} within the {string}'),
(text, ancestorKey, context) async { (text, ancestorKey, context) async {
final timeout = final timeout = context.configuration.timeout ?? const Duration(seconds: 20);
context.configuration.timeout ?? const Duration(seconds: 20);
final finder = context.world.appDriver.findByDescendant( final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(ancestorKey, FindType.key), context.world.appDriver.findBy(ancestorKey, FindType.key),
context.world.appDriver.findBy(text, FindType.text), context.world.appDriver.findBy(text, FindType.text),

View File

@ -10,8 +10,7 @@ import 'package:gherkin/gherkin.dart';
/// `Then I tap the field of type "TextField"` /// `Then I tap the field of type "TextField"`
StepDefinitionGeneric tapWidgetOfTypeStep() { StepDefinitionGeneric tapWidgetOfTypeStep() {
return given1<String, FlutterWorld>( return given1<String, FlutterWorld>(
RegExp( RegExp(r'I tap the (?:button|element|label|icon|field|text|widget) of type {string}$'),
r'I tap the (?:button|element|label|icon|field|text|widget) of type {string}$'),
(input1, context) async { (input1, context) async {
await context.world.appDriver.tap( await context.world.appDriver.tap(
context.world.appDriver.findBy( context.world.appDriver.findBy(

View File

@ -8,8 +8,7 @@ import 'package:gherkin/gherkin.dart';
/// `Then I tap the element of type "MaterialButton" within the "user_settings_list"` /// `Then I tap the element of type "MaterialButton" within the "user_settings_list"`
StepDefinitionGeneric tapWidgetOfTypeWithinStep() { StepDefinitionGeneric tapWidgetOfTypeWithinStep() {
return when2<String, String, FlutterWorld>( return when2<String, String, FlutterWorld>(
RegExp( RegExp(r'I tap the (?:button|element|label|icon|field|text|widget) of type {string} within the {string}$'),
r'I tap the (?:button|element|label|icon|field|text|widget) of type {string} within the {string}$'),
(widgetType, ancestorKey, context) async { (widgetType, ancestorKey, context) async {
final finder = context.world.appDriver.findByDescendant( final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(ancestorKey, FindType.key), context.world.appDriver.findBy(ancestorKey, FindType.key),

View File

@ -1,3 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
@ -10,8 +13,7 @@ import 'package:gherkin/gherkin.dart';
/// `Then I tap the widget that contains the text "My User Profile"` /// `Then I tap the widget that contains the text "My User Profile"`
StepDefinitionGeneric tapWidgetWithTextStep() { StepDefinitionGeneric tapWidgetWithTextStep() {
return then1<String, FlutterWorld>( return then1<String, FlutterWorld>(
RegExp( RegExp(r'I tap the (?:button|element|label|field|text|widget) that contains the text {string}$'),
r'I tap the (?:button|element|label|field|text|widget) that contains the text {string}$'),
(input1, context) async { (input1, context) async {
final finder = context.world.appDriver.findBy(input1, FindType.text); final finder = context.world.appDriver.findBy(input1, FindType.text);
print("to tap: $finder"); print("to tap: $finder");
@ -20,3 +22,39 @@ StepDefinitionGeneric tapWidgetWithTextStep() {
}, },
); );
} }
/// Select and scroll a dropdown list in a given direction
///
/// Examples:
///
/// `Then I scroll the dropdown list "LanguageList" by -10`
StepDefinitionGeneric scrollDropDown() {
return then2<String, double, FlutterWorld>(
RegExp(r'I scroll the dropdown list {string} by {num}'),
(input1, distance, context) async {
final finder = context.world.appDriver.findBy(input1, FindType.key);
await context.world.appDriver.tap(finder);
await context.world.appDriver.waitForAppToSettle();
final dropdownListFinder = context.world.appDriver.findBy(Scrollable, FindType.type);
await context.world.appDriver.scroll(dropdownListFinder.last, dy: distance);
await context.world.appDriver.waitForAppToSettle();
},
);
}
/// Taps a dropdown that contains text.
///
/// Examples:
///
/// `Then I tap the dropdown button that contains the text "Logout"`
StepDefinitionGeneric selectDropDownWithTextStep() {
return then1<String, FlutterWorld>(
RegExp(r'I tap the dropdown button that contains the text {string}$'),
(input1, context) async {
final finder = context.world.appDriver.findBy(input1, FindType.text);
print("to tap: $finder");
await context.world.appDriver.scrollIntoView(finder.last);
await context.world.appDriver.tap(finder.last);
},
);
}

View File

@ -11,8 +11,7 @@ import '../parameters/existence_parameter.dart';
/// `But I expect the text "Sign up" to be absent within the "login_screen"` /// `But I expect the text "Sign up" to be absent within the "login_screen"`
StepDefinitionGeneric textExistsWithinStep() { StepDefinitionGeneric textExistsWithinStep() {
return then3<String, Existence, String, FlutterWorld>( return then3<String, Existence, String, FlutterWorld>(
RegExp( RegExp(r'I expect the text {string} to be {existence} within the {string}$'),
r'I expect the text {string} to be {existence} within the {string}$'),
(text, exists, ancestorKey, context) async { (text, exists, ancestorKey, context) async {
final finder = context.world.appDriver.findByDescendant( final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(ancestorKey, FindType.key), context.world.appDriver.findBy(ancestorKey, FindType.key),

View File

@ -13,8 +13,7 @@ import 'package:gherkin/gherkin.dart';
/// `Then I expect the button 'save' to be present within 1 second` /// `Then I expect the button 'save' to be present within 1 second`
StepDefinitionGeneric thenExpectWidgetToBePresent() { StepDefinitionGeneric thenExpectWidgetToBePresent() {
return given2<String, int, FlutterWorld>( return given2<String, int, FlutterWorld>(
RegExp( RegExp(r'I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$'),
r'I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$'),
(key, seconds, context) async { (key, seconds, context) async {
await context.world.appDriver.waitUntil( await context.world.appDriver.waitUntil(
() async { () async {
@ -27,7 +26,6 @@ StepDefinitionGeneric thenExpectWidgetToBePresent() {
timeout: Duration(seconds: seconds), timeout: Duration(seconds: seconds),
); );
}, },
configuration: StepDefinitionConfiguration() configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1),
..timeout = const Duration(days: 1),
); );
} }

View File

@ -18,8 +18,7 @@ import 'package:gherkin/gherkin.dart';
/// `When I long press "controlKey" widget` /// `When I long press "controlKey" widget`
StepDefinitionGeneric whenLongPressWidget() { StepDefinitionGeneric whenLongPressWidget() {
return when1<String, FlutterWorld>( return when1<String, FlutterWorld>(
RegExp( RegExp(r'I long press the {string} (?:button|element|label|icon|field|text|widget)$'),
r'I long press the {string} (?:button|element|label|icon|field|text|widget)$'),
(key, context) async { (key, context) async {
final finder = context.world.appDriver.findBy(key, FindType.key); final finder = context.world.appDriver.findBy(key, FindType.key);
@ -33,8 +32,7 @@ StepDefinitionGeneric whenLongPressWidget() {
/// Long presses the widget found with the given control key, without scrolling into view /// Long presses the widget found with the given control key, without scrolling into view
StepDefinitionGeneric whenLongPressWidgetWithoutScroll() { StepDefinitionGeneric whenLongPressWidgetWithoutScroll() {
return when1<String, FlutterWorld>( return when1<String, FlutterWorld>(
RegExp( RegExp(r'I long press the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'),
r'I long press the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'),
(key, context) async { (key, context) async {
final finder = context.world.appDriver.findBy(key, FindType.key); final finder = context.world.appDriver.findBy(key, FindType.key);
@ -48,8 +46,7 @@ StepDefinitionGeneric whenLongPressWidgetWithoutScroll() {
/// Long presses the widget found with the given control key, for the given duration /// Long presses the widget found with the given control key, for the given duration
StepDefinitionGeneric whenLongPressWidgetForDuration() { StepDefinitionGeneric whenLongPressWidgetForDuration() {
return when2<String, int, FlutterWorld>( return when2<String, int, FlutterWorld>(
RegExp( RegExp(r'I long press the {string} (?:button|element|label|icon|field|text|widget) for {int} milliseconds$'),
r'I long press the {string} (?:button|element|label|icon|field|text|widget) for {int} milliseconds$'),
(key, milliseconds, context) async { (key, milliseconds, context) async {
final finder = context.world.appDriver.findBy(key, FindType.key); final finder = context.world.appDriver.findBy(key, FindType.key);

View File

@ -13,6 +13,7 @@ StepDefinitionGeneric whenTapBackButtonWidget() {
RegExp(r'I tap the back (?:button|element|widget|icon|text)$'), RegExp(r'I tap the back (?:button|element|widget|icon|text)$'),
(context) async { (context) async {
await context.world.appDriver.pageBack(); await context.world.appDriver.pageBack();
await context.world.appDriver.waitForAppToSettle();
}, },
); );
} }

View File

@ -18,8 +18,7 @@ import 'package:gherkin/gherkin.dart';
/// `When I tap "controlKey" widget"` /// `When I tap "controlKey" widget"`
StepDefinitionGeneric whenTapWidget() { StepDefinitionGeneric whenTapWidget() {
return when1<String, FlutterWorld>( return when1<String, FlutterWorld>(
RegExp( RegExp(r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'),
r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'),
(key, context) async { (key, context) async {
final finder = context.world.appDriver.findBy(key, FindType.key); final finder = context.world.appDriver.findBy(key, FindType.key);
@ -36,11 +35,9 @@ StepDefinitionGeneric whenTapWidget() {
StepDefinitionGeneric whenTapWidgetWithoutScroll() { StepDefinitionGeneric whenTapWidgetWithoutScroll() {
return when1<String, FlutterWorld>( return when1<String, FlutterWorld>(
RegExp( RegExp(r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'),
r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'),
(key, context) async { (key, context) async {
final finder = final finder = context.world.appDriver.findByDescendant(key, FindType.key);
context.world.appDriver.findByDescendant(key, FindType.key);
await context.world.appDriver.tap( await context.world.appDriver.tap(
finder, finder,

View File

@ -4,8 +4,7 @@ import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart';
import '../runners/flutter_run_process_handler.dart'; import '../runners/flutter_run_process_handler.dart';
/// Driver version of the FlutterWorld with a typed driver /// Driver version of the FlutterWorld with a typed driver
class FlutterDriverWorld extends FlutterTypedAdapterWorld<FlutterDriver, class FlutterDriverWorld extends FlutterTypedAdapterWorld<FlutterDriver, SerializableFinder, dynamic> {
SerializableFinder, dynamic> {
FlutterRunProcessHandler? _flutterRunProcessHandler; FlutterRunProcessHandler? _flutterRunProcessHandler;
void setFlutterDriver(FlutterDriver flutterDriver) { void setFlutterDriver(FlutterDriver flutterDriver) {

View File

@ -6,5 +6,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart';
/// It also allows interaction with the app under test through the `appDriver` /// It also allows interaction with the app under test through the `appDriver`
/// which exposes an instance of `AppDriverAdapter` and an /// which exposes an instance of `AppDriverAdapter` and an
/// instance of `WidgetTester` via the property `rawAppDriver` /// instance of `WidgetTester` via the property `rawAppDriver`
class FlutterWidgetTesterWorld class FlutterWidgetTesterWorld extends FlutterTypedAdapterWorld<WidgetTester, Finder, Widget> {}
extends FlutterTypedAdapterWorld<WidgetTester, Finder, Widget> {}

View File

@ -38,6 +38,5 @@ class FlutterTypedAdapterWorld<TDriver, TFinder, TWidget> extends FlutterWorld {
/// The adapter that is used to agnostically drive the app under test /// The adapter that is used to agnostically drive the app under test
@override @override
AppDriverAdapter<TDriver, TFinder, TWidget> get appDriver => AppDriverAdapter<TDriver, TFinder, TWidget> get appDriver => _adapter as AppDriverAdapter<TDriver, TFinder, TWidget>;
_adapter as AppDriverAdapter<TDriver, TFinder, TWidget>;
} }