Unverified Commit 11549e45 authored by Angjie Li's avatar Angjie Li Committed by GitHub

Use Async WebDriver for WebFlutterDriver. (#50835)

parent e7c90057
...@@ -9,7 +9,7 @@ import 'dart:io'; ...@@ -9,7 +9,7 @@ import 'dart:io';
import 'package:matcher/matcher.dart'; import 'package:matcher/matcher.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:vm_service_client/vm_service_client.dart'; import 'package:vm_service_client/vm_service_client.dart';
import 'package:webdriver/sync_io.dart' as sync_io; import 'package:webdriver/async_io.dart' as async_io;
import 'package:webdriver/support/async.dart'; import 'package:webdriver/support/async.dart';
import '../common/error.dart'; import '../common/error.dart';
...@@ -109,7 +109,7 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -109,7 +109,7 @@ class WebFlutterDriver extends FlutterDriver {
_checkBrowserSupportsTimeline(); _checkBrowserSupportsTimeline();
final List<Map<String, dynamic>> events = <Map<String, dynamic>>[]; final List<Map<String, dynamic>> events = <Map<String, dynamic>>[];
for (final sync_io.LogEntry entry in _connection.logs) { for (final async_io.LogEntry entry in await _connection.logs.toList()) {
if (_startTime.isBefore(entry.timestamp)) { if (_startTime.isBefore(entry.timestamp)) {
final Map<String, dynamic> data = jsonDecode(entry.message)['message'] as Map<String, dynamic>; final Map<String, dynamic> data = jsonDecode(entry.message)['message'] as Map<String, dynamic>;
if (data['method'] == 'Tracing.dataCollected') { if (data['method'] == 'Tracing.dataCollected') {
...@@ -168,7 +168,7 @@ class FlutterWebConnection { ...@@ -168,7 +168,7 @@ class FlutterWebConnection {
/// and whether the WebDriver supports timeline action /// and whether the WebDriver supports timeline action
FlutterWebConnection(this._driver, this._supportsTimelineAction); FlutterWebConnection(this._driver, this._supportsTimelineAction);
final sync_io.WebDriver _driver; final async_io.WebDriver _driver;
bool _supportsTimelineAction; bool _supportsTimelineAction;
...@@ -191,12 +191,11 @@ class FlutterWebConnection { ...@@ -191,12 +191,11 @@ class FlutterWebConnection {
{Duration timeout}) async { {Duration timeout}) async {
// Use sync WebDriver because async version will create a 15 seconds // Use sync WebDriver because async version will create a 15 seconds
// overhead when quitting. // overhead when quitting.
final sync_io.WebDriver driver = sync_io.fromExistingSession( final async_io.WebDriver driver = await async_io.fromExistingSession(
settings['session-id'].toString(), settings['session-id'].toString(),
uri: Uri.parse(settings['session-uri'].toString()), uri: Uri.parse(settings['session-uri'].toString()),
spec: _convertToSpec(settings['session-spec'].toString().toLowerCase()), spec: _convertToSpec(settings['session-spec'].toString().toLowerCase()));
capabilities: jsonDecode(settings['session-capabilities'].toString()) as Map<String, dynamic>); await driver.get(url);
driver.get(url);
await waitUntilExtensionInstalled(driver, timeout); await waitUntilExtensionInstalled(driver, timeout);
return FlutterWebConnection(driver, settings['support-timeline-action'] as bool); return FlutterWebConnection(driver, settings['support-timeline-action'] as bool);
...@@ -206,7 +205,7 @@ class FlutterWebConnection { ...@@ -206,7 +205,7 @@ class FlutterWebConnection {
Future<dynamic> sendCommand(String script, Duration duration) async { Future<dynamic> sendCommand(String script, Duration duration) async {
dynamic result; dynamic result;
try { try {
_driver.execute(script, <void>[]); await _driver.execute(script, <void>[]);
} catch (_) { } catch (_) {
// In case there is an exception, do nothing // In case there is an exception, do nothing
} }
...@@ -222,7 +221,7 @@ class FlutterWebConnection { ...@@ -222,7 +221,7 @@ class FlutterWebConnection {
return null; return null;
} finally { } finally {
// Resets the result. // Resets the result.
_driver.execute(r''' await _driver.execute(r'''
$flutterDriverResult = null $flutterDriverResult = null
''', <void>[]); ''', <void>[]);
} }
...@@ -230,32 +229,32 @@ class FlutterWebConnection { ...@@ -230,32 +229,32 @@ class FlutterWebConnection {
} }
/// Gets performance log from WebDriver. /// Gets performance log from WebDriver.
List<sync_io.LogEntry> get logs => _driver.logs.get(sync_io.LogType.performance); Stream<async_io.LogEntry> get logs => _driver.logs.get(async_io.LogType.performance);
/// Takes screenshot via WebDriver. /// Takes screenshot via WebDriver.
List<int> screenshot() => _driver.captureScreenshotAsList(); Future<List<int>> screenshot() => _driver.captureScreenshotAsList();
/// Closes the WebDriver. /// Closes the WebDriver.
Future<void> close() async { Future<void> close() async {
_driver.quit(closeSession: false); await _driver.quit(closeSession: false);
} }
} }
/// Waits until extension is installed. /// Waits until extension is installed.
Future<void> waitUntilExtensionInstalled(sync_io.WebDriver driver, Duration timeout) async { Future<void> waitUntilExtensionInstalled(async_io.WebDriver driver, Duration timeout) async {
await waitFor<void>(() => await waitFor<void>(() =>
driver.execute(r'return typeof(window.$flutterDriver)', <String>[]), driver.execute(r'return typeof(window.$flutterDriver)', <String>[]),
matcher: 'function', matcher: 'function',
timeout: timeout ?? const Duration(days: 365)); timeout: timeout ?? const Duration(days: 365));
} }
sync_io.WebDriverSpec _convertToSpec(String specString) { async_io.WebDriverSpec _convertToSpec(String specString) {
switch (specString.toLowerCase()) { switch (specString.toLowerCase()) {
case 'webdriverspec.w3c': case 'webdriverspec.w3c':
return sync_io.WebDriverSpec.W3c; return async_io.WebDriverSpec.W3c;
case 'webdriverspec.jsonwire': case 'webdriverspec.jsonwire':
return sync_io.WebDriverSpec.JsonWire; return async_io.WebDriverSpec.JsonWire;
default: default:
return sync_io.WebDriverSpec.Auto; return async_io.WebDriverSpec.Auto;
} }
} }
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:webdriver/sync_io.dart' as sync_io;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:webdriver/async_io.dart' as async_io;
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
...@@ -146,6 +146,13 @@ class DriveCommand extends RunCommandBase { ...@@ -146,6 +146,13 @@ class DriveCommand extends RunCommandBase {
if (argResults['use-existing-app'] == null) { if (argResults['use-existing-app'] == null) {
globals.printStatus('Starting application: $targetFile'); globals.printStatus('Starting application: $targetFile');
if (isWebPlatform) {
throwToolExit(
'Flutter Driver (web) does not support running without use-existing-app.\n'
'Please launch your application beforehand and connects via use-existing-app.'
);
}
if (getBuildInfo().isRelease && !isWebPlatform) { if (getBuildInfo().isRelease && !isWebPlatform) {
// This is because we need VM service to be able to drive the app. // This is because we need VM service to be able to drive the app.
// For Flutter Web, testing in release mode is allowed. // For Flutter Web, testing in release mode is allowed.
...@@ -173,27 +180,46 @@ class DriveCommand extends RunCommandBase { ...@@ -173,27 +180,46 @@ class DriveCommand extends RunCommandBase {
'VM_SERVICE_URL': observatoryUri, 'VM_SERVICE_URL': observatoryUri,
}; };
sync_io.WebDriver driver; async_io.WebDriver driver;
// For web device, WebDriver session will be launched beforehand // For web device, WebDriver session will be launched beforehand
// so that FlutterDriver can reuse it. // so that FlutterDriver can reuse it.
if (isWebPlatform) { if (isWebPlatform) {
final Browser browser = _browserNameToEnum(
argResults['browser-name'].toString());
final String driverPort = argResults['driver-port'].toString();
// start WebDriver // start WebDriver
final Browser browser = _browserNameToEnum(argResults['browser-name'].toString()); try {
driver = _createDriver( driver = await _createDriver(
argResults['driver-port'].toString(), driverPort,
browser, browser,
argResults['headless'].toString() == 'true', argResults['headless'].toString() == 'true',
); );
} on Exception catch (ex) {
throwToolExit(
'Unable to start WebDriver Session for Flutter for Web testing. \n'
'Make sure you have the correct WebDriver Server running at $driverPort. \n'
'Make sure the WebDriver Server matches option --browser-name. \n'
'$ex'
);
}
// set window size // set window size
final List<String> dimensions = argResults['browser-dimension'].split(',') as List<String>; final List<String> dimensions = argResults['browser-dimension'].split(',') as List<String>;
assert(dimensions.length == 2); assert(dimensions.length == 2);
final int x = int.parse(dimensions[0]); int x, y;
final int y = int.parse(dimensions[1]);
final sync_io.Window window = driver.window;
try { try {
window.setLocation(const math.Point<int>(0, 0)); x = int.parse(dimensions[0]);
window.setSize(math.Rectangle<int>(0, 0, x, y)); y = int.parse(dimensions[1]);
} on FormatException catch (ex) {
throwToolExit('''
Dimension provided to --browser-dimension is invalid:
$ex
''');
}
final async_io.Window window = await driver.window;
try {
await window.setLocation(const math.Point<int>(0, 0));
await window.setSize(math.Rectangle<int>(0, 0, x, y));
} catch (_) { } catch (_) {
// Error might be thrown in some browsers. // Error might be thrown in some browsers.
} }
...@@ -215,9 +241,9 @@ class DriveCommand extends RunCommandBase { ...@@ -215,9 +241,9 @@ class DriveCommand extends RunCommandBase {
if (error is ToolExit) { if (error is ToolExit) {
rethrow; rethrow;
} }
throwToolExit('CAUGHT EXCEPTION: $error\n$stackTrace'); throw Exception('Unable to run test: $error\n$stackTrace');
} finally { } finally {
driver?.quit(); await driver?.quit();
if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) { if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) {
globals.printStatus('Leaving the application running.'); globals.printStatus('Leaving the application running.');
} else { } else {
...@@ -437,11 +463,11 @@ Browser _browserNameToEnum(String browserName){ ...@@ -437,11 +463,11 @@ Browser _browserNameToEnum(String browserName){
throw UnsupportedError('Browser $browserName not supported'); throw UnsupportedError('Browser $browserName not supported');
} }
sync_io.WebDriver _createDriver(String driverPort, Browser browser, bool headless) { Future<async_io.WebDriver> _createDriver(String driverPort, Browser browser, bool headless) async {
return sync_io.createDriver( return async_io.createDriver(
uri: Uri.parse('http://localhost:$driverPort/wd/hub/'), uri: Uri.parse('http://localhost:$driverPort/'),
desired: getDesiredCapabilities(browser, headless), desired: getDesiredCapabilities(browser, headless),
spec: browser != Browser.iosSafari ? sync_io.WebDriverSpec.JsonWire : sync_io.WebDriverSpec.W3c spec: async_io.WebDriverSpec.Auto
); );
} }
...@@ -453,7 +479,7 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless) { ...@@ -453,7 +479,7 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless) {
return <String, dynamic>{ return <String, dynamic>{
'acceptInsecureCerts': true, 'acceptInsecureCerts': true,
'browserName': 'chrome', 'browserName': 'chrome',
'goog:loggingPrefs': <String, String>{ sync_io.LogType.performance: 'ALL'}, 'goog:loggingPrefs': <String, String>{ async_io.LogType.performance: 'ALL'},
'chromeOptions': <String, dynamic>{ 'chromeOptions': <String, dynamic>{
'w3c': false, 'w3c': false,
'args': <String>[ 'args': <String>[
...@@ -508,10 +534,6 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless) { ...@@ -508,10 +534,6 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless) {
case Browser.safari: case Browser.safari:
return <String, dynamic>{ return <String, dynamic>{
'browserName': 'safari', 'browserName': 'safari',
'safari.options': <String, dynamic>{
'skipExtensionInstallation': true,
'cleanSession': true
}
}; };
break; break;
case Browser.iosSafari: case Browser.iosSafari:
......
...@@ -699,10 +699,6 @@ void main() { ...@@ -699,10 +699,6 @@ void main() {
test('macOS Safari', () { test('macOS Safari', () {
final Map<String, dynamic> expected = <String, dynamic>{ final Map<String, dynamic> expected = <String, dynamic>{
'browserName': 'safari', 'browserName': 'safari',
'safari.options': <String, dynamic>{
'skipExtensionInstallation': true,
'cleanSession': true
}
}; };
expect(getDesiredCapabilities(Browser.safari, false), expected); expect(getDesiredCapabilities(Browser.safari, false), expected);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment