From 17dac0f7b3904df208ff90ae700ca2e830d69ca3 Mon Sep 17 00:00:00 2001 From: nturgut Date: Thu, 30 Jul 2020 19:58:36 -0700 Subject: [PATCH 01/10] screenshot taking works --- .../example_integration_extended.dart | 18 ++ .../example_integration_extended_test.dart | 5 + .../example_integration_io_extended.dart | 37 ++++ .../example_integration_web_extended.dart | 52 ++++++ .../lib/_driver_commands_io.dart | 12 ++ .../lib/_driver_commands_web.dart | 168 ++++++++++++++++++ packages/integration_test/lib/common.dart | 116 ++++++++++++ .../lib/integration_test.dart | 82 ++++++--- .../lib/integration_test_driver_extended.dart | 84 +++++++++ 9 files changed, 551 insertions(+), 23 deletions(-) create mode 100644 packages/integration_test/example/test_driver/example_integration_extended.dart create mode 100644 packages/integration_test/example/test_driver/example_integration_extended_test.dart create mode 100644 packages/integration_test/example/test_driver/example_integration_io_extended.dart create mode 100644 packages/integration_test/example/test_driver/example_integration_web_extended.dart create mode 100644 packages/integration_test/lib/_driver_commands_io.dart create mode 100644 packages/integration_test/lib/_driver_commands_web.dart create mode 100644 packages/integration_test/lib/integration_test_driver_extended.dart diff --git a/packages/integration_test/example/test_driver/example_integration_extended.dart b/packages/integration_test/example/test_driver/example_integration_extended.dart new file mode 100644 index 000000000000..79ed2762165e --- /dev/null +++ b/packages/integration_test/example/test_driver/example_integration_extended.dart @@ -0,0 +1,18 @@ +// This is a Flutter widget test can take a screenshot. +// +// NOTE: Screenshots are only supported on Web for now. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:integration_test/integration_test.dart'; + +import 'example_integration_io_extended.dart' + if (dart.library.html) 'example_integration_web_extended.dart' as tests; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + tests.main(); +} diff --git a/packages/integration_test/example/test_driver/example_integration_extended_test.dart b/packages/integration_test/example/test_driver/example_integration_extended_test.dart new file mode 100644 index 000000000000..849ace247e10 --- /dev/null +++ b/packages/integration_test/example/test_driver/example_integration_extended_test.dart @@ -0,0 +1,5 @@ +import 'dart:async'; + +import 'package:integration_test/integration_test_driver_extended.dart'; + +Future main() async => integrationDriver(); diff --git a/packages/integration_test/example/test_driver/example_integration_io_extended.dart b/packages/integration_test/example/test_driver/example_integration_io_extended.dart new file mode 100644 index 000000000000..0358b0473ff8 --- /dev/null +++ b/packages/integration_test/example/test_driver/example_integration_io_extended.dart @@ -0,0 +1,37 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'dart:io' show Platform; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:integration_test_example/main.dart' as app; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('verify text', (WidgetTester tester) async { + // Build our app and trigger a frame. + app.main(); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // TODO: Add screenshot capability for mobile platforms. + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && + widget.data.startsWith('Platform: ${Platform.operatingSystem}'), + ), + findsOneWidget, + ); + }); +} diff --git a/packages/integration_test/example/test_driver/example_integration_web_extended.dart b/packages/integration_test/example/test_driver/example_integration_web_extended.dart new file mode 100644 index 000000000000..210c2dac75ba --- /dev/null +++ b/packages/integration_test/example/test_driver/example_integration_web_extended.dart @@ -0,0 +1,52 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'dart:html' as html; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:integration_test_example/main.dart' as app; + +void main() { + final IntegrationTestWidgetsFlutterBinding binding = + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('verify text', (WidgetTester tester) async { + // Build our app and trigger a frame. + app.main(); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // Take a screenshot. + await binding.takeScreenshot('platform_name'); + + // Verify that platform is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && + widget.data + .startsWith('Platform: ${html.window.navigator.platform}\n'), + ), + findsOneWidget, + ); + }); + + testWidgets('verify screenshot', (WidgetTester tester) async { + // Build our app and trigger a frame. + app.main(); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // Multiple methods can take screenshots. Screenshots are taken with the + // same order the methods run. + await binding.takeScreenshot('platform_name_2'); + }); +} diff --git a/packages/integration_test/lib/_driver_commands_io.dart b/packages/integration_test/lib/_driver_commands_io.dart new file mode 100644 index 000000000000..c4425eccfdb9 --- /dev/null +++ b/packages/integration_test/lib/_driver_commands_io.dart @@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'common.dart'; + +/// The dart:io implementation of [driverCommandManager]. +/// +/// See also: +/// +/// * [_driver_commands_web.dart], which has the dart:html implementation +DriverCommandManager get driverCommandManager => null; diff --git a/packages/integration_test/lib/_driver_commands_web.dart b/packages/integration_test/lib/_driver_commands_web.dart new file mode 100644 index 000000000000..c86e7c893909 --- /dev/null +++ b/packages/integration_test/lib/_driver_commands_web.dart @@ -0,0 +1,168 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'common.dart'; + +/// Manager for sending [WebDriverAction]s to driver side. +/// +/// See: [WebDriverCommandManager]. +DriverCommandManager get driverCommandManager => + _singletonWebDriverCommandManager; + +/// WebDriverCommandManager singleton. +final WebDriverCommandManager _singletonWebDriverCommandManager = + WebDriverCommandManager(); + +/// Enables Web Driver commands for `integration_tests`. +/// +/// Manages communication between `integration_tests` and the `driver_tests`. +/// +/// Tests can execute an Web Driver actions such as `screenshot` using browsers' +/// WebDriver APIs. +/// +/// See: https://www.w3.org/TR/webdriver/ +class WebDriverCommandManager extends DriverCommandManager { + /// Tests will put the action requests from WebDriver to this pipe. + Completer webDriverActionPipe = Completer(); + + /// Updated when WebDriver completes the request by the test method. + /// + /// For example, a test method will ask for a screenshot by calling + /// `takeScreenshot`. When this screenshot is taken [driverActionComplete] + /// will complete. + Completer driverActionComplete = Completer(); + + /// Takes screenshot using WebDriver screenshot command. + /// + /// Only works on Web when tests are run via `flutter driver` command. + /// + /// See: https://www.w3.org/TR/webdriver/#screen-capture + Future takeScreenshot(String screenshot_name) async { + await _webDriverCommand(WebDriverAction.screenshot(screenshot_name)); + } + + Future _webDriverCommand(WebDriverAction command) async { + try { + webDriverActionPipe.complete(Future.value(command)); + try { + final bool awaitCommand = await driverActionComplete.future; + if (!awaitCommand) { + throw Exception('Web Driver Command failed: ${command.type}'); + } + } catch (e) { + throw Exception( + 'Web Driver Command failed: ${command.type} with ' 'exception $e'); + } + } finally { + // Reset the completer and release the lock. + driverActionComplete = Completer(); + } + } + + /// The callback function to response the driver side input. + /// + /// Provides a handshake mechanism for executing [WebDriverAction]s on the + /// driver side. + Future> callbackWithDriverCommands( + Map params, IntegrationTestResults testRunner) async { + final String command = params['command']; + Map response; + switch (command) { + case 'request_data': + return params['message'] == null + ? _requestData(testRunner) + : _requestDataWithMessage(params['message'], testRunner); + break; + case 'get_health': + response = {'status': 'ok'}; + break; + default: + throw UnimplementedError('$command is not implemented'); + } + return { + 'isError': false, + 'response': response, + }; + } + + Future> _requestDataWithMessage( + String extraMessage, IntegrationTestResults testRunner) async { + // Test status is added as an exta message. + Map response; + // If Test status is `wait_on_webdriver_command` send the first + // command in the `commandPipe` to the tests. + if (extraMessage == '${TestStatus.waitOnWebdriverCommand}') { + final WebDriverAction action = await webDriverActionPipe.future; + switch (action.type) { + case WebDriverActionTypes.screenshot: + final Map data = Map.from(action.values); + data.addAll( + WebDriverAction.typeToMap(WebDriverActionTypes.screenshot)); + response = { + 'message': Response.webDriverCommand(data: data).toJson(), + }; + break; + case WebDriverActionTypes.noop: + final Map data = Map(); + data.addAll(WebDriverAction.typeToMap(WebDriverActionTypes.noop)); + response = { + 'message': Response.webDriverCommand(data: data).toJson(), + }; + break; + default: + throw UnimplementedError('${action.type} is not implemented'); + } + } + // Tests will send `webdriver_command_complete` status after + // WebDriver completes an action. + else if (extraMessage == '${TestStatus.webdriverCommandComplete}') { + final Map data = Map(); + data.addAll(WebDriverAction.typeToMap(WebDriverActionTypes.ack)); + response = { + 'message': Response.webDriverCommand(data: data).toJson(), + }; + driverActionComplete.complete(Future.value(true)); + webDriverActionPipe = Completer(); + } else { + throw UnimplementedError('$extraMessage is not implemented'); + } + return { + 'isError': false, + 'response': response, + }; + } + + Future> _requestData( + IntegrationTestResults testRunner) async { + final bool allTestsPassed = await testRunner.allTestsPassed.future; + final Map response = { + 'message': allTestsPassed + ? Response.allTestsPassed(data: testRunner.reportData).toJson() + : Response.someTestsFailed( + testRunner.failureMethodsDetails, + data: testRunner.reportData, + ).toJson(), + }; + return { + 'isError': false, + 'response': response, + }; + } + + @override + void cleanup() { + if (!webDriverActionPipe.isCompleted) { + webDriverActionPipe + .complete(Future.value(WebDriverAction.noop())); + } + + if (!driverActionComplete.isCompleted) { + driverActionComplete.complete(Future.value(false)); + } + } +} diff --git a/packages/integration_test/lib/common.dart b/packages/integration_test/lib/common.dart index 789b1fa54948..b12e1c75d805 100644 --- a/packages/integration_test/lib/common.dart +++ b/packages/integration_test/lib/common.dart @@ -2,8 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:convert'; +/// Classes shared between `integration_test.dart` and `flutter drive` based +/// adoptor (ex: `integration_test_driver.dart`). + /// An object sent from integration_test back to the Flutter Driver in response to /// `request_data` command. class Response { @@ -23,6 +27,16 @@ class Response { Response.someTestsFailed(this._failureDetails, {this.data}) : this._allTestsPassed = false; + /// Constructor for failure response. + Response.toolException({String ex}) + : this._allTestsPassed = false, + this._failureDetails = [Failure('ToolException', ex)]; + + /// Constructor for web driver commands response. + Response.webDriverCommand({this.data}) + : this._allTestsPassed = false, + this._failureDetails = null; + /// Whether the test ran successfully or not. bool get allTestsPassed => _allTestsPassed; @@ -123,3 +137,105 @@ class Failure { return Failure(failure['methodName'], failure['details']); } } + +/// Integration web tests can execute WebDriver actions such as screenshots. +/// +/// These test will use [TestStatus] to notify `integration_test` of their +/// state. +enum TestStatus { + /// Test is waiting for executing WebDriver actions. + waitOnWebdriverCommand, + + /// Test executed the previously requested action. + webdriverCommandComplete, +} + +/// Types of different WebDriver actions that can be used in web integration +/// tests. +/// +/// These actions are either commands that WebDriver can execute or used +/// for the communication between `integration_test` and the driver test. +enum WebDriverActionTypes { + /// Acknowlegement for the previously sent message. + ack, + + /// No further WebDriver Action is necessary. + noop, + + /// Asking WebDriver to take a screenshot of the Web page. + screenshot, +} + +/// Command for WebDriver to execute. +/// +/// Only works on Web when tests are run via `flutter driver` command. +/// +/// See: https://www.w3.org/TR/webdriver/ +class WebDriverAction { + /// Type of the [WebDriverAction]. + /// + /// Currently the only action that triggers a WebDriver API command is + /// `screenshot`. + /// + /// There are also `ack` and `noop` actions defined to manage the handshake + /// during the communication. + final WebDriverActionTypes type; + + /// Used for adding extra values to the actions such as file name for + /// `screenshot`. + final Map values; + + /// Constructor for [WebDriverActionTypes.noop] action. + WebDriverAction.noop() + : this.type = WebDriverActionTypes.noop, + this.values = Map(); + + /// Constructor for [WebDriverActionTypes.noop] screenshot. + WebDriverAction.screenshot(String screenshot_name) + : this.type = WebDriverActionTypes.screenshot, + this.values = {'screenshot_name': screenshot_name}; + + /// Util method for converting [WebDriverActionTypes] to a map entry. + /// + /// Used for converting messages to json format. + static Map typeToMap(WebDriverActionTypes type) => { + 'web_driver_action': '${type}', + }; +} + +/// Template methods to implement for any class which manages communication +/// between `integration_tests` and the `driver_tests`. +/// +/// See example [WebDriverCommandManager]. +abstract class DriverCommandManager { + /// The callback function to response the driver side input which can also + /// send WebDriver command requests such as `screenshot` to driver side. + Future> callbackWithDriverCommands( + Map params, IntegrationTestResults testRunner); + + /// Request to take a screenshot of the application from the driver side. + void takeScreenshot(String screenshot); + + /// Cleanup and completers or locks used during the communication. + void cleanup(); +} + +/// Interface that surfaces test results of integration tests. +/// +/// Implemented by [IntegrationTestWidgetsFlutterBinding]s. +/// +/// Any class which needs to access the test results but do not want to create +/// a cyclic dependency [IntegrationTestWidgetsFlutterBinding]s can use this +/// interface. Example [WebDriverCommandManager]. +abstract class IntegrationTestResults { + /// Stores failure details. + /// + /// Failed test method's names used as key. + List get failureMethodsDetails; + + /// The extra data for the reported result. + Map get reportData; + + /// Whether all the test methods completed succesfully. + Completer get allTestsPassed; +} diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index f6980bc9d6d1..e8eb4f3ce0d2 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -15,13 +15,15 @@ import 'package:vm_service/vm_service_io.dart' as vm_io; import 'common.dart'; import '_extension_io.dart' if (dart.library.html) '_extension_web.dart'; +import '_driver_commands_io.dart' + if (dart.library.html) '_driver_commands_web.dart' as driver_actions; const String _success = 'success'; /// A subclass of [LiveTestWidgetsFlutterBinding] that reports tests results /// on a channel to adapt them to native instrumentation test format. -class IntegrationTestWidgetsFlutterBinding - extends LiveTestWidgetsFlutterBinding { +class IntegrationTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding + implements IntegrationTestResults { /// Sets up a listener to report that the tests are finished when everything is /// torn down. IntegrationTestWidgetsFlutterBinding() { @@ -35,6 +37,7 @@ class IntegrationTestWidgetsFlutterBinding if (!_allTestsPassed.isCompleted) { _allTestsPassed.complete(true); } + driverCommandManager.cleanup(); } await _channel.invokeMethod( 'allTestsFinished', @@ -104,8 +107,14 @@ class IntegrationTestWidgetsFlutterBinding ); } + @override + Completer get allTestsPassed => _allTestsPassed; final Completer _allTestsPassed = Completer(); + @override + List get failureMethodsDetails => _failureMethodsDetails; + List _failureMethodsDetails = List(); + /// Similar to [WidgetsFlutterBinding.ensureInitialized]. /// /// Returns an instance of the [IntegrationTestWidgetsFlutterBinding], creating and @@ -136,35 +145,62 @@ class IntegrationTestWidgetsFlutterBinding /// If it's `null`, no extra data is attached to the result. /// /// The default value is `null`. - Map reportData; + @override + Map get reportData => _reportData; + Map _reportData; + set reportData(Map data) => this._reportData = data; + + /// Manages commands send to driver side. + /// + /// Only works on Web when tests are run via `flutter driver` command. + /// See [WebDriverCommandManager]. + final DriverCommandManager driverCommandManager = + driver_actions.driverCommandManager; + + /// Taking a screenshot. + /// + /// Called by test methods. Implementation differs for each platform. + Future takeScreenshot(String screenshot_name) async { + if (kIsWeb) { + await driverCommandManager.takeScreenshot(screenshot_name); + } else { + throw UnimplementedError( + 'Screenshots are not implemented on this platform'); + } + } - /// the callback function to response the driver side input. + /// The callback function to response the driver side input. @visibleForTesting Future> callback(Map params) async { - final String command = params['command']; - Map response; - switch (command) { - case 'request_data': - final bool allTestsPassed = await _allTestsPassed.future; - response = { - 'message': allTestsPassed - ? Response.allTestsPassed(data: reportData).toJson() + if (kIsWeb) { + return await driverCommandManager.callbackWithDriverCommands( + params, this /* as IntegrationTestResults */); + } else { + final String command = params['command']; + Map response; + switch (command) { + case 'request_data': + final bool allTestsPassed = await _allTestsPassed.future; + response = { + 'message': allTestsPassed + ? Response.allTestsPassed(data: reportData).toJson() : Response.someTestsFailed( _failures, data: reportData, ).toJson(), - }; - break; - case 'get_health': - response = {'status': 'ok'}; - break; - default: - throw UnimplementedError('$command is not implemented'); + }; + break; + case 'get_health': + response = {'status': 'ok'}; + break; + default: + throw UnimplementedError('$command is not implemented'); + } + return { + 'isError': false, + 'response': response, + }; } - return { - 'isError': false, - 'response': response, - }; } // Emulates the Flutter driver extension, returning 'pass' or 'fail'. diff --git a/packages/integration_test/lib/integration_test_driver_extended.dart b/packages/integration_test/lib/integration_test_driver_extended.dart new file mode 100644 index 000000000000..7d7ab6bdb2fe --- /dev/null +++ b/packages/integration_test/lib/integration_test_driver_extended.dart @@ -0,0 +1,84 @@ +import 'dart:async'; +import 'dart:io'; + +import 'common.dart'; + +import 'package:flutter_driver/flutter_driver.dart'; + +/// Example Integration Test which can also run WebDriver action depending on +/// the requests coming from the test methods. +Future integrationDriver() async { + final FlutterDriver driver = await FlutterDriver.connect(); + + // Test states that it's waiting on web driver commands. + String jsonResponse = await driver.requestData( + '${TestStatus.waitOnWebdriverCommand}', + timeout: const Duration(seconds: 10)); + + Response response = Response.fromJson(jsonResponse); + + // Until `integration_test` returns a [WebDriverActionTypes.noop], keep + // executing WebDriver actions. + while (response.data != null && + response.data['web_driver_action'] != null && + response.data['web_driver_action'] != '${WebDriverActionTypes.noop}') { + final String webDriverCommand = response.data['web_driver_action']; + if (webDriverCommand == '${WebDriverActionTypes.screenshot}') { + // Use `driver.screenshot()` method to get a screenshot of the web page. + final List screenshotImage = await driver.screenshot(); + final String screenshotName = response.data['screenshot_name']; + + // The screenshot is saved as png. Later it can be used for golden testing + // with library of choice, such as skia_client.dart. + final String screenshotPath = + await _saveScreenshot(screenshotImage, screenshotName); + print('INFO: screenshot recorded $screenshotPath'); + + jsonResponse = await driver.requestData( + '${TestStatus.webdriverCommandComplete}', + timeout: const Duration(seconds: 10)); + + response = Response.fromJson(jsonResponse); + } else if (webDriverCommand == '${WebDriverActionTypes.ack}') { + // Previous command completed ask for a new one. + jsonResponse = await driver.requestData( + '${TestStatus.waitOnWebdriverCommand}', + timeout: const Duration(seconds: 10)); + + response = Response.fromJson(jsonResponse); + } else { + break; + } + } + + // If No-op command is sent, ask for the result of all tests. + if (response.data != null && + response.data['web_driver_action'] != null && + response.data['web_driver_action'] == '${WebDriverActionTypes.noop}') { + jsonResponse = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + + response = Response.fromJson(jsonResponse); + print('result $jsonResponse'); + } + + await driver.close(); + + if (response.allTestsPassed) { + print('All tests passed.'); + exit(0); + } else { + print('Failure Details:\n${response.formattedFailureDetails}'); + exit(1); + } +} + +/// Example method for saving the screenshot taken by the Webdriver to a `png` +/// file. +Future _saveScreenshot(List screenshot, String path) async { + final File file = File('$path.png'); + if (!file.existsSync()) { + await file.writeAsBytes(screenshot); + } + return '$path.png'; +} From 6adf298cbe0c9f09f12a9c1db438d8f5708edc7b Mon Sep 17 00:00:00 2001 From: nturgut Date: Fri, 28 Aug 2020 13:52:06 -0700 Subject: [PATCH 02/10] squash commits. addressing reviewer comments. making drivercommandmanager->callback manager --- .../integration_test/lib/_callback_io.dart | 62 +++++++++++++++++++ ...r_commands_web.dart => _callback_web.dart} | 33 +++++----- .../lib/_driver_commands_io.dart | 12 ---- packages/integration_test/lib/common.dart | 35 ++++++----- .../lib/integration_test.dart | 56 +++-------------- .../lib/integration_test_driver_extended.dart | 10 +-- 6 files changed, 115 insertions(+), 93 deletions(-) create mode 100644 packages/integration_test/lib/_callback_io.dart rename packages/integration_test/lib/{_driver_commands_web.dart => _callback_web.dart} (87%) delete mode 100644 packages/integration_test/lib/_driver_commands_io.dart diff --git a/packages/integration_test/lib/_callback_io.dart b/packages/integration_test/lib/_callback_io.dart new file mode 100644 index 000000000000..b4826ec8599b --- /dev/null +++ b/packages/integration_test/lib/_callback_io.dart @@ -0,0 +1,62 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'common.dart'; + +/// The dart:io implementation of [CallbackManager]. +/// +/// See also: +/// +/// * [_callback_web.dart], which has the dart:html implementation +CallbackManager get callbackManager => _singletonCallbackManager; + +/// IOCallbackManager singleton. +final IOCallbackManager _singletonCallbackManager = IOCallbackManager(); + +/// Manages communication between `integration_tests` and the `driver_tests`. +/// +/// This is the dart:io implementation. +class IOCallbackManager extends CallbackManager { + @override + Future> callback( + Map params, IntegrationTestResults testRunner) async { + final String command = params['command']; + Map response; + switch (command) { + case 'request_data': + final bool allTestsPassed = await testRunner.allTestsPassed.future; + response = { + 'message': allTestsPassed + ? Response.allTestsPassed(data: testRunner.reportData).toJson() + : Response.someTestsFailed( + testRunner.failureMethodsDetails, + data: testRunner.reportData, + ).toJson(), + }; + break; + case 'get_health': + response = {'status': 'ok'}; + break; + default: + throw UnimplementedError('$command is not implemented'); + } + return { + 'isError': false, + 'response': response, + }; + } + + @override + void cleanup() { + // no-op. + // Add any IO platform specific Completer/Future cleanups to here if any + // comes up in the future. For example: `WebCallbackManager.cleanup`. + } + + @override + Future takeScreenshot(String screenshot) { + throw UnimplementedError( + 'Screenshots are not implemented on this platform'); + } +} diff --git a/packages/integration_test/lib/_driver_commands_web.dart b/packages/integration_test/lib/_callback_web.dart similarity index 87% rename from packages/integration_test/lib/_driver_commands_web.dart rename to packages/integration_test/lib/_callback_web.dart index c86e7c893909..be08b8a191fe 100644 --- a/packages/integration_test/lib/_driver_commands_web.dart +++ b/packages/integration_test/lib/_callback_web.dart @@ -8,25 +8,28 @@ import 'package:flutter_test/flutter_test.dart'; import 'common.dart'; -/// Manager for sending [WebDriverAction]s to driver side. +/// The dart:html implementation of [CallbackManager]. /// -/// See: [WebDriverCommandManager]. -DriverCommandManager get driverCommandManager => +/// See also: +/// +/// * [_callback_io.dart], which has the dart:io implementation +CallbackManager get callbackManager => _singletonWebDriverCommandManager; /// WebDriverCommandManager singleton. -final WebDriverCommandManager _singletonWebDriverCommandManager = - WebDriverCommandManager(); +final WebCallbackManager _singletonWebDriverCommandManager = + WebCallbackManager(); -/// Enables Web Driver commands for `integration_tests`. -/// /// Manages communication between `integration_tests` and the `driver_tests`. /// +/// Along with responding to callbacks from the driver side this calls enables +/// usage of Web Driver commands by sending [WebDriverAction]s to driver side. +/// /// Tests can execute an Web Driver actions such as `screenshot` using browsers' /// WebDriver APIs. /// /// See: https://www.w3.org/TR/webdriver/ -class WebDriverCommandManager extends DriverCommandManager { +class WebCallbackManager extends CallbackManager { /// Tests will put the action requests from WebDriver to this pipe. Completer webDriverActionPipe = Completer(); @@ -42,6 +45,7 @@ class WebDriverCommandManager extends DriverCommandManager { /// Only works on Web when tests are run via `flutter driver` command. /// /// See: https://www.w3.org/TR/webdriver/#screen-capture + @override Future takeScreenshot(String screenshot_name) async { await _webDriverCommand(WebDriverAction.screenshot(screenshot_name)); } @@ -68,7 +72,8 @@ class WebDriverCommandManager extends DriverCommandManager { /// /// Provides a handshake mechanism for executing [WebDriverAction]s on the /// driver side. - Future> callbackWithDriverCommands( + @override + Future> callback( Map params, IntegrationTestResults testRunner) async { final String command = params['command']; Map response; @@ -99,17 +104,17 @@ class WebDriverCommandManager extends DriverCommandManager { if (extraMessage == '${TestStatus.waitOnWebdriverCommand}') { final WebDriverAction action = await webDriverActionPipe.future; switch (action.type) { - case WebDriverActionTypes.screenshot: + case WebDriverActionType.screenshot: final Map data = Map.from(action.values); data.addAll( - WebDriverAction.typeToMap(WebDriverActionTypes.screenshot)); + WebDriverAction.typeToMap(WebDriverActionType.screenshot)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; break; - case WebDriverActionTypes.noop: + case WebDriverActionType.noop: final Map data = Map(); - data.addAll(WebDriverAction.typeToMap(WebDriverActionTypes.noop)); + data.addAll(WebDriverAction.typeToMap(WebDriverActionType.noop)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; @@ -122,7 +127,7 @@ class WebDriverCommandManager extends DriverCommandManager { // WebDriver completes an action. else if (extraMessage == '${TestStatus.webdriverCommandComplete}') { final Map data = Map(); - data.addAll(WebDriverAction.typeToMap(WebDriverActionTypes.ack)); + data.addAll(WebDriverAction.typeToMap(WebDriverActionType.ack)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; diff --git a/packages/integration_test/lib/_driver_commands_io.dart b/packages/integration_test/lib/_driver_commands_io.dart deleted file mode 100644 index c4425eccfdb9..000000000000 --- a/packages/integration_test/lib/_driver_commands_io.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'common.dart'; - -/// The dart:io implementation of [driverCommandManager]. -/// -/// See also: -/// -/// * [_driver_commands_web.dart], which has the dart:html implementation -DriverCommandManager get driverCommandManager => null; diff --git a/packages/integration_test/lib/common.dart b/packages/integration_test/lib/common.dart index b12e1c75d805..0463137ebb81 100644 --- a/packages/integration_test/lib/common.dart +++ b/packages/integration_test/lib/common.dart @@ -155,7 +155,7 @@ enum TestStatus { /// /// These actions are either commands that WebDriver can execute or used /// for the communication between `integration_test` and the driver test. -enum WebDriverActionTypes { +enum WebDriverActionType { /// Acknowlegement for the previously sent message. ack, @@ -179,42 +179,45 @@ class WebDriverAction { /// /// There are also `ack` and `noop` actions defined to manage the handshake /// during the communication. - final WebDriverActionTypes type; + final WebDriverActionType type; /// Used for adding extra values to the actions such as file name for /// `screenshot`. final Map values; - /// Constructor for [WebDriverActionTypes.noop] action. + /// Constructor for [WebDriverActionType.noop] action. WebDriverAction.noop() - : this.type = WebDriverActionTypes.noop, + : this.type = WebDriverActionType.noop, this.values = Map(); /// Constructor for [WebDriverActionTypes.noop] screenshot. WebDriverAction.screenshot(String screenshot_name) - : this.type = WebDriverActionTypes.screenshot, + : this.type = WebDriverActionType.screenshot, this.values = {'screenshot_name': screenshot_name}; /// Util method for converting [WebDriverActionTypes] to a map entry. /// /// Used for converting messages to json format. - static Map typeToMap(WebDriverActionTypes type) => { + static Map typeToMap(WebDriverActionType type) => { 'web_driver_action': '${type}', }; } -/// Template methods to implement for any class which manages communication -/// between `integration_tests` and the `driver_tests`. +/// Template methods each class that responses the driver side inputs must +/// implement. /// -/// See example [WebDriverCommandManager]. -abstract class DriverCommandManager { - /// The callback function to response the driver side input which can also - /// send WebDriver command requests such as `screenshot` to driver side. - Future> callbackWithDriverCommands( +/// Depending on the platform the communication between `integration_tests` and +/// the `driver_tests` can be different. +/// +/// For the web implementation [WebCallbackManager]. +/// For the io implementation [IOCallbackManager]. +abstract class CallbackManager { + /// The callback function to response the driver side input. + Future> callback( Map params, IntegrationTestResults testRunner); - /// Request to take a screenshot of the application from the driver side. - void takeScreenshot(String screenshot); + /// Request to take a screenshot of the application. + Future takeScreenshot(String screenshot); /// Cleanup and completers or locks used during the communication. void cleanup(); @@ -226,7 +229,7 @@ abstract class DriverCommandManager { /// /// Any class which needs to access the test results but do not want to create /// a cyclic dependency [IntegrationTestWidgetsFlutterBinding]s can use this -/// interface. Example [WebDriverCommandManager]. +/// interface. Example [CallbackManager]. abstract class IntegrationTestResults { /// Stores failure details. /// diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index e8eb4f3ce0d2..79de991ef13a 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -15,8 +15,8 @@ import 'package:vm_service/vm_service_io.dart' as vm_io; import 'common.dart'; import '_extension_io.dart' if (dart.library.html) '_extension_web.dart'; -import '_driver_commands_io.dart' - if (dart.library.html) '_driver_commands_web.dart' as driver_actions; +import '_callback_io.dart' if (dart.library.html) '_callback_web.dart' + as driver_actions; const String _success = 'success'; @@ -37,8 +37,8 @@ class IntegrationTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding if (!_allTestsPassed.isCompleted) { _allTestsPassed.complete(true); } - driverCommandManager.cleanup(); } + callbackManager.cleanup(); await _channel.invokeMethod( 'allTestsFinished', { @@ -112,8 +112,7 @@ class IntegrationTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding final Completer _allTestsPassed = Completer(); @override - List get failureMethodsDetails => _failureMethodsDetails; - List _failureMethodsDetails = List(); + List get failureMethodsDetails => _failures; /// Similar to [WidgetsFlutterBinding.ensureInitialized]. /// @@ -150,57 +149,22 @@ class IntegrationTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding Map _reportData; set reportData(Map data) => this._reportData = data; - /// Manages commands send to driver side. - /// - /// Only works on Web when tests are run via `flutter driver` command. - /// See [WebDriverCommandManager]. - final DriverCommandManager driverCommandManager = - driver_actions.driverCommandManager; + /// Manages callbacks received from driver side and commands send to driver + /// side. + final CallbackManager callbackManager = driver_actions.callbackManager; /// Taking a screenshot. /// /// Called by test methods. Implementation differs for each platform. Future takeScreenshot(String screenshot_name) async { - if (kIsWeb) { - await driverCommandManager.takeScreenshot(screenshot_name); - } else { - throw UnimplementedError( - 'Screenshots are not implemented on this platform'); - } + await callbackManager.takeScreenshot(screenshot_name); } /// The callback function to response the driver side input. @visibleForTesting Future> callback(Map params) async { - if (kIsWeb) { - return await driverCommandManager.callbackWithDriverCommands( - params, this /* as IntegrationTestResults */); - } else { - final String command = params['command']; - Map response; - switch (command) { - case 'request_data': - final bool allTestsPassed = await _allTestsPassed.future; - response = { - 'message': allTestsPassed - ? Response.allTestsPassed(data: reportData).toJson() - : Response.someTestsFailed( - _failures, - data: reportData, - ).toJson(), - }; - break; - case 'get_health': - response = {'status': 'ok'}; - break; - default: - throw UnimplementedError('$command is not implemented'); - } - return { - 'isError': false, - 'response': response, - }; - } + return await callbackManager.callback( + params, this /* as IntegrationTestResults */); } // Emulates the Flutter driver extension, returning 'pass' or 'fail'. diff --git a/packages/integration_test/lib/integration_test_driver_extended.dart b/packages/integration_test/lib/integration_test_driver_extended.dart index 7d7ab6bdb2fe..936940a8591a 100644 --- a/packages/integration_test/lib/integration_test_driver_extended.dart +++ b/packages/integration_test/lib/integration_test_driver_extended.dart @@ -17,13 +17,13 @@ Future integrationDriver() async { Response response = Response.fromJson(jsonResponse); - // Until `integration_test` returns a [WebDriverActionTypes.noop], keep + // Until `integration_test` returns a [WebDriverActionType.noop], keep // executing WebDriver actions. while (response.data != null && response.data['web_driver_action'] != null && - response.data['web_driver_action'] != '${WebDriverActionTypes.noop}') { + response.data['web_driver_action'] != '${WebDriverActionType.noop}') { final String webDriverCommand = response.data['web_driver_action']; - if (webDriverCommand == '${WebDriverActionTypes.screenshot}') { + if (webDriverCommand == '${WebDriverActionType.screenshot}') { // Use `driver.screenshot()` method to get a screenshot of the web page. final List screenshotImage = await driver.screenshot(); final String screenshotName = response.data['screenshot_name']; @@ -39,7 +39,7 @@ Future integrationDriver() async { timeout: const Duration(seconds: 10)); response = Response.fromJson(jsonResponse); - } else if (webDriverCommand == '${WebDriverActionTypes.ack}') { + } else if (webDriverCommand == '${WebDriverActionType.ack}') { // Previous command completed ask for a new one. jsonResponse = await driver.requestData( '${TestStatus.waitOnWebdriverCommand}', @@ -54,7 +54,7 @@ Future integrationDriver() async { // If No-op command is sent, ask for the result of all tests. if (response.data != null && response.data['web_driver_action'] != null && - response.data['web_driver_action'] == '${WebDriverActionTypes.noop}') { + response.data['web_driver_action'] == '${WebDriverActionType.noop}') { jsonResponse = await driver.requestData(null, timeout: const Duration(minutes: 1)); From f832b171af747a53503d9910088e8882e8b30e61 Mon Sep 17 00:00:00 2001 From: nturgut Date: Fri, 28 Aug 2020 14:07:09 -0700 Subject: [PATCH 03/10] addressing reviewer comment. mostly name changes --- .../integration_test/lib/_callback_web.dart | 53 +++++++++---------- .../lib/integration_test.dart | 4 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/integration_test/lib/_callback_web.dart b/packages/integration_test/lib/_callback_web.dart index be08b8a191fe..257197e0f169 100644 --- a/packages/integration_test/lib/_callback_web.dart +++ b/packages/integration_test/lib/_callback_web.dart @@ -13,8 +13,7 @@ import 'common.dart'; /// See also: /// /// * [_callback_io.dart], which has the dart:io implementation -CallbackManager get callbackManager => - _singletonWebDriverCommandManager; +CallbackManager get callbackManager => _singletonWebDriverCommandManager; /// WebDriverCommandManager singleton. final WebCallbackManager _singletonWebDriverCommandManager = @@ -30,41 +29,41 @@ final WebCallbackManager _singletonWebDriverCommandManager = /// /// See: https://www.w3.org/TR/webdriver/ class WebCallbackManager extends CallbackManager { - /// Tests will put the action requests from WebDriver to this pipe. - Completer webDriverActionPipe = Completer(); + /// App side tests will put the action requests from WebDriver to this pipe. + Completer _webDriverActionPipe = Completer(); /// Updated when WebDriver completes the request by the test method. /// /// For example, a test method will ask for a screenshot by calling - /// `takeScreenshot`. When this screenshot is taken [driverActionComplete] + /// `takeScreenshot`. When this screenshot is taken [_driverActionComplete] /// will complete. - Completer driverActionComplete = Completer(); + Completer _driverActionComplete = Completer(); /// Takes screenshot using WebDriver screenshot command. /// /// Only works on Web when tests are run via `flutter driver` command. /// - /// See: https://www.w3.org/TR/webdriver/#screen-capture + /// See: https://www.w3.org/TR/webdriver/#screen-capture. @override - Future takeScreenshot(String screenshot_name) async { - await _webDriverCommand(WebDriverAction.screenshot(screenshot_name)); + Future takeScreenshot(String screenshotName) async { + await _sendWebDriverCommand(WebDriverAction.screenshot(screenshotName)); } - Future _webDriverCommand(WebDriverAction command) async { + Future _sendWebDriverCommand(WebDriverAction command) async { try { - webDriverActionPipe.complete(Future.value(command)); - try { - final bool awaitCommand = await driverActionComplete.future; - if (!awaitCommand) { - throw Exception('Web Driver Command failed: ${command.type}'); - } - } catch (e) { + _webDriverActionPipe.complete(Future.value(command)); + final bool awaitCommand = await _driverActionComplete.future; + if (!awaitCommand) { throw Exception( - 'Web Driver Command failed: ${command.type} with ' 'exception $e'); + 'Web Driver Command ${command.type} failed while waiting for ' + 'driver side'); } + } catch (exception) { + throw Exception('Web Driver Command failed: ${command.type} with ' + 'exception $exception'); } finally { - // Reset the completer and release the lock. - driverActionComplete = Completer(); + // Reset the completer. + _driverActionComplete = Completer(); } } @@ -102,7 +101,7 @@ class WebCallbackManager extends CallbackManager { // If Test status is `wait_on_webdriver_command` send the first // command in the `commandPipe` to the tests. if (extraMessage == '${TestStatus.waitOnWebdriverCommand}') { - final WebDriverAction action = await webDriverActionPipe.future; + final WebDriverAction action = await _webDriverActionPipe.future; switch (action.type) { case WebDriverActionType.screenshot: final Map data = Map.from(action.values); @@ -131,8 +130,8 @@ class WebCallbackManager extends CallbackManager { response = { 'message': Response.webDriverCommand(data: data).toJson(), }; - driverActionComplete.complete(Future.value(true)); - webDriverActionPipe = Completer(); + _driverActionComplete.complete(Future.value(true)); + _webDriverActionPipe = Completer(); } else { throw UnimplementedError('$extraMessage is not implemented'); } @@ -161,13 +160,13 @@ class WebCallbackManager extends CallbackManager { @override void cleanup() { - if (!webDriverActionPipe.isCompleted) { - webDriverActionPipe + if (!_webDriverActionPipe.isCompleted) { + _webDriverActionPipe .complete(Future.value(WebDriverAction.noop())); } - if (!driverActionComplete.isCompleted) { - driverActionComplete.complete(Future.value(false)); + if (!_driverActionComplete.isCompleted) { + _driverActionComplete.complete(Future.value(false)); } } } diff --git a/packages/integration_test/lib/integration_test.dart b/packages/integration_test/lib/integration_test.dart index 79de991ef13a..4176c1c2c5e1 100644 --- a/packages/integration_test/lib/integration_test.dart +++ b/packages/integration_test/lib/integration_test.dart @@ -156,8 +156,8 @@ class IntegrationTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding /// Taking a screenshot. /// /// Called by test methods. Implementation differs for each platform. - Future takeScreenshot(String screenshot_name) async { - await callbackManager.takeScreenshot(screenshot_name); + Future takeScreenshot(String screenshotName) async { + await callbackManager.takeScreenshot(screenshotName); } /// The callback function to response the driver side input. From 9917bcd6c1bffe2d6bc51f1f761de849d0a7c1ab Mon Sep 17 00:00:00 2001 From: nturgut Date: Fri, 28 Aug 2020 14:25:46 -0700 Subject: [PATCH 04/10] major rename on all files webdriveraction->webdrivercommand --- .../example/platform_name.png | Bin 0 -> 43360 bytes .../example/platform_name_2.png | Bin 0 -> 43360 bytes .../integration_test/lib/_callback_web.dart | 59 +++++++++--------- packages/integration_test/lib/common.dart | 45 +++++++------ .../lib/integration_test_driver_extended.dart | 20 +++--- 5 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 packages/integration_test/example/platform_name.png create mode 100644 packages/integration_test/example/platform_name_2.png diff --git a/packages/integration_test/example/platform_name.png b/packages/integration_test/example/platform_name.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec45795f38280a634a81d87a69881771d88304e GIT binary patch literal 43360 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

(EAP`Clv-=E2}D3At%1Tuf2 z7}V^v8zwY^;jRhgfzr3`b$`?M-kqq=+l{y=KZGd(NuZr(ePhLS^>Rqs)`%ODm z0qIv363^{|VfyyI9V?{o=_;LNPc$l5l$?jJ)lzT$zFtb*v!7CZLx-|dA4p>1Y#&0f zy9ltm^V)bV9R}7MKozv01=pdXqb^!0Dj;R1PhALbc_gLVvQi5ZOEE1 zc&nKING^$1euUFd5LY=Oo5{RwnCfF*#oKag{LGvixdpU=SR%+)Sg?}DL)74k-EjsThjfYzIc~(4$Y4Jz-=uz z_)N_D<)fXAf`cUN?r9%|J>W?qj`g)x^TCUo~B^t%DEAkan_$ zo5h2QZ@V$Aeu|qP!PIt#o?cG1*&>v{k~Y(J9z!FE6V0s<&F4n2{jJfxk4|~BF6v?` zoK65%9**p>8RR^ndhV29C39qbAUjo9Nid<|s`j|bFAr>)>9)shESoO+3-2KN_D5Gw zMNG*V%FnTv7Wd(1znXcq?khKG)5>UcZx-{~--_flU8j@pGElp$JPr|zAT}B9#}<=z$GQp<`1tDj=r3P zStwy>C#HUliex@@#(IFNai*o8iv>(ZoRQq7mMZH*n6BkmFGokeM!LzIm8y5Fohx;Q z4?HnS!P*B;-6FHv(>aq|jyb4Qyf_;W!x$Ki_mK2-0y$7&t*7z>Z5DFuK8SPEnN=mA zgr6EdUb%pMlJ%+M-Uw64A$`2GtqoMhxJdFqH?^s!CoMS8H3)Z#&DJd&3~Y4rp9Ge> zTp(?L8Ed&H+$PlB6qYw+r859yHNUzRZBeogSJ9n-4dEC zxFA(Y?QOR6nW*8MTuww|UH#MujB>!<)vXF7&iP$J;iVJ@0&kw2=6jAqxe`>@A9CQ@XYOf4O)U8OuBe`_ zxYk$Uqw6J`Gv7nTWHZ?h8s&{>r8)+0q}ZhFJx*QNpjzZaVC_Ol5kU6iGJAGs$S^*Cew9)dqD1Unk{S%XSSGgQm_Hiz=o)KxrCAXaS3ffkyDOQ zTpo0w=&zc!ue-low@k*ZdaTeOFJx?>sG9y+;s8b=m;yYR0 ziZImwT(eB{Rl|K8y}gxHpk+3mggggKwq@R%&ReQAs7dx3rq(7q?9tfw?|n?#oK=~P z$(uh7!6W#0H83zZ72kpeJ3W64^!4?94O(Yro?Y{(b(mb2ye%e_+!AO!s;eazf9YV< z1@pyBn}Xxb=r4^|7c(6&8e1U`A?pXPKGD;2CL6jb;NUDV?pw<|{EuO6yO73Hc6mL2 z=3M;~Lrl$mupRl&7L(##c}VI0y+7nw=HWIUF@N$bc{nC#T+)0vX6!@En1%j8`dV_h z)+p>%GCXpu2a<))Iu&;HRQ%9B{Vax#F82zCX!{tFmGRbM`?-G`!;7*p>&USKk;$8$ zjU9cayXjf-?(A?#R=Bx!jlkc2UfG za&o~BP2CwP&~)>oF*)0!E@XNxIu?Fe%L~^>wo^l8`=8sxjF)Aa(a##M{^V5nCH$v< zUODr=rYrZyrf$m4eflnz+>3uMayCk;>t|qf%zbKX^E3g&Qn}1l#Y1WSrHONadpv}E&oZD9L z@$g5P;*bmZW-2mEu_b8}i-uB7>vKmNJ1<)zOO#O9G+R`|c|u3#8D>uS+%W&iST zgAKLpr%3#>`E~#G0`mG!8CjvV3l`R1U-lNf&9{WWEUS1Bz+ufT1aSBt4F@h{Vd@%x z1lOP8=T-fm8wd%t?KrYJr1b}%e~&b2|3-?x$%g&&SV7ZuYrGejY26Y6NC+SyV2KTq z6p-nQIccx{`qt_bb<01$tO(QG^v`>g1x+_hB{0*v zCH^}=qU+p)PaD?)2nq1KJ~IN}-EdX`Bm|K7@4!<6Zrboj0XJ<}LcmP|ZW3_QhBpXg z^|~blkPtvZ5F|G+APAD{mJmSV{}>WseK^*Uyw;B~-z4?#K1(e`op?E-_Q5Fi_$6-X z$=^zTmb&u8KA4GRw42j~5!Hjqi6=4fnTBp^M=zlIg&dO-yJrsm`p2uke@ZR=RsMPY z5BE8o2mM`zxc7=;@Xti|tYlg+p1?w&eQF!=0~%}hXs&kc@|jKly!KXqc*~KEF7uxs z?G5|q%U@0Z?HV&dVVxX+yC$9v#YWkmHQ{Nu-u+sWTLv>9zLet)2R>(h(wo}QZB zd0s=%mDxnQz~M)l<8L$nw6{;+w0WBoxf-tRq5H-L=kGbnF0{vChlm=gs;Zo*=dbUY zz^Z2&q8(a9PmbbcRzL4Vv#EB_+6}^&@5h;Hoh&OWOHK3na!;80+}yO;RgalGH`!lO z&*pJyka&nCCxJ)JkH1iB$?AUwU>HDXbn)BV}8k&p+5)2b50^v^E-L2nOIZ=r7 ze!ofBgCVnGhFBWQF1a)O(cfMLK07j1no`i^Tb_cQwCi%%DW=%=>E&4!-|wFWk-TLR zcWtp0{fUhaHu0SrP;+dI_Vmv$EL7wlJ~!4CC+$P)XlK(hG9-D6jRo{A#k%tLRjO)g zYN_cg8iZG=T)VtLGPkfO(@mU@o^6G$C_^~|!~xZEELL5`rLC_+?o`)!Po5mU-U=Gx zCyZHLltzfGyFS~W&avo2KGbAaY!Lmkky3kKk)uigeXuQETO;ncNelP{b~*e}D)v_A zD``VNzp};5bb1Yo96V+Oy_BeEudW?1-kk%hd%8NOLMul9G9o3EGn(RF*!=EB9ZH&@ z!)z1XdL+-XpamskG)q9PsDOpoT^A@wo-ch6p6L_kG&NAB5G^C_FK>+x4Y9;H3{6(i zhj1FH!~)xLO9uXC{Zo1CV(HdIB{K^Pi-msI43o!F6TJm_)mB{yMVrzS5n@Um2nvTh ziRY~tqSV}bN1`;y7qd;%PhF{Bnx9~PjZks{z}EZ{wz-((g;T>J&z?P-plF|hU!H=E zk!uipw7$QXtY&tI+Y_)?q0o#2DlSfo)5EE0Xl#AGLZely>$?{UfN zcjFEWe|aF%aZmVg($<}#BVZBNwt+Ht5;vU7M@vlTT&86}%o*-pr-OtrG%Y?D-s)9grdvJTvPe0&@w?V9dqSy=J)sdRJ2 z-EYdE<8u>4B@vxz{aRpFS~-5%vn1>~FyjFYFT0?H$>jbLSJ#gZcO+{Ec$Khp#$LyH zx2q9Lt5h0uE%KCMT&yJU~qEb^~ z9j^`JZ1P7R?lo;1O@yL1?>Y0LePZ>V<*LkOx8vCn?jc7 zEYF5XC#$%cUWhu_Gbyqxe(<0=f&%DOy&b`9*O2>GA0b8?^dWevtz1<9Tgo*f#D8Up zh{rC4Mim178uTZ5#_zxB=tIOnGXUQx#>tv_{cL2MuB8TLw^%8=_vQyvRWjUu1}jlU z9#HX~sTb2AZIv-xSz+O6fCM{|PCF_C;t{2h909-#)pf#qdp~vSvIfl$WWcG(;_2lw zgz6kC2e^!5L!{e$FuwH2tIR+y*kHM-IRJ_RWip^@4Q=F5 zl^-7&GNEUNAMW)ANTl)so}%Wi>^V?soOb54;cOWuL;>)Jv6kQX^C5qdf_av)A<8af zz9$|s2h&NA7Cv-GpUkRdSwu(eKYh%QAB|wa-^AnLQ8*+BSEHaSZ_kN8*tWX`u&>Ye zm*Lbmi4J_QF#8;1-qhCWiP_!x{kR!A4cIxahoA-wAT91cv-m|AlBB!Pm)TPZ$d=6} zAkhHuX@0%d{h;eAKGZfoi|1f4gl{nrbfYA)x_D);na*iGuc3iS?GRIChA6OK2N1i%=SOgFrnZR5wz4_Mse!RVsISU~4#5Dn}Fe<}*Zrxm3zd^SBr^&YocT*4a7S9CN=MVt@Ixp*L;>|QofXl2J}@xApYKWz9ajSIh;?6H zo#tsTskO_}!u*o}@cDp|9s?ek`ol9Bql6JaJ62WxQz=q0I?CnbAa|_*x}7HE`ui~H z=D(lpH3y`d=n!-={BY1ME&s`+x;@(RhQ*DC5m%`ihuyp17$x!rL4Of{QtGRvlYhJM zhcgki*YwO$2zOXSZuo<+-t@h+(50~h+WsEE1<|0B!OT=j+f+HFwv*rwcyMI#TMT6~ z;LhT3I1Q*8V?ZNu9^1`Kb!Prf0}~gonXx?S>eKI>XyL$OP#^%Et^2I(Vx{53kHk{8 z9+6!76rNIJ@Qqa=!;e6_4k$~jxOTLSG{>p-lsI*k0NYMB_rRB&`e1 zRN63nkA(7#j}PbQL)xn};8Oo|UjZ4`mUPpZw$Np*f{0^|DmwV@%(Fq)q_dELqI# z4CPEE9zFZ`YVR!2b+{pxmmLxn%)(I$K)3t{+_HlwY$>_bp_+C}w2vi6G1eG-F5*Lf;-3L`qWN-#)*k**fs;X0>y?;)1D3H(C<`Bwwy%;n< zQh3jyiq7k=fL|Wa3B2Q69s!J4dNAlvz~EiaP=-|WnO8mEYJ%o30n!CZB@vKZK|MP+ zH@78_FN!)O;Ulq{AnuW>qfImy`W(B;VU+If?h#iA$NECd;UW2H`1A*1zW2O_%NNV2 z&$sbr6Y*CYcq}B3IH`T>Hc-GVLjjbUdiOI+-!nCW3ZeMW_?g+!R?&-W2`67 zDwTpWh&i09!|NUT-Tz^DI4S<+ z6$ZIR?pwLs<&z(PoaHMPX+!WkLg-6Ee96Kev7tAR&{;sLr`8Y?^q5(cfG*m*0>8<= zySPDCj}_E&q3ZN(UmD=BAN5!JaeeQ<1UNW3pD;YS5PZb~Tmp<{8A5w&m@uJ8j1`! z0m07(ij`?y87p%xj1u51t^{=a3~-6^*}XN(DJu@!cI|&=q*(ddgg8Ie<%&>PThEF7 z^Lp3br32K2#LtM~7wh?g|MqFxJP}HvPkaZH=gggLRf2hYbumpa2#x)k-cF(Oc~BX{ z(B%oJYxmnL$*OLDqCmQJG<8TH&@XKT^haNuzSS{udP?_3y)K)Dr z-BA>>u&|b%oB|Vh`9RD;wcH*}Go6`psSMVDJI1q9iwpv#RReOdPLi*cEolXImC7-C zOSeYnZ-h%=&ofx~@xP>^o5D72MoEKSD|z#lSymlUhjpt=SbZt3WT0mGS(f4Kbe%|c zR#sLI9HgE6M2}CoBOrfpNbM~v0IK;_eNnpqz)8T{#weg7Je6-+V?C@L;G^*%{qn^X z{Jh#{^2vXhO2WJ8X#8DIPtORZneP16RKO$Hg8X2w!?_9=`~&D`gFhnlMXG$1bd=_!1+S(Z81 zWtI)2)Oy+wvF|{*wrq&eUAUI#W2!sf-LeQWqe3n^B?x2#%7N2gf+rm%TV)dMil@Z* zzkDVn#Q05n>igE!&G*_v;UL(qCfweDC0$0EV%eX<^_~Mj&_KYTc!1xN;KwEBV31%t z|NYm`nscAd`gC70P>VRL?#f-9N!EZlPzf0sKqDQ`yb#s$?C4qVPrnO04}W|F2ilCU zWTg#3zT`a`Z-r%c)rO!0zdqqdabuKmhMq2-x6CLZ=y5#a5E;Io4J((%^8oQdHT^+6 z&US3XE|Y9*Ic-X~$zpy6TjEOS7_9VBx%u~Bdy36 zdnJQ}7N?8`>bxOZB9$-V_(ND?F*2kN=miI&Ll*k^N*nwg0O80~d#W^e`hjFyv2*Jz zKLhU%xC0bJjpuqMz_VCqn7$5EplWx4@L;CLb^=*UBw$^fbS41(I0+;!6d5F&tB z53J#>Ek}K2U2Y|2g zlTCh^!@E^MfeOwwDhyfty~=N*ms;joVDH~ABXuG2?H0wZER)t!w=Qq)Od}0sRTohp zhd63w?`M%9Z;i^%2-S)ZRcHa)yT+0c0GykjYJ`${*S<*Q=?tYnCYu`W3dsw0Fo@QF(ROKH4erDJtS^jvYP}A5LE{ah2@EFme+rT>y#8`WzGsB}zS7&(cjfRJX9!lL zS;x|U<)$$~Q^6g$>lhH+l)Y{U!F~E0h7dqv!x93P5U_+GzOG|HaG(ALEeIeXfP^4O zZg|80-ypdd)2P33Ex=lj1U$7dGXkDkw}b!^0!aK1&F_j}jjPM{Kj0Q*7iH@F#}fX6 z+i2G@Ex3(#!?^{J5I{n38|^v<1h>&{(1HLG0!Rpgy{8eLI4RNL6BSrfgnh3c*Fk_NQ4dir)#|gmD>&b3yS&cmJmQf0Ez#h#r%zl6B1x} z!xREq6VTfKt!pU|#nzXFgoKL?&;5Q?@WZbg9ws20e_296HUhHwzeP3zg87$Q3DnGn hDFg)bUm}=ID}P*M{;l_02fwx9dDC-+e_X%+zX0W;mb(A| literal 0 HcmV?d00001 diff --git a/packages/integration_test/example/platform_name_2.png b/packages/integration_test/example/platform_name_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec45795f38280a634a81d87a69881771d88304e GIT binary patch literal 43360 zcmeHw2UJs8+b)g;9Sbw#2r42fAS!L7N)0m>M4Ao)QlrvDN|YV~1S_L73DO~`C`gU8 zPy$3{=p`a0Kp+u?L|Os_2qA&o9m@C5_pf!=y6aze-TVJ*g~duvPR>5r``yp`ywCgQ zoSi3^O$~Pcc<@IdA)(!d7tj4EB(y6`NNBsz_HAI#2qLFMNNAIS+u5_1&E4H@2nn5t z4T;q@+I;1w3o#!W#I~QZx#p1_m2xibr@tP-_irh>`0FpYc9$JDe@SeA+WRye^3lfD zsK`|5n%l{1*S6pHF#sL=sirUpOd`~={vJ>*d_zBo5OjNws@g>aQ~M9p^U8#N9ZZ@ z(KPYUpIvR*&1Lgb4a-9=TNXBno%DXy`Ql0ILFZ`AkD~i7#!8vpe|z)k?whZ++|Akg z*~#!yt6bKb*Vn8%N@R3f)W@cEl^kxA3{1=){=+jsgig7^mFiBQUmyRu$_hP2!he3t zme$w_^*O~6bs=B>`sa9c@*dV>HGoe(f4oUdcP}jb&bWZi?8e z^H!8x2d6SW=MX)DdCY5D8W};aq>Z2NVfSLbH8U!4vA@_9&E#@Bt;k7bWr*S`yn#lE06&KW>mPl&Qpi+XUxrMV8}nTQ-gX&+nkV&&z(; z{*OIEIE#e<;eYN(N(UJFk3aq99by0RN=WE=n&53<4}n?MK=HqW!~2jIHipqQ=0lXB zwFAEh^;F`uSGV=qx(iA4>Myaa)S=Kjr3vk%iOSjDnc#1aXEaHW>1VgecU+KNSR`-e zq9Uic?>@)of)b=giFTM0cgdjG=NP*cGuad`PCG2tCq`!w#8yvwNPdg{>PAx6isdkwTUXB}9LGl5uK5SzWnPNT)Za2}A}zm7pYefJG&j~b|0}j&N_(+k z%^AZGjT*+Q?FMD{*jqLWqbrZaKbVXgYYo*(X^8YpEhn;!psNQ6ow+071T9%@_YUJB z={|x>J)Yt~Bx@!YNX>T-jEFBGv}UP390nIj;ZCEoNh9%r#<}RpUg`71P(9*9Mn+3*S;dgj@OqBGt-w_0E2? zl`Zzkhtt@H5Vf=nvX@ILnLhHc4##{S-{Wf)=U#3+vP662 zc1acnrJ#MgKhR78g7;C*+*`uXW~Rikqd4Tjyc~}2Ty96CnYwr59bJtfwPX`pn#P%u zD9*sX9`3-6bWIPqjJU9S<#@uA$jCCr)iP9CV4$md{!~k#Lci*@R*Uk;Z1tE)g2(bj zl$4c~&)0ztU+Kya1UQ$braN@u(QO&cAQLqM485)D-fe7*dhy+N2^mA0mESFbv#69J z_p0Kma;bpb#1dx%T}ZWaFqs*8y3x1L5i_1Qa&)4q__f@D!Jek50Anip#?jx_>@`G+!NxuO)wbYn7eXxJ{O{aO$%&J6#e(u^{A292*St(j_0GT=kxYPn;z#)ZMH4R#h~zK+~KCzN)WM2#-zrBY^G5;t%_s|C6EKZFBMiV zo^DN9Qn8n6_6snf)=>JBC)C9VxEuDaWH?#>2FD%$PReTeX7%W4TyAqVv_i{XC96R; zv|5fgI~Dr}r%W!FU81t|S=VfQm z%A^meWTp`M<|jU0JZTHG>6M$DAgM~OSUvpo5Y3@lE4|CV2L3JqviLW4&R-S-$%6o- z^~|nNur+8TccH_ssA#@|Ucmp*%tobFshIDV-t;_)^EgmQ%e0CbF(O7!b@@Q2r{BQo zb%>JJ?FF7rtP)-aY_dtM9Gk(4(9BYywL9s;TR7#%4ISnd$!Yi-Zn+98sie~hc5`*H zcELS`*hapv5(@KQpo^=oPG3@XhQ~?8PYzWvq3E~DF=mz8w)qZ>Laak)EwHYc4$Kc- z`_gYw$}qwAIC4omgq`h*F}89o+B_9KG?@JrTXV*!y!F}pk7NgKXKP$7H01rIUmDNs76kC#i?$4KY+N@pMLNe@6nchwsw!;#u@67Z4E zA@&HqZ`e>M-vePT1#e0WvPVEr8GtbOp#vMNCiJ_}iF>vp@FS+gz zT+N+~MAmEP>rTr@dlG0N-x9H6rvj=+x}7zdeegXkN`6*x0g#aMmSsD9jjal#F#yHb z;johuh7K!>wTH^GQb1P&Iu;Czhd*_2r(k6w_l(Q)wc%ZH2?XtHcNgyJ!k4XCdRt~c zHqKM`#V}Hd^TwJtOkDM5adM0vxNwjhyOC+t#$l&=k6+Pc$H5;%MG?l7kPq*xGl){c zr`XeseB;JHwjJb;KJ3AN(8%k$r*c&W$6FGG;7rX{(#kQOlkS0o1+Kvtu0(tj={I;| zZ1X+$_2qjvi}etJ)ZMQ1kRzj&F=OpTdBEK}o{TI=%twkwR7x9m1YR~fyzrhX)TmoG zAG|1BKQ1AbR;s$WrkyxXEm_(*RqNAwF4VX6To+bY!ls=qo=`ejm##fgxk7LDAUnV` zFWASA#G54{P9xt)nOG-czc!f+5armVL3Y>*tBJ%*6fhjZY|V_q{{ zlWtTh;B~hNC;!MYGAr#{P_cLaAQY}ZO|sNNopqhNL3Ap1%wJZ)Rh6S4OYI@%m8)?) z>_Oev5S6v)Zm4*svf?^&;$qGD)@9mJ1OL&7YV|Q~6Hy~bB;rg4e#uIXU}jB;*U-yB zeu)2(9%TgLxY_b8Ra@+o>9Mpsfv)7B4*i!RU?kq%DYw`*P~!!zJe5yAZHEk{&ZhAQwyD?Z#^DbYG(EVRQK%Gn3&g zYE*`t15}%W^E(E^c^F&>Jy5&^jgR(=aTLVL12BRk9?1>pKELkEtQj8GWGfE zd-u00=90?OeKw(t&KfocvxmS*1C(p~cmiH0hIQ@g9r7u*F+}tF$aaC=GY7O8KxCCO z7`KBOR=xa%#6JDijcck%?+Z{%1%=RGm-{oK>EYy3R-5Q>xHKCFQwxWnMht+0a2A85 zmi^LLJ2xyDIU-qGTXOQtogr$o4vj*0>kr>6`EHsL8nhslAS&7o6EC1`l@ynkH^xt) zZ5(EsJ(JUl973$EyzL@dL^15ig+J6H_Y!@n!v=oYc`4PCMfJHq!>T+CZY%WGL2lan z#K&Gh(dGcwOpn3t4S%am1%E*=eDa8FB4V6{@B;>95oJcJf z6L!09C^I3F%$v#P<8E7mDkQHgX!f%i%>75CbaPYStT0b6NjpH4z>5qo3r%qGS$OItVzp#u>gEEFtbQO7fM4FP$vN8(6jV zd*gMEoivw_Xf`zPr9v}Glxc^o@s!xRk`Hn@yG>xE62sYDG)fL;s!qTo_i4vyr9vG{?TQo zU%SVfcL#{oi5Xzus-Es5YDB9ke8~uH!z@>yp|U1(X{``9T`MwgDE*OVNPXk0H}-MO zyv4c8Bo=fb$20lX&(Ae^M|Xd*NKJe6C}zYN1AVa)*}~UMWqkNV~#uNs;|06l>9M zWu>N6gb*GFbDis6MzF4MJbr&D4N`!+MeJa)k7m}mS4p8`W5#4o+rFLJmr>3`Q)0z< zst<`-yVC_iwXLm|+FL?1O#z(OY~br$w^^&CxE!@2BQZ8n_!@(gsvER3E!+=x)IT7|-y zGuy*fH|}IC#^eQz0@s^lc>Q{)7rIM-aOl-XeK|;t&&?~FkECKd989ji@@E+3v(cRFTdn%@r%?wB`h^mUFqYsyU zFzS}KJfE83gP9-5!hv?Z#4WA>1du*??6Bu@ZDL2@kFF7Dwpr)Gn za`q8de|0?FIM-NC&_&AL#+^NXiqSYrbsxVAZMMYnj2TCG8Kp;gF8lqtJ& zL{CawJ{C9sT0O=skQIG1bNq*$PVy7|oTmp1=KPhofiztwLx-gb=l*hCH*BBd5)(&b z@jgTs1zEDx3B&O<68$=7Fhi?AI`h|hnd+Stb{CNeg^0^}U^GzeB=`P`vV>GdN*eM0 z#PS$A_Uo&uv$O-ho$;T)$2-lz+SgBj!(wm#{7`du3?Z}xI`gm=%cvR4U#b4?Sb_;d zQdDSrklo@qv85Oyid^`*IO)m#^WD~lx$%{ocZSO=MK}Jjvo12SoO~lNbgT7`+YCf| z?-@vTr6=lo_2+vY?AI0DJMR=+jJ_xOp`cQJAxZE1v)k&P>DpV0tFr^So;cB*$TgZ^ z$qBD$n6IK^QjF-4+!S7`QVivM(u`62;Q?y8$4F~>zd$h$7fWZbXKuB!BoW2|d)$kDL$==0m8`ifZNix87W|8(86nuSTqZ}L$2<9WaSp~e{iq70x0(ikPpkI5d@d44Cp_6e6> zyxi7xGkR<$yln^rwYAdmmuh~+%&mt0P+rG@p4}kj%d@>oN*c3pT^Nm`DN>k0<7X2} zJy?xq-;F?)*M*hyN^R<~O%eNQCD&|={!E$O?{0I|8%`(>z)sm37fPRwRbs>tdv_ni8s(EAX- z)YkiZRN?Je@=jWzR;;wupt@zB|KQPdc;`2SEf(S&VufZ-_cA2@szn&hEK!idE!K5v zVtO~-(7qmKhs$dbgoz>%dHNk7h|$VY7cT=9G+#dj?|MVp`4YW5+@CrlqIfu)d^3tW zF8cD>NsL~Jl7uHk_c!?|V={Yg!Dn$uEdQca#H+nL_5j)W+_~C( z$rBy@FtT0n%IYv*?Ax?f=GwDgTv&D|33OQ-b_S=^0}cH;)EUcNx-tY3^@6tCH%!ic zZXLzFrh@Y^E|JR9&Qr<@J)nARAies_-5NNlzaa1Gp|^TV55CT`{A48qdt37DJF5@Y zW=g^7jzP><2BMpP%fJ`zKNg6HczRfp`{Ck+hPdI*3wjHgUn`-ArP+_ixC(Xka|CxY zXf2IwoO2b0UnyA8nQ?fyrDdeACmr5u&gs%FxZk1=lVJod>F;4Ebuc6xlgolGXj3@s zzzeQA4)M_=41##mS)$n9-7!Iz1IR3r+>a#Wr(?!ysi}-pdR=)4IfXha5zS&b$%=P= z`i@6IbV~F+&OlIG@*yKu>G_r@3NMNb>PcVIJfzv(A`yhu7%fz)Y%q*iwb+RmGlv>E z*qO|0GvB&1gS^Kh5XufDd$!}tcu94U-r3RA25)Ln{nmkOiJ`cz=vw5%9Rrm>QPz9br@>TEi1D=GIz#)6nM|!oP

(EAP`Clv-=E2}D3At%1Tuf2 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 literal 0 HcmV?d00001 diff --git a/packages/integration_test/lib/_callback_web.dart b/packages/integration_test/lib/_callback_web.dart index 257197e0f169..758b95d7d329 100644 --- a/packages/integration_test/lib/_callback_web.dart +++ b/packages/integration_test/lib/_callback_web.dart @@ -22,22 +22,23 @@ final WebCallbackManager _singletonWebDriverCommandManager = /// Manages communication between `integration_tests` and the `driver_tests`. /// /// Along with responding to callbacks from the driver side this calls enables -/// usage of Web Driver commands by sending [WebDriverAction]s to driver side. +/// usage of Web Driver commands by sending [WebDriverCommand]s to driver side. /// -/// Tests can execute an Web Driver actions such as `screenshot` using browsers' +/// Tests can execute an Web Driver commands such as `screenshot` using browsers' /// WebDriver APIs. /// /// See: https://www.w3.org/TR/webdriver/ class WebCallbackManager extends CallbackManager { - /// App side tests will put the action requests from WebDriver to this pipe. - Completer _webDriverActionPipe = Completer(); + /// App side tests will put the command requests from WebDriver to this pipe. + Completer _webDriverCommandPipe = + Completer(); /// Updated when WebDriver completes the request by the test method. /// /// For example, a test method will ask for a screenshot by calling - /// `takeScreenshot`. When this screenshot is taken [_driverActionComplete] + /// `takeScreenshot`. When this screenshot is taken [_driverCommandComplete] /// will complete. - Completer _driverActionComplete = Completer(); + Completer _driverCommandComplete = Completer(); /// Takes screenshot using WebDriver screenshot command. /// @@ -46,13 +47,13 @@ class WebCallbackManager extends CallbackManager { /// See: https://www.w3.org/TR/webdriver/#screen-capture. @override Future takeScreenshot(String screenshotName) async { - await _sendWebDriverCommand(WebDriverAction.screenshot(screenshotName)); + await _sendWebDriverCommand(WebDriverCommand.screenshot(screenshotName)); } - Future _sendWebDriverCommand(WebDriverAction command) async { + Future _sendWebDriverCommand(WebDriverCommand command) async { try { - _webDriverActionPipe.complete(Future.value(command)); - final bool awaitCommand = await _driverActionComplete.future; + _webDriverCommandPipe.complete(Future.value(command)); + final bool awaitCommand = await _driverCommandComplete.future; if (!awaitCommand) { throw Exception( 'Web Driver Command ${command.type} failed while waiting for ' @@ -63,13 +64,13 @@ class WebCallbackManager extends CallbackManager { 'exception $exception'); } finally { // Reset the completer. - _driverActionComplete = Completer(); + _driverCommandComplete = Completer(); } } /// The callback function to response the driver side input. /// - /// Provides a handshake mechanism for executing [WebDriverAction]s on the + /// Provides a handshake mechanism for executing [WebDriverCommand]s on the /// driver side. @override Future> callback( @@ -101,37 +102,37 @@ class WebCallbackManager extends CallbackManager { // If Test status is `wait_on_webdriver_command` send the first // command in the `commandPipe` to the tests. if (extraMessage == '${TestStatus.waitOnWebdriverCommand}') { - final WebDriverAction action = await _webDriverActionPipe.future; - switch (action.type) { - case WebDriverActionType.screenshot: - final Map data = Map.from(action.values); + final WebDriverCommand command = await _webDriverCommandPipe.future; + switch (command.type) { + case WebDriverCommandType.screenshot: + final Map data = Map.from(command.values); data.addAll( - WebDriverAction.typeToMap(WebDriverActionType.screenshot)); + WebDriverCommand.typeToMap(WebDriverCommandType.screenshot)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; break; - case WebDriverActionType.noop: + case WebDriverCommandType.noop: final Map data = Map(); - data.addAll(WebDriverAction.typeToMap(WebDriverActionType.noop)); + data.addAll(WebDriverCommand.typeToMap(WebDriverCommandType.noop)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; break; default: - throw UnimplementedError('${action.type} is not implemented'); + throw UnimplementedError('${command.type} is not implemented'); } } // Tests will send `webdriver_command_complete` status after - // WebDriver completes an action. + // WebDriver completes an command. else if (extraMessage == '${TestStatus.webdriverCommandComplete}') { final Map data = Map(); - data.addAll(WebDriverAction.typeToMap(WebDriverActionType.ack)); + data.addAll(WebDriverCommand.typeToMap(WebDriverCommandType.ack)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; - _driverActionComplete.complete(Future.value(true)); - _webDriverActionPipe = Completer(); + _driverCommandComplete.complete(Future.value(true)); + _webDriverCommandPipe = Completer(); } else { throw UnimplementedError('$extraMessage is not implemented'); } @@ -160,13 +161,13 @@ class WebCallbackManager extends CallbackManager { @override void cleanup() { - if (!_webDriverActionPipe.isCompleted) { - _webDriverActionPipe - .complete(Future.value(WebDriverAction.noop())); + if (!_webDriverCommandPipe.isCompleted) { + _webDriverCommandPipe + .complete(Future.value(WebDriverCommand.noop())); } - if (!_driverActionComplete.isCompleted) { - _driverActionComplete.complete(Future.value(false)); + if (!_driverCommandComplete.isCompleted) { + _driverCommandComplete.complete(Future.value(false)); } } } diff --git a/packages/integration_test/lib/common.dart b/packages/integration_test/lib/common.dart index 0463137ebb81..b3cf08894ef2 100644 --- a/packages/integration_test/lib/common.dart +++ b/packages/integration_test/lib/common.dart @@ -138,28 +138,28 @@ class Failure { } } -/// Integration web tests can execute WebDriver actions such as screenshots. +/// Integration web tests can execute WebDriver commands such as screenshots. /// /// These test will use [TestStatus] to notify `integration_test` of their /// state. enum TestStatus { - /// Test is waiting for executing WebDriver actions. + /// Test is waiting for executing WebDriver commands. waitOnWebdriverCommand, - /// Test executed the previously requested action. + /// Test executed the previously requested commands. webdriverCommandComplete, } -/// Types of different WebDriver actions that can be used in web integration +/// Types of different WebDriver commands that can be used in web integration /// tests. /// -/// These actions are either commands that WebDriver can execute or used +/// These commands are either commands that WebDriver can execute or used /// for the communication between `integration_test` and the driver test. -enum WebDriverActionType { +enum WebDriverCommandType { /// Acknowlegement for the previously sent message. ack, - /// No further WebDriver Action is necessary. + /// No further WebDriver commands is requested by the app-side tests. noop, /// Asking WebDriver to take a screenshot of the Web page. @@ -171,35 +171,34 @@ enum WebDriverActionType { /// Only works on Web when tests are run via `flutter driver` command. /// /// See: https://www.w3.org/TR/webdriver/ -class WebDriverAction { - /// Type of the [WebDriverAction]. +class WebDriverCommand { + /// Type of the [WebDriverCommand]. /// - /// Currently the only action that triggers a WebDriver API command is - /// `screenshot`. + /// Currently the only command that triggers a WebDriver API is `screenshot`. /// - /// There are also `ack` and `noop` actions defined to manage the handshake + /// There are also `ack` and `noop` commands defined to manage the handshake /// during the communication. - final WebDriverActionType type; + final WebDriverCommandType type; - /// Used for adding extra values to the actions such as file name for + /// Used for adding extra values to the commands such as file name for /// `screenshot`. final Map values; - /// Constructor for [WebDriverActionType.noop] action. - WebDriverAction.noop() - : this.type = WebDriverActionType.noop, + /// Constructor for [WebDriverCommandType.noop] command. + WebDriverCommand.noop() + : this.type = WebDriverCommandType.noop, this.values = Map(); - /// Constructor for [WebDriverActionTypes.noop] screenshot. - WebDriverAction.screenshot(String screenshot_name) - : this.type = WebDriverActionType.screenshot, + /// Constructor for [WebDriverCommandType.noop] screenshot. + WebDriverCommand.screenshot(String screenshot_name) + : this.type = WebDriverCommandType.screenshot, this.values = {'screenshot_name': screenshot_name}; - /// Util method for converting [WebDriverActionTypes] to a map entry. + /// Util method for converting [WebDriverCommandType] to a map entry. /// /// Used for converting messages to json format. - static Map typeToMap(WebDriverActionType type) => { - 'web_driver_action': '${type}', + static Map typeToMap(WebDriverCommandType type) => { + 'web_driver_command': '${type}', }; } diff --git a/packages/integration_test/lib/integration_test_driver_extended.dart b/packages/integration_test/lib/integration_test_driver_extended.dart index 936940a8591a..93a2da0e7e5a 100644 --- a/packages/integration_test/lib/integration_test_driver_extended.dart +++ b/packages/integration_test/lib/integration_test_driver_extended.dart @@ -5,7 +5,7 @@ import 'common.dart'; import 'package:flutter_driver/flutter_driver.dart'; -/// Example Integration Test which can also run WebDriver action depending on +/// Example Integration Test which can also run WebDriver command depending on /// the requests coming from the test methods. Future integrationDriver() async { final FlutterDriver driver = await FlutterDriver.connect(); @@ -17,13 +17,13 @@ Future integrationDriver() async { Response response = Response.fromJson(jsonResponse); - // Until `integration_test` returns a [WebDriverActionType.noop], keep - // executing WebDriver actions. + // Until `integration_test` returns a [WebDriverCommandType.noop], keep + // executing WebDriver commands. while (response.data != null && - response.data['web_driver_action'] != null && - response.data['web_driver_action'] != '${WebDriverActionType.noop}') { - final String webDriverCommand = response.data['web_driver_action']; - if (webDriverCommand == '${WebDriverActionType.screenshot}') { + response.data['web_driver_command'] != null && + response.data['web_driver_command'] != '${WebDriverCommandType.noop}') { + final String webDriverCommand = response.data['web_driver_command']; + if (webDriverCommand == '${WebDriverCommandType.screenshot}') { // Use `driver.screenshot()` method to get a screenshot of the web page. final List screenshotImage = await driver.screenshot(); final String screenshotName = response.data['screenshot_name']; @@ -39,7 +39,7 @@ Future integrationDriver() async { timeout: const Duration(seconds: 10)); response = Response.fromJson(jsonResponse); - } else if (webDriverCommand == '${WebDriverActionType.ack}') { + } else if (webDriverCommand == '${WebDriverCommandType.ack}') { // Previous command completed ask for a new one. jsonResponse = await driver.requestData( '${TestStatus.waitOnWebdriverCommand}', @@ -53,8 +53,8 @@ Future integrationDriver() async { // If No-op command is sent, ask for the result of all tests. if (response.data != null && - response.data['web_driver_action'] != null && - response.data['web_driver_action'] == '${WebDriverActionType.noop}') { + response.data['web_driver_command'] != null && + response.data['web_driver_command'] == '${WebDriverCommandType.noop}') { jsonResponse = await driver.requestData(null, timeout: const Duration(minutes: 1)); From 1492eba0648b8dcbd0b1807b977d2bf47ad8efd4 Mon Sep 17 00:00:00 2001 From: nturgut Date: Fri, 28 Aug 2020 15:44:30 -0700 Subject: [PATCH 05/10] remove files. use implements --- .../integration_test/example/platform_name.png | Bin 43360 -> 0 bytes .../example/platform_name_2.png | Bin 43360 -> 0 bytes packages/integration_test/lib/_callback_io.dart | 2 +- .../integration_test/lib/_callback_web.dart | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 packages/integration_test/example/platform_name.png delete mode 100644 packages/integration_test/example/platform_name_2.png diff --git a/packages/integration_test/example/platform_name.png b/packages/integration_test/example/platform_name.png deleted file mode 100644 index 8ec45795f38280a634a81d87a69881771d88304e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43360 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

(EAP`Clv-=E2}D3At%1Tuf2 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 diff --git a/packages/integration_test/example/platform_name_2.png b/packages/integration_test/example/platform_name_2.png deleted file mode 100644 index 8ec45795f38280a634a81d87a69881771d88304e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43360 zcmeHw2UJs8+b)g;9Sbw#2r42fAS!L7N)0m>M4Ao)QlrvDN|YV~1S_L73DO~`C`gU8 zPy$3{=p`a0Kp+u?L|Os_2qA&o9m@C5_pf!=y6aze-TVJ*g~duvPR>5r``yp`ywCgQ zoSi3^O$~Pcc<@IdA)(!d7tj4EB(y6`NNBsz_HAI#2qLFMNNAIS+u5_1&E4H@2nn5t z4T;q@+I;1w3o#!W#I~QZx#p1_m2xibr@tP-_irh>`0FpYc9$JDe@SeA+WRye^3lfD zsK`|5n%l{1*S6pHF#sL=sirUpOd`~={vJ>*d_zBo5OjNws@g>aQ~M9p^U8#N9ZZ@ z(KPYUpIvR*&1Lgb4a-9=TNXBno%DXy`Ql0ILFZ`AkD~i7#!8vpe|z)k?whZ++|Akg z*~#!yt6bKb*Vn8%N@R3f)W@cEl^kxA3{1=){=+jsgig7^mFiBQUmyRu$_hP2!he3t zme$w_^*O~6bs=B>`sa9c@*dV>HGoe(f4oUdcP}jb&bWZi?8e z^H!8x2d6SW=MX)DdCY5D8W};aq>Z2NVfSLbH8U!4vA@_9&E#@Bt;k7bWr*S`yn#lE06&KW>mPl&Qpi+XUxrMV8}nTQ-gX&+nkV&&z(; z{*OIEIE#e<;eYN(N(UJFk3aq99by0RN=WE=n&53<4}n?MK=HqW!~2jIHipqQ=0lXB zwFAEh^;F`uSGV=qx(iA4>Myaa)S=Kjr3vk%iOSjDnc#1aXEaHW>1VgecU+KNSR`-e zq9Uic?>@)of)b=giFTM0cgdjG=NP*cGuad`PCG2tCq`!w#8yvwNPdg{>PAx6isdkwTUXB}9LGl5uK5SzWnPNT)Za2}A}zm7pYefJG&j~b|0}j&N_(+k z%^AZGjT*+Q?FMD{*jqLWqbrZaKbVXgYYo*(X^8YpEhn;!psNQ6ow+071T9%@_YUJB z={|x>J)Yt~Bx@!YNX>T-jEFBGv}UP390nIj;ZCEoNh9%r#<}RpUg`71P(9*9Mn+3*S;dgj@OqBGt-w_0E2? zl`Zzkhtt@H5Vf=nvX@ILnLhHc4##{S-{Wf)=U#3+vP662 zc1acnrJ#MgKhR78g7;C*+*`uXW~Rikqd4Tjyc~}2Ty96CnYwr59bJtfwPX`pn#P%u zD9*sX9`3-6bWIPqjJU9S<#@uA$jCCr)iP9CV4$md{!~k#Lci*@R*Uk;Z1tE)g2(bj zl$4c~&)0ztU+Kya1UQ$braN@u(QO&cAQLqM485)D-fe7*dhy+N2^mA0mESFbv#69J z_p0Kma;bpb#1dx%T}ZWaFqs*8y3x1L5i_1Qa&)4q__f@D!Jek50Anip#?jx_>@`G+!NxuO)wbYn7eXxJ{O{aO$%&J6#e(u^{A292*St(j_0GT=kxYPn;z#)ZMH4R#h~zK+~KCzN)WM2#-zrBY^G5;t%_s|C6EKZFBMiV zo^DN9Qn8n6_6snf)=>JBC)C9VxEuDaWH?#>2FD%$PReTeX7%W4TyAqVv_i{XC96R; zv|5fgI~Dr}r%W!FU81t|S=VfQm z%A^meWTp`M<|jU0JZTHG>6M$DAgM~OSUvpo5Y3@lE4|CV2L3JqviLW4&R-S-$%6o- z^~|nNur+8TccH_ssA#@|Ucmp*%tobFshIDV-t;_)^EgmQ%e0CbF(O7!b@@Q2r{BQo zb%>JJ?FF7rtP)-aY_dtM9Gk(4(9BYywL9s;TR7#%4ISnd$!Yi-Zn+98sie~hc5`*H zcELS`*hapv5(@KQpo^=oPG3@XhQ~?8PYzWvq3E~DF=mz8w)qZ>Laak)EwHYc4$Kc- z`_gYw$}qwAIC4omgq`h*F}89o+B_9KG?@JrTXV*!y!F}pk7NgKXKP$7H01rIUmDNs76kC#i?$4KY+N@pMLNe@6nchwsw!;#u@67Z4E zA@&HqZ`e>M-vePT1#e0WvPVEr8GtbOp#vMNCiJ_}iF>vp@FS+gz zT+N+~MAmEP>rTr@dlG0N-x9H6rvj=+x}7zdeegXkN`6*x0g#aMmSsD9jjal#F#yHb z;johuh7K!>wTH^GQb1P&Iu;Czhd*_2r(k6w_l(Q)wc%ZH2?XtHcNgyJ!k4XCdRt~c zHqKM`#V}Hd^TwJtOkDM5adM0vxNwjhyOC+t#$l&=k6+Pc$H5;%MG?l7kPq*xGl){c zr`XeseB;JHwjJb;KJ3AN(8%k$r*c&W$6FGG;7rX{(#kQOlkS0o1+Kvtu0(tj={I;| zZ1X+$_2qjvi}etJ)ZMQ1kRzj&F=OpTdBEK}o{TI=%twkwR7x9m1YR~fyzrhX)TmoG zAG|1BKQ1AbR;s$WrkyxXEm_(*RqNAwF4VX6To+bY!ls=qo=`ejm##fgxk7LDAUnV` zFWASA#G54{P9xt)nOG-czc!f+5armVL3Y>*tBJ%*6fhjZY|V_q{{ zlWtTh;B~hNC;!MYGAr#{P_cLaAQY}ZO|sNNopqhNL3Ap1%wJZ)Rh6S4OYI@%m8)?) z>_Oev5S6v)Zm4*svf?^&;$qGD)@9mJ1OL&7YV|Q~6Hy~bB;rg4e#uIXU}jB;*U-yB zeu)2(9%TgLxY_b8Ra@+o>9Mpsfv)7B4*i!RU?kq%DYw`*P~!!zJe5yAZHEk{&ZhAQwyD?Z#^DbYG(EVRQK%Gn3&g zYE*`t15}%W^E(E^c^F&>Jy5&^jgR(=aTLVL12BRk9?1>pKELkEtQj8GWGfE zd-u00=90?OeKw(t&KfocvxmS*1C(p~cmiH0hIQ@g9r7u*F+}tF$aaC=GY7O8KxCCO z7`KBOR=xa%#6JDijcck%?+Z{%1%=RGm-{oK>EYy3R-5Q>xHKCFQwxWnMht+0a2A85 zmi^LLJ2xyDIU-qGTXOQtogr$o4vj*0>kr>6`EHsL8nhslAS&7o6EC1`l@ynkH^xt) zZ5(EsJ(JUl973$EyzL@dL^15ig+J6H_Y!@n!v=oYc`4PCMfJHq!>T+CZY%WGL2lan z#K&Gh(dGcwOpn3t4S%am1%E*=eDa8FB4V6{@B;>95oJcJf z6L!09C^I3F%$v#P<8E7mDkQHgX!f%i%>75CbaPYStT0b6NjpH4z>5qo3r%qGS$OItVzp#u>gEEFtbQO7fM4FP$vN8(6jV zd*gMEoivw_Xf`zPr9v}Glxc^o@s!xRk`Hn@yG>xE62sYDG)fL;s!qTo_i4vyr9vG{?TQo zU%SVfcL#{oi5Xzus-Es5YDB9ke8~uH!z@>yp|U1(X{``9T`MwgDE*OVNPXk0H}-MO zyv4c8Bo=fb$20lX&(Ae^M|Xd*NKJe6C}zYN1AVa)*}~UMWqkNV~#uNs;|06l>9M zWu>N6gb*GFbDis6MzF4MJbr&D4N`!+MeJa)k7m}mS4p8`W5#4o+rFLJmr>3`Q)0z< zst<`-yVC_iwXLm|+FL?1O#z(OY~br$w^^&CxE!@2BQZ8n_!@(gsvER3E!+=x)IT7|-y zGuy*fH|}IC#^eQz0@s^lc>Q{)7rIM-aOl-XeK|;t&&?~FkECKd989ji@@E+3v(cRFTdn%@r%?wB`h^mUFqYsyU zFzS}KJfE83gP9-5!hv?Z#4WA>1du*??6Bu@ZDL2@kFF7Dwpr)Gn za`q8de|0?FIM-NC&_&AL#+^NXiqSYrbsxVAZMMYnj2TCG8Kp;gF8lqtJ& zL{CawJ{C9sT0O=skQIG1bNq*$PVy7|oTmp1=KPhofiztwLx-gb=l*hCH*BBd5)(&b z@jgTs1zEDx3B&O<68$=7Fhi?AI`h|hnd+Stb{CNeg^0^}U^GzeB=`P`vV>GdN*eM0 z#PS$A_Uo&uv$O-ho$;T)$2-lz+SgBj!(wm#{7`du3?Z}xI`gm=%cvR4U#b4?Sb_;d zQdDSrklo@qv85Oyid^`*IO)m#^WD~lx$%{ocZSO=MK}Jjvo12SoO~lNbgT7`+YCf| z?-@vTr6=lo_2+vY?AI0DJMR=+jJ_xOp`cQJAxZE1v)k&P>DpV0tFr^So;cB*$TgZ^ z$qBD$n6IK^QjF-4+!S7`QVivM(u`62;Q?y8$4F~>zd$h$7fWZbXKuB!BoW2|d)$kDL$==0m8`ifZNix87W|8(86nuSTqZ}L$2<9WaSp~e{iq70x0(ikPpkI5d@d44Cp_6e6> zyxi7xGkR<$yln^rwYAdmmuh~+%&mt0P+rG@p4}kj%d@>oN*c3pT^Nm`DN>k0<7X2} zJy?xq-;F?)*M*hyN^R<~O%eNQCD&|={!E$O?{0I|8%`(>z)sm37fPRwRbs>tdv_ni8s(EAX- z)YkiZRN?Je@=jWzR;;wupt@zB|KQPdc;`2SEf(S&VufZ-_cA2@szn&hEK!idE!K5v zVtO~-(7qmKhs$dbgoz>%dHNk7h|$VY7cT=9G+#dj?|MVp`4YW5+@CrlqIfu)d^3tW zF8cD>NsL~Jl7uHk_c!?|V={Yg!Dn$uEdQca#H+nL_5j)W+_~C( z$rBy@FtT0n%IYv*?Ax?f=GwDgTv&D|33OQ-b_S=^0}cH;)EUcNx-tY3^@6tCH%!ic zZXLzFrh@Y^E|JR9&Qr<@J)nARAies_-5NNlzaa1Gp|^TV55CT`{A48qdt37DJF5@Y zW=g^7jzP><2BMpP%fJ`zKNg6HczRfp`{Ck+hPdI*3wjHgUn`-ArP+_ixC(Xka|CxY zXf2IwoO2b0UnyA8nQ?fyrDdeACmr5u&gs%FxZk1=lVJod>F;4Ebuc6xlgolGXj3@s zzzeQA4)M_=41##mS)$n9-7!Iz1IR3r+>a#Wr(?!ysi}-pdR=)4IfXha5zS&b$%=P= z`i@6IbV~F+&OlIG@*yKu>G_r@3NMNb>PcVIJfzv(A`yhu7%fz)Y%q*iwb+RmGlv>E z*qO|0GvB&1gS^Kh5XufDd$!}tcu94U-r3RA25)Ln{nmkOiJ`cz=vw5%9Rrm>QPz9br@>TEi1D=GIz#)6nM|!oP

(EAP`Clv-=E2}D3At%1Tuf2 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 diff --git a/packages/integration_test/lib/_callback_io.dart b/packages/integration_test/lib/_callback_io.dart index b4826ec8599b..c1a447e27cab 100644 --- a/packages/integration_test/lib/_callback_io.dart +++ b/packages/integration_test/lib/_callback_io.dart @@ -17,7 +17,7 @@ final IOCallbackManager _singletonCallbackManager = IOCallbackManager(); /// Manages communication between `integration_tests` and the `driver_tests`. /// /// This is the dart:io implementation. -class IOCallbackManager extends CallbackManager { +class IOCallbackManager implements CallbackManager { @override Future> callback( Map params, IntegrationTestResults testRunner) async { diff --git a/packages/integration_test/lib/_callback_web.dart b/packages/integration_test/lib/_callback_web.dart index 758b95d7d329..6f3fa07d0d73 100644 --- a/packages/integration_test/lib/_callback_web.dart +++ b/packages/integration_test/lib/_callback_web.dart @@ -28,7 +28,7 @@ final WebCallbackManager _singletonWebDriverCommandManager = /// WebDriver APIs. /// /// See: https://www.w3.org/TR/webdriver/ -class WebCallbackManager extends CallbackManager { +class WebCallbackManager implements CallbackManager { /// App side tests will put the command requests from WebDriver to this pipe. Completer _webDriverCommandPipe = Completer(); From 911abe5833c2006aee7e6d9e3f51f782140890f5 Mon Sep 17 00:00:00 2001 From: nturgut Date: Mon, 31 Aug 2020 09:57:52 -0700 Subject: [PATCH 06/10] remove timeout. add onScreenshot callback --- .../example_integration_extended_test.dart | 30 +++++++++++++++- .../lib/integration_test_driver_extended.dart | 36 ++++++++----------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/packages/integration_test/example/test_driver/example_integration_extended_test.dart b/packages/integration_test/example/test_driver/example_integration_extended_test.dart index 849ace247e10..3009ba0a5818 100644 --- a/packages/integration_test/example/test_driver/example_integration_extended_test.dart +++ b/packages/integration_test/example/test_driver/example_integration_extended_test.dart @@ -1,5 +1,33 @@ import 'dart:async'; +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; import 'package:integration_test/integration_test_driver_extended.dart'; -Future main() async => integrationDriver(); +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + await integrationDriver( + driver: driver, + onScreenshot: (String screenshotName, List screenshotBytes) async { + if(screenshotName == 'platform_name_2') { + return false; + } + // The screenshot is saved as png. Later it can be used for golden testing + // with library of choice, such as skia_client.dart. + final String screenshotPath = + await _saveScreenshot(screenshotBytes, screenshotName); + print('INFO: screenshot recorded $screenshotPath'); + return true; + }, + ); +} + +/// Example method for saving the screenshot taken by the Webdriver to a `png` +/// file. +Future _saveScreenshot(List screenshot, String path) async { + final File file = File('$path.png'); + if (!file.existsSync()) { + await file.writeAsBytes(screenshot); + } + return '$path.png'; +} diff --git a/packages/integration_test/lib/integration_test_driver_extended.dart b/packages/integration_test/lib/integration_test_driver_extended.dart index 93a2da0e7e5a..9dc6263c9bfe 100644 --- a/packages/integration_test/lib/integration_test_driver_extended.dart +++ b/packages/integration_test/lib/integration_test_driver_extended.dart @@ -7,9 +7,11 @@ import 'package:flutter_driver/flutter_driver.dart'; /// Example Integration Test which can also run WebDriver command depending on /// the requests coming from the test methods. -Future integrationDriver() async { - final FlutterDriver driver = await FlutterDriver.connect(); - +Future integrationDriver( + {FlutterDriver driver, Function onScreenshot}) async { + if (driver == null) { + driver = await FlutterDriver.connect(); + } // Test states that it's waiting on web driver commands. String jsonResponse = await driver.requestData( '${TestStatus.waitOnWebdriverCommand}', @@ -28,15 +30,15 @@ Future integrationDriver() async { final List screenshotImage = await driver.screenshot(); final String screenshotName = response.data['screenshot_name']; - // The screenshot is saved as png. Later it can be used for golden testing - // with library of choice, such as skia_client.dart. - final String screenshotPath = - await _saveScreenshot(screenshotImage, screenshotName); - print('INFO: screenshot recorded $screenshotPath'); - - jsonResponse = await driver.requestData( - '${TestStatus.webdriverCommandComplete}', - timeout: const Duration(seconds: 10)); + final bool screenshotSuccess = + await onScreenshot(screenshotName, screenshotImage); + if (screenshotSuccess) { + jsonResponse = + await driver.requestData('${TestStatus.webdriverCommandComplete}'); + } else { + jsonResponse = + await driver.requestData('${TestStatus.webdriverCommandComplete}'); + } response = Response.fromJson(jsonResponse); } else if (webDriverCommand == '${WebDriverCommandType.ack}') { @@ -72,13 +74,3 @@ Future integrationDriver() async { exit(1); } } - -/// Example method for saving the screenshot taken by the Webdriver to a `png` -/// file. -Future _saveScreenshot(List screenshot, String path) async { - final File file = File('$path.png'); - if (!file.existsSync()) { - await file.writeAsBytes(screenshot); - } - return '$path.png'; -} From b32bba261ffe87d8ea67bb2825c56477266a0cf6 Mon Sep 17 00:00:00 2001 From: nturgut Date: Mon, 31 Aug 2020 11:56:39 -0700 Subject: [PATCH 07/10] remove remaning timeouts. add an error message for screenshots. use an object instead of strings for status messages --- .../example_integration_extended_test.dart | 3 - .../integration_test/lib/_callback_web.dart | 19 ++--- packages/integration_test/lib/common.dart | 76 ++++++++++++++++--- .../lib/integration_test_driver_extended.dart | 19 +++-- 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/packages/integration_test/example/test_driver/example_integration_extended_test.dart b/packages/integration_test/example/test_driver/example_integration_extended_test.dart index 3009ba0a5818..0dd8966ff7a8 100644 --- a/packages/integration_test/example/test_driver/example_integration_extended_test.dart +++ b/packages/integration_test/example/test_driver/example_integration_extended_test.dart @@ -9,9 +9,6 @@ Future main() async { await integrationDriver( driver: driver, onScreenshot: (String screenshotName, List screenshotBytes) async { - if(screenshotName == 'platform_name_2') { - return false; - } // The screenshot is saved as png. Later it can be used for golden testing // with library of choice, such as skia_client.dart. final String screenshotPath = diff --git a/packages/integration_test/lib/_callback_web.dart b/packages/integration_test/lib/_callback_web.dart index 6f3fa07d0d73..036098148d99 100644 --- a/packages/integration_test/lib/_callback_web.dart +++ b/packages/integration_test/lib/_callback_web.dart @@ -97,11 +97,13 @@ class WebCallbackManager implements CallbackManager { Future> _requestDataWithMessage( String extraMessage, IntegrationTestResults testRunner) async { - // Test status is added as an exta message. Map response; - // If Test status is `wait_on_webdriver_command` send the first - // command in the `commandPipe` to the tests. - if (extraMessage == '${TestStatus.waitOnWebdriverCommand}') { + // Driver side tests' status is added as an extra message. + final DriverTestMessage message = + DriverTestMessage.fromString(extraMessage); + // If driver side tests are pending send the first command in the + // `commandPipe` to the tests. + if (message.isPending) { final WebDriverCommand command = await _webDriverCommandPipe.future; switch (command.type) { case WebDriverCommandType.screenshot: @@ -122,19 +124,14 @@ class WebCallbackManager implements CallbackManager { default: throw UnimplementedError('${command.type} is not implemented'); } - } - // Tests will send `webdriver_command_complete` status after - // WebDriver completes an command. - else if (extraMessage == '${TestStatus.webdriverCommandComplete}') { + } else { final Map data = Map(); data.addAll(WebDriverCommand.typeToMap(WebDriverCommandType.ack)); response = { 'message': Response.webDriverCommand(data: data).toJson(), }; - _driverCommandComplete.complete(Future.value(true)); + _driverCommandComplete.complete(Future.value(message.isSuccess)); _webDriverCommandPipe = Completer(); - } else { - throw UnimplementedError('$extraMessage is not implemented'); } return { 'isError': false, diff --git a/packages/integration_test/lib/common.dart b/packages/integration_test/lib/common.dart index b3cf08894ef2..53714a8e97ee 100644 --- a/packages/integration_test/lib/common.dart +++ b/packages/integration_test/lib/common.dart @@ -138,16 +138,74 @@ class Failure { } } -/// Integration web tests can execute WebDriver commands such as screenshots. +/// Message used to communicate between app side tests and driver tests. /// -/// These test will use [TestStatus] to notify `integration_test` of their -/// state. -enum TestStatus { - /// Test is waiting for executing WebDriver commands. - waitOnWebdriverCommand, - - /// Test executed the previously requested commands. - webdriverCommandComplete, +/// Not all `integration_tests` use this message. They are only used when app +/// side tests are sending [WebDriverCommand]s to the driver side. +/// +/// These messages are used for the handshake since they carry information on +/// the driver side test such as: status pending or tests failed. +class DriverTestMessage { + final bool _isSuccess; + final bool _isPending; + + /// When tests are failed on the driver side. + DriverTestMessage.error() + : _isSuccess = false, + _isPending = false; + + /// When driver side is waiting on [WebDriverCommand]s to be sent from the + /// app side. + DriverTestMessage.pending() + : _isSuccess = false, + _isPending = true; + + /// When driver side successfully completed executing the [WebDriverCommand]. + DriverTestMessage.complete() + : _isSuccess = true, + _isPending = false; + + // /// Status of this message. + // /// + // /// The status will be use to notify `integration_test` of driver side's + // /// state. + // String get status => _status; + + /// Has the command completed successfully by the driver. + bool get isSuccess => _isSuccess; + + /// Is the driver waiting for a command. + bool get isPending => _isPending; + + /// Depending on the values of [isPending] and [isSuccess], returns a string + /// to represent the [DriverTestMessage]. + /// + /// Used as an alternative method to converting the object to json since + /// [RequestData] is only accepting string as `message`. + @override + String toString() { + if (isPending) { + return 'pending'; + } else if (isSuccess) { + return 'complete'; + } else { + return 'error'; + } + } + + /// Return a DriverTestMessage depending on `status`. + static DriverTestMessage fromString(String status) { + switch (status) { + case 'error': + return DriverTestMessage.error(); + case 'pending': + return DriverTestMessage.pending(); + case 'complete': + return DriverTestMessage.complete(); + default: + throw StateError('This type of status does not exist: $status'); + } + } } /// Types of different WebDriver commands that can be used in web integration diff --git a/packages/integration_test/lib/integration_test_driver_extended.dart b/packages/integration_test/lib/integration_test_driver_extended.dart index 9dc6263c9bfe..bc38bb71de50 100644 --- a/packages/integration_test/lib/integration_test_driver_extended.dart +++ b/packages/integration_test/lib/integration_test_driver_extended.dart @@ -13,9 +13,10 @@ Future integrationDriver( driver = await FlutterDriver.connect(); } // Test states that it's waiting on web driver commands. - String jsonResponse = await driver.requestData( - '${TestStatus.waitOnWebdriverCommand}', - timeout: const Duration(seconds: 10)); + // [DriverTestMessage] is converted to string since json format causes an + // error if it's used as a message for requestData. + String jsonResponse = + await driver.requestData(DriverTestMessage.pending().toString()); Response response = Response.fromJson(jsonResponse); @@ -34,18 +35,17 @@ Future integrationDriver( await onScreenshot(screenshotName, screenshotImage); if (screenshotSuccess) { jsonResponse = - await driver.requestData('${TestStatus.webdriverCommandComplete}'); + await driver.requestData(DriverTestMessage.complete().toString()); } else { jsonResponse = - await driver.requestData('${TestStatus.webdriverCommandComplete}'); + await driver.requestData(DriverTestMessage.error().toString()); } response = Response.fromJson(jsonResponse); } else if (webDriverCommand == '${WebDriverCommandType.ack}') { // Previous command completed ask for a new one. - jsonResponse = await driver.requestData( - '${TestStatus.waitOnWebdriverCommand}', - timeout: const Duration(seconds: 10)); + jsonResponse = + await driver.requestData(DriverTestMessage.pending().toString()); response = Response.fromJson(jsonResponse); } else { @@ -57,8 +57,7 @@ Future integrationDriver( if (response.data != null && response.data['web_driver_command'] != null && response.data['web_driver_command'] == '${WebDriverCommandType.noop}') { - jsonResponse = - await driver.requestData(null, timeout: const Duration(minutes: 1)); + jsonResponse = await driver.requestData(null); response = Response.fromJson(jsonResponse); print('result $jsonResponse'); From 5fc80fac8ab3ec64cba9cfcb01a1ee2b082481e1 Mon Sep 17 00:00:00 2001 From: nturgut Date: Mon, 31 Aug 2020 12:02:07 -0700 Subject: [PATCH 08/10] created a new issue --- .../example/test_driver/example_integration_io_extended.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/integration_test/example/test_driver/example_integration_io_extended.dart b/packages/integration_test/example/test_driver/example_integration_io_extended.dart index 0358b0473ff8..56fee6f7179c 100644 --- a/packages/integration_test/example/test_driver/example_integration_io_extended.dart +++ b/packages/integration_test/example/test_driver/example_integration_io_extended.dart @@ -22,7 +22,8 @@ void main() { // Trigger a frame. await tester.pumpAndSettle(); - // TODO: Add screenshot capability for mobile platforms. + // TODO: https://github.com/flutter/flutter/issues/51890 + // Add screenshot capability for mobile platforms. // Verify that platform version is retrieved. expect( From 0c1d3b3f7af5903b1ceaed84e13c39ba64b423b9 Mon Sep 17 00:00:00 2001 From: nturgut Date: Mon, 31 Aug 2020 12:11:15 -0700 Subject: [PATCH 09/10] remove example screenshot saving since it's failing on android. examples in integration_test package is common --- .../example_integration_extended_test.dart | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/integration_test/example/test_driver/example_integration_extended_test.dart b/packages/integration_test/example/test_driver/example_integration_extended_test.dart index 0dd8966ff7a8..1428a5092a78 100644 --- a/packages/integration_test/example/test_driver/example_integration_extended_test.dart +++ b/packages/integration_test/example/test_driver/example_integration_extended_test.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:integration_test/integration_test_driver_extended.dart'; @@ -9,22 +8,7 @@ Future main() async { await integrationDriver( driver: driver, onScreenshot: (String screenshotName, List screenshotBytes) async { - // The screenshot is saved as png. Later it can be used for golden testing - // with library of choice, such as skia_client.dart. - final String screenshotPath = - await _saveScreenshot(screenshotBytes, screenshotName); - print('INFO: screenshot recorded $screenshotPath'); return true; }, ); } - -/// Example method for saving the screenshot taken by the Webdriver to a `png` -/// file. -Future _saveScreenshot(List screenshot, String path) async { - final File file = File('$path.png'); - if (!file.existsSync()) { - await file.writeAsBytes(screenshot); - } - return '$path.png'; -} From 0b3456dbec6f5d68f4b0f271c4d77081d5e057bc Mon Sep 17 00:00:00 2001 From: nturgut Date: Mon, 31 Aug 2020 12:14:43 -0700 Subject: [PATCH 10/10] changing the version and the change log --- packages/integration_test/CHANGELOG.md | 4 ++++ packages/integration_test/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/integration_test/CHANGELOG.md b/packages/integration_test/CHANGELOG.md index d57819e331ea..3bed2b8b73b4 100644 --- a/packages/integration_test/CHANGELOG.md +++ b/packages/integration_test/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.0 + +* Add screenshot capability to web tests. + ## 0.8.2 * Add support to get timeline. diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index 9dd4ade6ce49..6e0412924748 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -1,6 +1,6 @@ name: integration_test description: Runs tests that use the flutter_test API as integration tests. -version: 0.8.2 +version: 0.9.0 homepage: https://github.com/flutter/plugins/tree/master/packages/integration_test environment: