From 6fb14d0f0e495b9ee499d5e5026c08f7ba4ccc8e Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 27 Jul 2022 11:27:16 +0300 Subject: [PATCH 1/2] Make test example e2e example with text field and manual widget manipulation --- examples/profile/.fvm/flutter_sdk | 1 + examples/profile/.fvm/fvm_config.json | 4 + examples/profile/analysis_options.yaml | 6 + examples/profile/build.yaml | 8 + .../integration_test/.run/test.run.xml | 6 + .../integration_test/features/profile.feature | 27 ++ .../integration_test/gherkin_suite_test.dart | 38 ++ .../gherkin_suite_test.g.dart | 328 ++++++++++++++++++ .../step_definitions/implemented/my_steps.rb | 11 + .../main_step_definitions.dart | 15 + .../profile_step_definitions.dart | 31 ++ examples/profile/pubspec.yaml | 5 + examples/profile/script/mkdir.sh | 5 + .../screens/general_test_screen.dart | 11 + .../test_screen/screens/main_test_screen.dart | 8 + .../screens/profile_test_screen.dart | 11 + .../test_screen/test_screen_library.dart | 3 + 17 files changed, 518 insertions(+) create mode 120000 examples/profile/.fvm/flutter_sdk create mode 100644 examples/profile/.fvm/fvm_config.json create mode 100644 examples/profile/build.yaml create mode 100644 examples/profile/integration_test/.run/test.run.xml create mode 100644 examples/profile/integration_test/features/profile.feature create mode 100644 examples/profile/integration_test/gherkin_suite_test.dart create mode 100644 examples/profile/integration_test/gherkin_suite_test.g.dart create mode 100644 examples/profile/integration_test/step_definitions/implemented/my_steps.rb create mode 100644 examples/profile/integration_test/step_definitions/main_step_definitions.dart create mode 100644 examples/profile/integration_test/step_definitions/profile_step_definitions.dart create mode 100644 examples/profile/script/mkdir.sh create mode 100644 examples/profile/test_screen/screens/general_test_screen.dart create mode 100644 examples/profile/test_screen/screens/main_test_screen.dart create mode 100644 examples/profile/test_screen/screens/profile_test_screen.dart create mode 100644 examples/profile/test_screen/test_screen_library.dart diff --git a/examples/profile/.fvm/flutter_sdk b/examples/profile/.fvm/flutter_sdk new file mode 120000 index 00000000..ad67ebfa --- /dev/null +++ b/examples/profile/.fvm/flutter_sdk @@ -0,0 +1 @@ +/Users/voronin/fvm/versions/3.0.5 \ No newline at end of file diff --git a/examples/profile/.fvm/fvm_config.json b/examples/profile/.fvm/fvm_config.json new file mode 100644 index 00000000..5d1bc03b --- /dev/null +++ b/examples/profile/.fvm/fvm_config.json @@ -0,0 +1,4 @@ +{ + "flutterSdkVersion": "3.0.5", + "flavors": {} +} \ No newline at end of file diff --git a/examples/profile/analysis_options.yaml b/examples/profile/analysis_options.yaml index 0914fb94..820afa80 100644 --- a/examples/profile/analysis_options.yaml +++ b/examples/profile/analysis_options.yaml @@ -9,7 +9,13 @@ dart_code_metrics: number-of-methods: 10 weight-of-class: 0.33 maintainability-index: 50 + rules: + - member-ordering-extended: + exclude: + - integration_test/** + - test_screen/** linter: rules: public_member_api_docs: true + sort_pub_dependencies: false diff --git a/examples/profile/build.yaml b/examples/profile/build.yaml new file mode 100644 index 00000000..ae61e5f3 --- /dev/null +++ b/examples/profile/build.yaml @@ -0,0 +1,8 @@ +targets: + $default: + sources: + - lib/** + - pubspec.* + - $package$ + # Allows the code generator to target files outside of the lib folder + - integration_test/**.dart \ No newline at end of file diff --git a/examples/profile/integration_test/.run/test.run.xml b/examples/profile/integration_test/.run/test.run.xml new file mode 100644 index 00000000..744b9a53 --- /dev/null +++ b/examples/profile/integration_test/.run/test.run.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/examples/profile/integration_test/features/profile.feature b/examples/profile/integration_test/features/profile.feature new file mode 100644 index 00000000..b1e206d3 --- /dev/null +++ b/examples/profile/integration_test/features/profile.feature @@ -0,0 +1,27 @@ +#language: ru +#noinspection NonAsciiCharacters + +Функциональность: Профиль + + Сценарий: Успешное заполнение профиля + Когда Я перехожу к редактированию профиля + И Я указываю фамилию "Semenikhina" + И Я указываю дату рождения "1998-11-14" + И Я указываю имя "Daria" + И Я указываю отчество "Evgenevna" + И Я перехожу далее + И Я выбираю город "Voronezh" + И Я перехожу далее + И Я выбираю "Sleep" из интересов + И Я перехожу далее + И Я заполняю заметку о себе "Тестовая заметка о себе" + И Я перехожу далее + И Я перехожу к редактированию профиля + Тогда Я вижу заполненные поля ФИО + И Я вижу заполненное поле даты рождения + Когда Я перехожу далее + Тогда Я вижу заполненное поле города + Когда Я перехожу далее + Тогда Я вижу выбранные интересы + Когда Я перехожу далее + Тогда Я вижу заполненное поле заметки о себе diff --git a/examples/profile/integration_test/gherkin_suite_test.dart b/examples/profile/integration_test/gherkin_suite_test.dart new file mode 100644 index 00000000..eb7aca57 --- /dev/null +++ b/examples/profile/integration_test/gherkin_suite_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_gherkin/flutter_gherkin.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:gherkin/gherkin.dart'; +import 'package:profile/runner.dart' as app; +import 'package:surf_logger/surf_logger.dart'; + +import 'step_definitions/main_step_definitions.dart'; +import 'step_definitions/profile_step_definitions.dart'; + +part 'gherkin_suite_test.g.dart'; + +@GherkinTestSuite() +void main() { + executeTestSuite( + configuration: FlutterTestConfiguration( + featureDefaultLanguage: 'ru', + order: ExecutionOrder.alphabetical, + defaultTimeout: const Duration(minutes: 5), + reporters: [ + StdoutReporter(MessageLevel.error) + ..setWriteLineFn(Logger.d) + ..setWriteFn(Logger.d), + ProgressReporter() + ..setWriteLineFn(Logger.d) + ..setWriteFn(Logger.d), + TestRunSummaryReporter() + ..setWriteLineFn(Logger.d) + ..setWriteFn(Logger.d), + JsonReporter(writeReport: (_, __) => Future.value()), + ], + stepDefinitions: [ + ...MainStepDefinitions.steps, + ...ProfileStepDefinitions.steps, + ], + ), + appMainFunction: (world) => app.run(), + ); +} diff --git a/examples/profile/integration_test/gherkin_suite_test.g.dart b/examples/profile/integration_test/gherkin_suite_test.g.dart new file mode 100644 index 00000000..d044b6b2 --- /dev/null +++ b/examples/profile/integration_test/gherkin_suite_test.g.dart @@ -0,0 +1,328 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'gherkin_suite_test.dart'; + +// ************************************************************************** +// GherkinSuiteTestGenerator +// ************************************************************************** + +class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { + _CustomGherkinIntegrationTestRunner({ + required FlutterTestConfiguration configuration, + required StartAppFn appMainFunction, + required Timeout scenarioExecutionTimeout, + AppLifecyclePumpHandlerFn? appLifecyclePumpHandler, + LiveTestWidgetsFlutterBindingFramePolicy? framePolicy, + }) : super( + configuration: configuration, + appMainFunction: appMainFunction, + scenarioExecutionTimeout: scenarioExecutionTimeout, + appLifecyclePumpHandler: appLifecyclePumpHandler, + framePolicy: framePolicy, + ); + + @override + void onRun() { + testFeature0(); + } + + void testFeature0() { + runFeature( + name: 'Профиль:', + tags: [], + run: () { + runScenario( + name: 'Успешное заполнение профиля', + description: null, + path: + '/Users/voronin/flutter-elementary-test/examples/profile/./integration_test/features/profile.feature', + tags: [], + steps: [ + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Когда Я перехожу к редактированию профиля', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я указываю фамилию "Semenikhina"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я указываю дату рождения "1998-11-14"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я указываю имя "Daria"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я указываю отчество "Evgenevna"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я выбираю город "Voronezh"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я выбираю "Sleep" из интересов', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я заполняю заметку о себе "Тестовая заметка о себе"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я перехожу к редактированию профиля', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Тогда Я вижу заполненные поля ФИО', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'И Я вижу заполненное поле даты рождения', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Когда Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Тогда Я вижу заполненное поле города', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Когда Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Тогда Я вижу выбранные интересы', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Когда Я перехожу далее', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Тогда Я вижу заполненное поле заметки о себе', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ], + onBefore: () async => onBeforeRunFeature( + name: 'Профиль', + path: + r'/Users/voronin/flutter-elementary-test/examples/profile/./integration_test/features/profile.feature', + description: null, + tags: [], + ), + onAfter: () async => onAfterRunFeature( + name: 'Профиль', + path: + r'/Users/voronin/flutter-elementary-test/examples/profile/./integration_test/features/profile.feature', + description: null, + tags: [], + ), + ); + }, + ); + } +} + +void executeTestSuite({ + required FlutterTestConfiguration configuration, + required StartAppFn appMainFunction, + Timeout scenarioExecutionTimeout = const Timeout(const Duration(minutes: 10)), + AppLifecyclePumpHandlerFn? appLifecyclePumpHandler, + LiveTestWidgetsFlutterBindingFramePolicy? framePolicy, +}) { + _CustomGherkinIntegrationTestRunner( + configuration: configuration, + appMainFunction: appMainFunction, + appLifecyclePumpHandler: appLifecyclePumpHandler, + scenarioExecutionTimeout: scenarioExecutionTimeout, + framePolicy: framePolicy, + ).run(); +} diff --git a/examples/profile/integration_test/step_definitions/implemented/my_steps.rb b/examples/profile/integration_test/step_definitions/implemented/my_steps.rb new file mode 100644 index 00000000..a896bb6a --- /dev/null +++ b/examples/profile/integration_test/step_definitions/implemented/my_steps.rb @@ -0,0 +1,11 @@ +When(/^Я перехожу к редактированию профиля$/) do + pending +end + +When(/^Я указываю фамилию "([^"]+)"$/) do |arg| + pending +end + +When(/^Я указываю дату рождения "([^"]+)"$/) do |arg| + pending +end \ No newline at end of file diff --git a/examples/profile/integration_test/step_definitions/main_step_definitions.dart b/examples/profile/integration_test/step_definitions/main_step_definitions.dart new file mode 100644 index 00000000..58001f71 --- /dev/null +++ b/examples/profile/integration_test/step_definitions/main_step_definitions.dart @@ -0,0 +1,15 @@ +import 'package:flutter_gherkin/flutter_gherkin.dart'; +import 'package:gherkin/gherkin.dart'; +import '../../test_screen/test_screen_library.dart'; + +abstract class MainStepDefinitions { + static Iterable get steps => [ + when( + RegExp(r'Я перехожу к редактированию профиля$'), + (context) async { + final tester = context.world.rawAppDriver; + await tester.tap(MainTestScreen.editProfileBtn); + }, + ), + ]; +} diff --git a/examples/profile/integration_test/step_definitions/profile_step_definitions.dart b/examples/profile/integration_test/step_definitions/profile_step_definitions.dart new file mode 100644 index 00000000..b6db0c52 --- /dev/null +++ b/examples/profile/integration_test/step_definitions/profile_step_definitions.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gherkin/flutter_gherkin.dart'; +import 'package:gherkin/gherkin.dart'; + +import '../../test_screen/screens/profile_test_screen.dart'; + +abstract class ProfileStepDefinitions { + static Iterable get steps => [ + when1( + RegExp(r'Я указываю фамилию {string}$'), + (surname, context) async { + final tester = context.world.rawAppDriver; + await tester.pumpAndSettle(); + await tester.enterText(ProfileTestScreen.surnameField, surname); + await tester.pump(); + }, + ), + when1( + RegExp(r'Я указываю дату рождения {string}$'), + (birthdate, context) async { + final tester = context.world.rawAppDriver; + await tester.pumpAndSettle(); + tester + .widget(ProfileTestScreen.birthdayField) + .controller + ?.text = birthdate; + await tester.pump(); + }, + ), + ]; +} diff --git a/examples/profile/pubspec.yaml b/examples/profile/pubspec.yaml index 6e3e3ab7..2987f7ed 100644 --- a/examples/profile/pubspec.yaml +++ b/examples/profile/pubspec.yaml @@ -20,6 +20,11 @@ dev_dependencies: build_runner: ^2.1.5 dart_code_metrics: ^4.7.0 elementary_test: ^1.0.1 + integration_test: + sdk: flutter + flutter_driver: + sdk: flutter + flutter_gherkin: ^3.0.0-rc.17 flutter_test: sdk: flutter surf_lint_rules: ^1.4.1 diff --git a/examples/profile/script/mkdir.sh b/examples/profile/script/mkdir.sh new file mode 100644 index 00000000..1bcaff59 --- /dev/null +++ b/examples/profile/script/mkdir.sh @@ -0,0 +1,5 @@ +mkdir -p integration_test/features +mkdir -p integration_test/step_definitions +mkdir -p integration_test/step_definitions/implemented + +mkdir -p test_screen/screens \ No newline at end of file diff --git a/examples/profile/test_screen/screens/general_test_screen.dart b/examples/profile/test_screen/screens/general_test_screen.dart new file mode 100644 index 00000000..18d86448 --- /dev/null +++ b/examples/profile/test_screen/screens/general_test_screen.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +abstract class GeneralTestScreen { + /// текстовое поле [TextField] по подсказке или хинту [hint]. Строгое соответствие + static Finder textField(String hint) => find.byWidgetPredicate((widget) { + return widget is TextField && + ((widget.decoration?.labelText == hint) || + (widget.decoration?.hintText == hint)); + }); +} diff --git a/examples/profile/test_screen/screens/main_test_screen.dart b/examples/profile/test_screen/screens/main_test_screen.dart new file mode 100644 index 00000000..0cadf575 --- /dev/null +++ b/examples/profile/test_screen/screens/main_test_screen.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +abstract class MainTestScreen { + /// кнопка редактирования профиля на главном экране + static Finder editProfileBtn = + find.widgetWithText(ElevatedButton, 'Edit profile'); +} diff --git a/examples/profile/test_screen/screens/profile_test_screen.dart b/examples/profile/test_screen/screens/profile_test_screen.dart new file mode 100644 index 00000000..f2e1eebc --- /dev/null +++ b/examples/profile/test_screen/screens/profile_test_screen.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'general_test_screen.dart'; + +abstract class ProfileTestScreen { + /// поле Surname на экране персональных данных + static Finder surnameField = GeneralTestScreen.textField('Surname'); + + /// поле Birthday на экране персональных данных + static Finder birthdayField = GeneralTestScreen.textField('Birthday'); +} diff --git a/examples/profile/test_screen/test_screen_library.dart b/examples/profile/test_screen/test_screen_library.dart new file mode 100644 index 00000000..761fb945 --- /dev/null +++ b/examples/profile/test_screen/test_screen_library.dart @@ -0,0 +1,3 @@ +export 'screens/general_test_screen.dart'; +export 'screens/main_test_screen.dart'; +export 'screens/profile_test_screen.dart'; From d9422d1c13ab63a357b4f621ef251cb0f97f43cc Mon Sep 17 00:00:00 2001 From: Daria Semenikhina Date: Tue, 22 Aug 2023 21:06:58 +0300 Subject: [PATCH 2/2] fix for android --- examples/profile/android/app/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/profile/android/app/build.gradle b/examples/profile/android/app/build.gradle index 0b40c81f..c3fdafa0 100644 --- a/examples/profile/android/app/build.gradle +++ b/examples/profile/android/app/build.gradle @@ -48,6 +48,7 @@ android { targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } buildTypes { @@ -68,4 +69,5 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation 'com.yandex.android:maps.mobile:4.0.0-full' + implementation 'com.android.support:multidex:1.0.3' } \ No newline at end of file