Allow Developers to enable Accessibility testing on WebFlutterDriver and get the underlying webDriver (#65051)

This commit is contained in:
Angjie Li 2020-09-08 13:15:06 -07:00 committed by GitHub
parent 46eacc5462
commit d2fa384c31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 10 deletions

View File

@ -2,10 +2,13 @@
// 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_driver/flutter_driver.dart';
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
import 'package:webdriver/async_io.dart';
/// The following test is used as a simple smoke test for verfying Flutter
/// The following test is used as a simple smoke test for verifying Flutter
/// Framework and Flutter Web Engine integration.
void main() {
group('Hello World App', () {
@ -28,5 +31,20 @@ void main() {
test('title is correct', () async {
expect(await driver.getText(titleFinder), 'Hello, world!');
});
test('enable accessibility', () async {
await driver.enableAccessibility();
await Future<void>.delayed(const Duration(seconds: 2));
// Elements with tag "flt-semantics" would show up after enabling
// accessibility.
//
// The tag used here is based on
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/semantics/semantics.dart#L534
final WebElement element = await driver.webDriver.findElement(const By.tagName('flt-semantics'));
expect(element, isNotNull);
});
});
}

View File

@ -8,6 +8,7 @@ import 'dart:io';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:meta/meta.dart';
import 'package:vm_service_client/vm_service_client.dart';
import 'package:webdriver/async_io.dart' as async_io;
import '../common/diagnostics_tree.dart';
import '../common/error.dart';
@ -169,6 +170,14 @@ abstract class FlutterDriver {
/// Getter of serviceClient.
VMServiceClient get serviceClient => throw UnimplementedError();
/// Getter of webDriver.
async_io.WebDriver get webDriver => throw UnimplementedError();
/// Enables accessibility feature.
Future<void> enableAccessibility() async {
throw UnimplementedError();
}
/// Sends [command] to the Flutter Driver extensions.
/// This must be implemented by subclass.
///

View File

@ -12,6 +12,7 @@ import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:vm_service_client/vm_service_client.dart';
import 'package:webdriver/async_io.dart' as async_io;
import 'package:web_socket_channel/io.dart';
import '../../flutter_driver.dart';
@ -303,6 +304,9 @@ class VMServiceFlutterDriver extends FlutterDriver {
@override
VMServiceClient get serviceClient => _serviceClient;
@override
async_io.WebDriver get webDriver => throw UnsupportedError('VMServiceFlutterDriver does not support webDriver');
/// The main isolate hosting the Flutter application.
///
/// If you used the [registerExtension] API to instrument your application,
@ -316,6 +320,11 @@ class VMServiceFlutterDriver extends FlutterDriver {
/// Whether to log communication between host and app to `flutter_driver_commands.log`.
final bool _logCommunicationToFile;
@override
Future<void> enableAccessibility() async {
throw UnsupportedError('VMServiceFlutterDriver does not support enableAccessibility');
}
@override
Future<Map<String, dynamic>> sendCommand(Command command) async {
Map<String, dynamic> response;

View File

@ -30,6 +30,7 @@ class WebFlutterDriver extends FlutterDriver {
final FlutterWebConnection _connection;
DateTime _startTime;
bool _accessibilityEnabled = false;
/// Start time for tracing.
@visibleForTesting
@ -41,11 +42,15 @@ class WebFlutterDriver extends FlutterDriver {
@override
VMServiceClient get serviceClient => throw UnsupportedError('WebFlutterDriver does not support serviceClient');
@override
async_io.WebDriver get webDriver => _connection._driver;
/// Creates a driver that uses a connection provided by the given
/// [hostUrl] which would fallback to environment variable VM_SERVICE_URL.
/// Driver also depends on environment variables DRIVER_SESSION_ID,
/// BROWSER_SUPPORTS_TIMELINE, DRIVER_SESSION_URI, DRIVER_SESSION_SPEC
/// and ANDROID_CHROME_ON_EMULATOR for configurations.
/// BROWSER_SUPPORTS_TIMELINE, DRIVER_SESSION_URI, DRIVER_SESSION_SPEC,
/// DRIVER_SESSION_CAPABILITIES and ANDROID_CHROME_ON_EMULATOR for
/// configurations.
static Future<FlutterDriver> connectWeb(
{String hostUrl, Duration timeout}) async {
hostUrl ??= Platform.environment['VM_SERVICE_URL'];
@ -55,12 +60,29 @@ class WebFlutterDriver extends FlutterDriver {
'session-uri': Platform.environment['DRIVER_SESSION_URI'],
'session-spec': Platform.environment['DRIVER_SESSION_SPEC'],
'android-chrome-on-emulator': Platform.environment['ANDROID_CHROME_ON_EMULATOR'] == 'true',
'session-capabilities': Platform.environment['DRIVER_SESSION_CAPABILITIES'],
};
final FlutterWebConnection connection = await FlutterWebConnection.connect
(hostUrl, settings, timeout: timeout);
return WebFlutterDriver.connectedTo(connection);
}
@override
Future<void> enableAccessibility() async {
if (!_accessibilityEnabled) {
// Clicks the button to enable accessibility via Javascript for Desktop Web.
//
// The tag used in the script is based on
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart#L193
//
// TODO(angjieli): Support Mobile Web. (https://github.com/flutter/flutter/issues/65192)
await webDriver.execute(
'document.querySelector(\'flt-semantics-placeholder\').click();',
<String>[]);
_accessibilityEnabled = true;
}
}
@override
Future<Map<String, dynamic>> sendCommand(Command command) async {
Map<String, dynamic> response;
@ -177,12 +199,14 @@ class FlutterWebConnection {
String url,
Map<String, dynamic> settings,
{Duration timeout}) async {
// Use sync WebDriver because async version will create a 15 seconds
// overhead when quitting.
final async_io.WebDriver driver = await async_io.fromExistingSession(
settings['session-id'].toString(),
uri: Uri.parse(settings['session-uri'].toString()),
spec: _convertToSpec(settings['session-spec'].toString().toLowerCase()));
final String sessionId = settings['session-id'].toString();
final Uri sessionUri = Uri.parse(settings['session-uri'].toString());
final async_io.WebDriver driver = async_io.WebDriver(
sessionUri,
sessionId,
json.decode(settings['session-capabilities'] as String) as Map<String, dynamic>,
async_io.AsyncIoRequestClient(sessionUri.resolve('session/$sessionId/')),
_convertToSpec(settings['session-spec'].toString().toLowerCase()));
if (settings['android-chrome-on-emulator'] == true) {
final Uri localUri = Uri.parse(url);
// Converts to Android Emulator Uri.

View File

@ -768,6 +768,16 @@ void main() {
expect(driver.waitFor(find.byTooltip('foo')), throwsDriverError);
});
});
group('VMServiceFlutterDriver Unsupported error', () {
test('enableAccessibility', () async {
expect(driver.enableAccessibility(), throwsA(isA<UnsupportedError>()));
});
test('webDriver', () async {
expect(() => driver.webDriver, throwsA(isA<UnsupportedError>()));
});
});
});
group('VMServiceFlutterDriver with custom timeout', () {
@ -1117,7 +1127,7 @@ void main() {
await driver.checkHealth();
});
group('WebFlutterDriver Unimplemented error', () {
group('WebFlutterDriver Unimplemented/Unsupported error', () {
test('forceGC', () async {
expect(driver.forceGC(),
throwsA(isA<UnimplementedError>()));

View File

@ -18,6 +18,7 @@ import '../base/common.dart';
import '../base/file_system.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../dart/package_map.dart';
import '../device.dart';
import '../globals.dart' as globals;
@ -306,6 +307,7 @@ $ex
'DRIVER_SESSION_ID': driver.id,
'DRIVER_SESSION_URI': driver.uri.toString(),
'DRIVER_SESSION_SPEC': driver.spec.toString(),
'DRIVER_SESSION_CAPABILITIES': json.encode(driver.capabilities),
'SUPPORT_TIMELINE_ACTION': (browser == Browser.chrome).toString(),
'FLUTTER_WEB_TEST': 'true',
'ANDROID_CHROME_ON_EMULATOR': (isAndroidChrome && useEmulator).toString(),