This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[web] Adding capability to e2e to take screenshot for web tests. #2904
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
17dac0f
screenshot taking works
6adf298
squash commits. addressing reviewer comments. making drivercommandman…
f832b17
addressing reviewer comment. mostly name changes
9917bcd
major rename on all files webdriveraction->webdrivercommand
1492eba
remove files. use implements
911abe5
remove timeout. add onScreenshot callback
b32bba2
remove remaning timeouts. add an error message for screenshots. use a…
5fc80fa
created a new issue
0c1d3b3
remove example screenshot saving since it's failing on android. examp…
0b3456d
changing the version and the change log
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ## 0.9.0 | ||
|
|
||
| * Add screenshot capability to web tests. | ||
|
|
||
| ## 0.8.2 | ||
|
|
||
| * Add support to get timeline. | ||
|
|
||
18 changes: 18 additions & 0 deletions
18
packages/integration_test/example/test_driver/example_integration_extended.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
14 changes: 14 additions & 0 deletions
14
packages/integration_test/example/test_driver/example_integration_extended_test.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'package:flutter_driver/flutter_driver.dart'; | ||
| import 'package:integration_test/integration_test_driver_extended.dart'; | ||
|
|
||
| Future<void> main() async { | ||
| final FlutterDriver driver = await FlutterDriver.connect(); | ||
| await integrationDriver( | ||
| driver: driver, | ||
| onScreenshot: (String screenshotName, List<int> screenshotBytes) async { | ||
| return true; | ||
| }, | ||
| ); | ||
| } |
38 changes: 38 additions & 0 deletions
38
packages/integration_test/example/test_driver/example_integration_io_extended.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // 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: https://github.com/flutter/flutter/issues/51890 | ||
| // 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, | ||
| ); | ||
| }); | ||
| } |
52 changes: 52 additions & 0 deletions
52
packages/integration_test/example/test_driver/example_integration_web_extended.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 implements CallbackManager { | ||
| @override | ||
| Future<Map<String, dynamic>> callback( | ||
| Map<String, String> params, IntegrationTestResults testRunner) async { | ||
| final String command = params['command']; | ||
| Map<String, String> response; | ||
| switch (command) { | ||
| case 'request_data': | ||
| final bool allTestsPassed = await testRunner.allTestsPassed.future; | ||
| response = <String, String>{ | ||
| 'message': allTestsPassed | ||
| ? Response.allTestsPassed(data: testRunner.reportData).toJson() | ||
| : Response.someTestsFailed( | ||
| testRunner.failureMethodsDetails, | ||
| data: testRunner.reportData, | ||
| ).toJson(), | ||
| }; | ||
| break; | ||
| case 'get_health': | ||
| response = <String, String>{'status': 'ok'}; | ||
| break; | ||
| default: | ||
| throw UnimplementedError('$command is not implemented'); | ||
| } | ||
| return <String, dynamic>{ | ||
| '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<void> takeScreenshot(String screenshot) { | ||
| throw UnimplementedError( | ||
| 'Screenshots are not implemented on this platform'); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| // 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'; | ||
|
|
||
| /// The dart:html implementation of [CallbackManager]. | ||
| /// | ||
| /// See also: | ||
| /// | ||
| /// * [_callback_io.dart], which has the dart:io implementation | ||
| CallbackManager get callbackManager => _singletonWebDriverCommandManager; | ||
|
|
||
| /// WebDriverCommandManager singleton. | ||
| final WebCallbackManager _singletonWebDriverCommandManager = | ||
| WebCallbackManager(); | ||
|
|
||
| /// 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 [WebDriverCommand]s to driver side. | ||
| /// | ||
| /// Tests can execute an Web Driver commands such as `screenshot` using browsers' | ||
| /// WebDriver APIs. | ||
| /// | ||
| /// See: https://www.w3.org/TR/webdriver/ | ||
| class WebCallbackManager implements CallbackManager { | ||
| /// App side tests will put the command requests from WebDriver to this pipe. | ||
| Completer<WebDriverCommand> _webDriverCommandPipe = | ||
| Completer<WebDriverCommand>(); | ||
|
|
||
| /// 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 [_driverCommandComplete] | ||
| /// will complete. | ||
| Completer<bool> _driverCommandComplete = Completer<bool>(); | ||
|
|
||
| /// 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. | ||
| @override | ||
| Future<void> takeScreenshot(String screenshotName) async { | ||
| await _sendWebDriverCommand(WebDriverCommand.screenshot(screenshotName)); | ||
| } | ||
|
|
||
| Future<void> _sendWebDriverCommand(WebDriverCommand command) async { | ||
| try { | ||
| _webDriverCommandPipe.complete(Future.value(command)); | ||
| final bool awaitCommand = await _driverCommandComplete.future; | ||
| if (!awaitCommand) { | ||
| throw Exception( | ||
| '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. | ||
| _driverCommandComplete = Completer<bool>(); | ||
| } | ||
| } | ||
|
|
||
| /// The callback function to response the driver side input. | ||
| /// | ||
| /// Provides a handshake mechanism for executing [WebDriverCommand]s on the | ||
| /// driver side. | ||
| @override | ||
| Future<Map<String, dynamic>> callback( | ||
| Map<String, String> params, IntegrationTestResults testRunner) async { | ||
| final String command = params['command']; | ||
| Map<String, String> response; | ||
| switch (command) { | ||
| case 'request_data': | ||
| return params['message'] == null | ||
| ? _requestData(testRunner) | ||
| : _requestDataWithMessage(params['message'], testRunner); | ||
| break; | ||
| case 'get_health': | ||
| response = <String, String>{'status': 'ok'}; | ||
| break; | ||
| default: | ||
| throw UnimplementedError('$command is not implemented'); | ||
| } | ||
| return <String, dynamic>{ | ||
| 'isError': false, | ||
| 'response': response, | ||
| }; | ||
| } | ||
|
|
||
| Future<Map<String, dynamic>> _requestDataWithMessage( | ||
| String extraMessage, IntegrationTestResults testRunner) async { | ||
| Map<String, String> response; | ||
| // 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: | ||
| final Map<String, dynamic> data = Map.from(command.values); | ||
| data.addAll( | ||
| WebDriverCommand.typeToMap(WebDriverCommandType.screenshot)); | ||
| response = <String, String>{ | ||
| 'message': Response.webDriverCommand(data: data).toJson(), | ||
| }; | ||
| break; | ||
| case WebDriverCommandType.noop: | ||
| final Map<String, dynamic> data = Map(); | ||
| data.addAll(WebDriverCommand.typeToMap(WebDriverCommandType.noop)); | ||
| response = <String, String>{ | ||
| 'message': Response.webDriverCommand(data: data).toJson(), | ||
| }; | ||
| break; | ||
| default: | ||
| throw UnimplementedError('${command.type} is not implemented'); | ||
| } | ||
| } else { | ||
| final Map<String, dynamic> data = Map(); | ||
| data.addAll(WebDriverCommand.typeToMap(WebDriverCommandType.ack)); | ||
| response = <String, String>{ | ||
| 'message': Response.webDriverCommand(data: data).toJson(), | ||
| }; | ||
| _driverCommandComplete.complete(Future.value(message.isSuccess)); | ||
| _webDriverCommandPipe = Completer<WebDriverCommand>(); | ||
| } | ||
| return <String, dynamic>{ | ||
| 'isError': false, | ||
| 'response': response, | ||
| }; | ||
| } | ||
|
|
||
| Future<Map<String, dynamic>> _requestData( | ||
| IntegrationTestResults testRunner) async { | ||
| final bool allTestsPassed = await testRunner.allTestsPassed.future; | ||
| final Map<String, String> response = <String, String>{ | ||
| 'message': allTestsPassed | ||
| ? Response.allTestsPassed(data: testRunner.reportData).toJson() | ||
| : Response.someTestsFailed( | ||
| testRunner.failureMethodsDetails, | ||
| data: testRunner.reportData, | ||
| ).toJson(), | ||
| }; | ||
| return <String, dynamic>{ | ||
| 'isError': false, | ||
| 'response': response, | ||
| }; | ||
| } | ||
|
|
||
| @override | ||
| void cleanup() { | ||
| if (!_webDriverCommandPipe.isCompleted) { | ||
| _webDriverCommandPipe | ||
| .complete(Future<WebDriverCommand>.value(WebDriverCommand.noop())); | ||
| } | ||
|
|
||
| if (!_driverCommandComplete.isCompleted) { | ||
| _driverCommandComplete.complete(Future<bool>.value(false)); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.