Unverified Commit 665d380f authored by Angjie Li's avatar Angjie Li Committed by GitHub

Add Android Chrome support to Flutter Web Driver. (#51677)

* Support Android Chrome for Flutter Web Driver.
parent 28e15ccc
...@@ -20,10 +20,8 @@ import 'timeline.dart'; ...@@ -20,10 +20,8 @@ import 'timeline.dart';
/// An implementation of the Flutter Driver using the WebDriver. /// An implementation of the Flutter Driver using the WebDriver.
/// ///
/// Example of how to test WebFlutterDriver: /// Example of how to test WebFlutterDriver:
/// 1. Have Selenium server (https://bit.ly/2TlkRyu) and WebDriver binary (https://chromedriver.chromium.org/downloads) downloaded and placed under the same folder /// 1. Launch WebDriver binary: ./chromedriver --port=4444
/// 2. Launch WebDriver Server: java -jar selenium-server-standalone-3.141.59.jar /// 2. Run test script: flutter drive --target=test_driver/scroll_perf_web.dart -d web-server --release
/// 3. Launch Flutter Web application: flutter run -v -d chrome --target=test_driver/scroll_perf_web.dart
/// 4. Run test script: flutter drive --target=test_driver/scroll_perf_web.dart -v --use-existing-app=/application address/
class WebFlutterDriver extends FlutterDriver { class WebFlutterDriver extends FlutterDriver {
/// Creates a driver that uses a connection provided by the given /// Creates a driver that uses a connection provided by the given
/// [_connection]. /// [_connection].
...@@ -47,7 +45,7 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -47,7 +45,7 @@ class WebFlutterDriver extends FlutterDriver {
/// [hostUrl] which would fallback to environment variable VM_SERVICE_URL. /// [hostUrl] which would fallback to environment variable VM_SERVICE_URL.
/// Driver also depends on environment variables DRIVER_SESSION_ID, /// Driver also depends on environment variables DRIVER_SESSION_ID,
/// BROWSER_SUPPORTS_TIMELINE, DRIVER_SESSION_URI, DRIVER_SESSION_SPEC /// BROWSER_SUPPORTS_TIMELINE, DRIVER_SESSION_URI, DRIVER_SESSION_SPEC
/// and DRIVER_SESSION_CAPABILITIES for configurations. /// and ANDROID_CHROME_ON_EMULATOR for configurations.
static Future<FlutterDriver> connectWeb( static Future<FlutterDriver> connectWeb(
{String hostUrl, Duration timeout}) async { {String hostUrl, Duration timeout}) async {
hostUrl ??= Platform.environment['VM_SERVICE_URL']; hostUrl ??= Platform.environment['VM_SERVICE_URL'];
...@@ -56,7 +54,7 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -56,7 +54,7 @@ class WebFlutterDriver extends FlutterDriver {
'session-id': Platform.environment['DRIVER_SESSION_ID'], 'session-id': Platform.environment['DRIVER_SESSION_ID'],
'session-uri': Platform.environment['DRIVER_SESSION_URI'], 'session-uri': Platform.environment['DRIVER_SESSION_URI'],
'session-spec': Platform.environment['DRIVER_SESSION_SPEC'], 'session-spec': Platform.environment['DRIVER_SESSION_SPEC'],
'session-capabilities': Platform.environment['DRIVER_SESSION_CAPABILITIES'], 'android-chrome-on-emulator': Platform.environment['ANDROID_CHROME_ON_EMULATOR'] == 'true',
}; };
final FlutterWebConnection connection = await FlutterWebConnection.connect final FlutterWebConnection connection = await FlutterWebConnection.connect
(hostUrl, settings, timeout: timeout); (hostUrl, settings, timeout: timeout);
...@@ -183,7 +181,7 @@ class FlutterWebConnection { ...@@ -183,7 +181,7 @@ class FlutterWebConnection {
_supportsTimelineAction = value; _supportsTimelineAction = value;
} }
/// Starts WebDriver with the given [capabilities] and /// Starts WebDriver with the given [settings] and
/// establishes the connection to Flutter Web application. /// establishes the connection to Flutter Web application.
static Future<FlutterWebConnection> connect( static Future<FlutterWebConnection> connect(
String url, String url,
...@@ -195,6 +193,13 @@ class FlutterWebConnection { ...@@ -195,6 +193,13 @@ class FlutterWebConnection {
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()));
if (settings['android-chrome-on-emulator'] == true) {
final Uri localUri = Uri.parse(url);
// Converts to Android Emulator Uri.
// Hardcode the host to 10.0.2.2 based on
// https://developer.android.com/studio/run/emulator-networking
url = Uri(scheme: localUri.scheme, host: '10.0.2.2', port:localUri.port).toString();
}
await driver.get(url); await driver.get(url);
await waitUntilExtensionInstalled(driver, timeout); await waitUntilExtensionInstalled(driver, timeout);
......
...@@ -14,7 +14,6 @@ import '../base/file_system.dart'; ...@@ -14,7 +14,6 @@ import '../base/file_system.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../convert.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../dart/sdk.dart'; import '../dart/sdk.dart';
import '../device.dart'; import '../device.dart';
...@@ -92,6 +91,7 @@ class DriveCommand extends RunCommandBase { ...@@ -92,6 +91,7 @@ class DriveCommand extends RunCommandBase {
'Following browsers are supported: \n' 'Following browsers are supported: \n'
'Chrome, Firefox, Safari (macOS and iOS) and Edge. Defaults to Chrome.', 'Chrome, Firefox, Safari (macOS and iOS) and Edge. Defaults to Chrome.',
allowed: <String>[ allowed: <String>[
'android-chrome',
'chrome', 'chrome',
'edge', 'edge',
'firefox', 'firefox',
...@@ -104,7 +104,11 @@ class DriveCommand extends RunCommandBase { ...@@ -104,7 +104,11 @@ class DriveCommand extends RunCommandBase {
help: 'The dimension of browser when running Flutter Web test. \n' help: 'The dimension of browser when running Flutter Web test. \n'
'This will affect screenshot and all offset-related actions. \n' 'This will affect screenshot and all offset-related actions. \n'
'By default. it is set to 1600,1024 (1600 by 1024).', 'By default. it is set to 1600,1024 (1600 by 1024).',
); )
..addFlag('android-emulator',
defaultsTo: true,
help: 'Whether to perform Flutter Driver testing on Android Emulator.'
'Works only if \'browser-name\' is set to \'android-chrome\'');
} }
@override @override
...@@ -187,7 +191,15 @@ class DriveCommand extends RunCommandBase { ...@@ -187,7 +191,15 @@ class DriveCommand extends RunCommandBase {
target: targetFile, target: targetFile,
flutterProject: flutterProject, flutterProject: flutterProject,
ipv6: ipv6, ipv6: ipv6,
debuggingOptions: DebuggingOptions.enabled(buildInfo), debuggingOptions: getBuildInfo().isRelease ?
DebuggingOptions.disabled(
getBuildInfo(),
port: stringArg('web-port')
)
: DebuggingOptions.enabled(
getBuildInfo(),
port: stringArg('web-port')
),
stayResident: false, stayResident: false,
urlTunneller: null, urlTunneller: null,
); );
...@@ -243,25 +255,27 @@ class DriveCommand extends RunCommandBase { ...@@ -243,25 +255,27 @@ class DriveCommand extends RunCommandBase {
); );
} }
final bool isAndroidChrome = browser == Browser.androidChrome;
final bool useEmulator = argResults['android-emulator'] as bool;
// set window size // set window size
final List<String> dimensions = argResults['browser-dimension'].split(',') as List<String>; // for android chrome, skip such action
assert(dimensions.length == 2); if (!isAndroidChrome) {
int x, y; final List<String> dimensions = argResults['browser-dimension'].split(
try { ',') as List<String>;
x = int.parse(dimensions[0]); assert(dimensions.length == 2);
y = int.parse(dimensions[1]); int x, y;
} on FormatException catch (ex) { try {
throwToolExit(''' x = int.parse(dimensions[0]);
y = int.parse(dimensions[1]);
} on FormatException catch (ex) {
throwToolExit('''
Dimension provided to --browser-dimension is invalid: Dimension provided to --browser-dimension is invalid:
$ex $ex
'''); ''');
} }
final async_io.Window window = await driver.window; final async_io.Window window = await driver.window;
try {
await window.setLocation(const math.Point<int>(0, 0)); await window.setLocation(const math.Point<int>(0, 0));
await window.setSize(math.Rectangle<int>(0, 0, x, y)); await window.setSize(math.Rectangle<int>(0, 0, x, y));
} on Exception {
// Error might be thrown in some browsers.
} }
// add driver info to environment variables // add driver info to environment variables
...@@ -269,9 +283,9 @@ $ex ...@@ -269,9 +283,9 @@ $ex
'DRIVER_SESSION_ID': driver.id, 'DRIVER_SESSION_ID': driver.id,
'DRIVER_SESSION_URI': driver.uri.toString(), 'DRIVER_SESSION_URI': driver.uri.toString(),
'DRIVER_SESSION_SPEC': driver.spec.toString(), 'DRIVER_SESSION_SPEC': driver.spec.toString(),
'DRIVER_SESSION_CAPABILITIES': jsonEncode(driver.capabilities),
'SUPPORT_TIMELINE_ACTION': (browser == Browser.chrome).toString(), 'SUPPORT_TIMELINE_ACTION': (browser == Browser.chrome).toString(),
'FLUTTER_WEB_TEST': 'true', 'FLUTTER_WEB_TEST': 'true',
'ANDROID_CHROME_ON_EMULATOR': (isAndroidChrome && useEmulator).toString(),
}); });
} }
...@@ -489,6 +503,8 @@ Future<bool> _stopApp(DriveCommand command) async { ...@@ -489,6 +503,8 @@ Future<bool> _stopApp(DriveCommand command) async {
/// A list of supported browsers /// A list of supported browsers
@visibleForTesting @visibleForTesting
enum Browser { enum Browser {
/// Chrome on Android: https://developer.chrome.com/multidevice/android/overview
androidChrome,
/// Chrome: https://www.google.com/chrome/ /// Chrome: https://www.google.com/chrome/
chrome, chrome,
/// Edge: https://www.microsoft.com/en-us/windows/microsoft-edge /// Edge: https://www.microsoft.com/en-us/windows/microsoft-edge
...@@ -504,6 +520,7 @@ enum Browser { ...@@ -504,6 +520,7 @@ enum Browser {
/// Converts [browserName] string to [Browser] /// Converts [browserName] string to [Browser]
Browser _browserNameToEnum(String browserName){ Browser _browserNameToEnum(String browserName){
switch (browserName) { switch (browserName) {
case 'android-chrome': return Browser.androidChrome;
case 'chrome': return Browser.chrome; case 'chrome': return Browser.chrome;
case 'edge': return Browser.edge; case 'edge': return Browser.edge;
case 'firefox': return Browser.firefox; case 'firefox': return Browser.firefox;
...@@ -592,6 +609,15 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless) { ...@@ -592,6 +609,15 @@ Map<String, dynamic> getDesiredCapabilities(Browser browser, bool headless) {
'browserName': 'safari', 'browserName': 'safari',
'safari:useSimulator': true 'safari:useSimulator': true
}; };
case Browser.androidChrome:
return <String, dynamic>{
'browserName': 'chrome',
'platformName': 'android',
'goog:chromeOptions': <String, dynamic>{
'androidPackage': 'com.android.chrome',
'args': <String>['--disable-fullscreen']
},
};
default: default:
throw UnsupportedError('Browser $browser not supported.'); throw UnsupportedError('Browser $browser not supported.');
} }
......
...@@ -713,6 +713,19 @@ void main() { ...@@ -713,6 +713,19 @@ void main() {
expect(getDesiredCapabilities(Browser.iosSafari, false), expected); expect(getDesiredCapabilities(Browser.iosSafari, false), expected);
}); });
test('android chrome', () {
final Map<String, dynamic> expected = <String, dynamic>{
'browserName': 'chrome',
'platformName': 'android',
'goog:chromeOptions': <String, dynamic>{
'androidPackage': 'com.android.chrome',
'args': <String>['--disable-fullscreen']
},
};
expect(getDesiredCapabilities(Browser.androidChrome, 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