From ba28cc8f3012e694c409555ad705d17bd35f7d0f Mon Sep 17 00:00:00 2001 From: Jon Samwell Date: Wed, 25 Mar 2020 11:40:42 +1100 Subject: [PATCH] feat(debug): added ability to run tests against an already running app --- CHANGELOG.md | 3 ++ README.md | 15 ++++++++ example/test_driver/app_test.dart | 4 +- .../flutter/flutter_test_configuration.dart | 35 +++++++++++++++++- lib/src/flutter/hooks/app_runner_hook.dart | 37 +++++++++++-------- pubspec.lock | 8 ++-- pubspec.yaml | 4 +- 7 files changed, 82 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7051986..7939fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.1.7+7] - 25/03/2019 +* Added the ability to test against an already running app; enabling you to debug a running application while it has tests executed against it. Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turrned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. + ## [1.1.7+6] - 04/03/2019 * Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#117---04032020) - this includes a breaking change to the `Hook` inteface that will need to be updated if any of the `Scenerio` level methods are implemented * Ensured the well known step `I tap the ".." button` scroll the element into view first diff --git a/README.md b/README.md index 1ec8c0e..1542ec8 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ NOTE: If you are using a Flutter branch other than the current stable version 1. - [Pre-defined Steps](#pre-defined-steps) - [Flutter Driver Utilities](#flutter-driver-utilities) - [Debugging](#debugging) + - [Debugging the app under test](#debugging-the-app-under-test) @@ -488,6 +489,12 @@ Defaults to empty string This optional argument lets you specify device target id as `flutter run --device-id` command. To show list of connected devices, run `flutter devices`. If you only have one device connected, no need to provide this argument. +#### runningAppProtocolEndpointUri + +An observatory url that the test runner can connect to instead of creating a new running instance of the target application +The url takes the form of `http://127.0.0.1:51540/EM72VtRsUV0=/` and usually printed to stdout in the form `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/` +You will have to add the `--verbose` flag to the command to start your flutter app to see this output and ensure `enableFlutterDriverExtension()` is called by the running app + ## Features Files ### Steps Definitions @@ -971,3 +978,11 @@ After which the file will most likely look like this ] } ``` + +#### Debugging the app under test + +Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turrned on) will ensure that the existing app is connected to rather than starting a new instance of the app. + +NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. + +Also ensure that the `--verbose` flag is set when starting the app to test, this will then log the service protocol endpoint out to the console which is the uri you will need to set this property to. It usually takes the form of `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/` so set the `runningAppProtocolEndpointUri` to `http://127.0.0.1:51540/EM72VtRsUV0=/` and then start the tests. \ No newline at end of file diff --git a/example/test_driver/app_test.dart b/example/test_driver/app_test.dart index b3e7442..871daad 100644 --- a/example/test_driver/app_test.dart +++ b/example/test_driver/app_test.dart @@ -31,7 +31,7 @@ Future main() { ..customStepParameterDefinitions = [ ColourParameter(), ] - ..restartAppBetweenScenarios = true + ..restartAppBetweenScenarios = false ..targetAppWorkingDirecotry = "../" ..targetAppPath = "test_driver/app.dart" // ..buildFlavor = "staging" // uncomment when using build flavor and check android/ios flavor setup see android file android\app\build.gradle @@ -40,6 +40,8 @@ Future main() { // ..logFlutterProcessOutput = true // uncomment to see command invoked to start the flutter test app // ..verboseFlutterProcessLogs = true // uncomment to see the verbose output from the Flutter process // ..flutterBuildTimeout = Duration(minutes: 3) // uncomment to change the default period that flutter is expected to build and start the app within + ..runningAppProtocolEndpointUri = + 'http://127.0.0.1:51540/bkegoer6eH8=/' // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false ..exitAfterTestRun = true; // set to false if debugging to exit cleanly return GherkinRunner().execute(config); } diff --git a/lib/src/flutter/flutter_test_configuration.dart b/lib/src/flutter/flutter_test_configuration.dart index f88d846..266b4bd 100644 --- a/lib/src/flutter/flutter_test_configuration.dart +++ b/lib/src/flutter/flutter_test_configuration.dart @@ -66,6 +66,11 @@ class FlutterTestConfiguration extends TestConfiguration { /// Defaults to 3 int flutterDriverMaxConnectionAttemps = 3; + /// An observatory url that the test runner can connect to instead of creating a new running instance of the target application + /// Url takes the form of `http://127.0.0.1:51540/EM72VtRsUV0=/` and usually printed to stdout in the form `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/` + /// You will have to add the `--verbose` flag to the command to start your flutter app to see this output and ensure `enableFlutterDriverExtension()` is called by the running app + String runningAppProtocolEndpointUri; + void setObservatoryDebuggerUri(String uri) => _observatoryDebuggerUri = uri; Future createFlutterDriver([String dartVmServiceUrl]) async { @@ -76,15 +81,26 @@ class FlutterTestConfiguration extends TestConfiguration { } Future createFlutterWorld( - TestConfiguration config, FlutterWorld world) async { + TestConfiguration config, + FlutterWorld world, + ) async { + FlutterTestConfiguration flutterConfig = config as FlutterTestConfiguration; world = world ?? FlutterWorld(); - final driver = await createFlutterDriver(); + + final driver = await createFlutterDriver( + flutterConfig.runningAppProtocolEndpointUri != null && + flutterConfig.runningAppProtocolEndpointUri.isNotEmpty + ? flutterConfig.runningAppProtocolEndpointUri + : null, + ); world.setFlutterDriver(driver); + return world; } @override void prepare() { + _ensureCorrectConfiguration(); final providedCreateWorld = createWorld; createWorld = (config) async { FlutterWorld world; @@ -133,4 +149,19 @@ class FlutterTestConfiguration extends TestConfiguration { } } } + + void _ensureCorrectConfiguration() { + if (runningAppProtocolEndpointUri != null && + runningAppProtocolEndpointUri.isNotEmpty) { + if (restartAppBetweenScenarios) { + throw AssertionError( + 'Cannot restart app between scenarios if using runningAppProtocolEndpointUri'); + } + + if (targetDeviceId != null && targetDeviceId.isNotEmpty) { + throw AssertionError( + 'Cannot target specific device id if using runningAppProtocolEndpointUri'); + } + } + } } diff --git a/lib/src/flutter/hooks/app_runner_hook.dart b/lib/src/flutter/hooks/app_runner_hook.dart index 95af663..65413a0 100644 --- a/lib/src/flutter/hooks/app_runner_hook.dart +++ b/lib/src/flutter/hooks/app_runner_hook.dart @@ -61,22 +61,29 @@ class FlutterAppRunnerHook extends Hook { } Future _runApp(FlutterTestConfiguration config) async { - _flutterRunProcessHandler = FlutterRunProcessHandler() - ..setLogFlutterProcessOutput(config.logFlutterProcessOutput) - ..setVerboseFluterlogs(config.verboseFlutterProcessLogs) - ..setApplicationTargetFile(config.targetAppPath) - ..setDriverConnectionDelay(config.flutterDriverReconnectionDelay) - ..setWorkingDirectory(config.targetAppWorkingDirecotry) - ..setBuildRequired(haveRunFirstScenario ? false : config.build) - ..setBuildFlavor(config.buildFlavor) - ..setDeviceTargetId(config.targetDeviceId); + if (config.runningAppProtocolEndpointUri != null && + config.runningAppProtocolEndpointUri.isNotEmpty) { + stdout.writeln( + "Connecting to running Flutter app under test at '${config.runningAppProtocolEndpointUri}', this might take a few moments"); + config.setObservatoryDebuggerUri(config.runningAppProtocolEndpointUri); + } else { + _flutterRunProcessHandler = FlutterRunProcessHandler() + ..setLogFlutterProcessOutput(config.logFlutterProcessOutput) + ..setVerboseFluterlogs(config.verboseFlutterProcessLogs) + ..setApplicationTargetFile(config.targetAppPath) + ..setDriverConnectionDelay(config.flutterDriverReconnectionDelay) + ..setWorkingDirectory(config.targetAppWorkingDirecotry) + ..setBuildRequired(haveRunFirstScenario ? false : config.build) + ..setBuildFlavor(config.buildFlavor) + ..setDeviceTargetId(config.targetDeviceId); - stdout.writeln( - "Starting Flutter app under test '${config.targetAppPath}', this might take a few moments"); - await _flutterRunProcessHandler.run(); - final observatoryUri = await _flutterRunProcessHandler - .waitForObservatoryDebuggerUri(config.flutterBuildTimeout); - config.setObservatoryDebuggerUri(observatoryUri); + stdout.writeln( + "Starting Flutter app under test '${config.targetAppPath}', this might take a few moments"); + await _flutterRunProcessHandler.run(); + final observatoryUri = await _flutterRunProcessHandler + .waitForObservatoryDebuggerUri(config.flutterBuildTimeout); + config.setObservatoryDebuggerUri(observatoryUri); + } } Future _terminateApp() async { diff --git a/pubspec.lock b/pubspec.lock index 73123a7..b328652 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,7 +70,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.13.8" + version: "0.13.9" crypto: dependency: transitive description: @@ -153,7 +153,7 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "3.1.4" image: dependency: transitive description: @@ -251,7 +251,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.1" + version: "1.9.2" package_resolver: dependency: transitive description: @@ -452,7 +452,7 @@ packages: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+13" + version: "0.9.7+14" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f8a7c2f..4d9a75f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_gherkin description: A Gherkin / Cucumber parser and test runner for Dart and Flutter -version: 1.1.7+6 +version: 1.1.7+7 homepage: https://github.com/jonsamwell/flutter_gherkin environment: @@ -15,7 +15,7 @@ dependencies: sdk: flutter glob: ^1.1.7 meta: ">=1.1.6 <2.0.0" - gherkin: ^1.1.7 + gherkin: 1.1.7 # gherkin: # path: ../dart_gherkin