Unverified Commit 3cfbb958 authored by Nguyen Phuc Loi's avatar Nguyen Phuc Loi Committed by GitHub

[flutter_driver] support log communication for WebFlutterDriver (#81150)

parent af3337b6
...@@ -147,7 +147,12 @@ abstract class FlutterDriver { ...@@ -147,7 +147,12 @@ abstract class FlutterDriver {
Map<String, dynamic>? headers, Map<String, dynamic>? headers,
}) async { }) async {
if (Platform.environment['FLUTTER_WEB_TEST'] != null) { if (Platform.environment['FLUTTER_WEB_TEST'] != null) {
return WebFlutterDriver.connectWeb(hostUrl: dartVmServiceUrl, timeout: timeout); return WebFlutterDriver.connectWeb(
hostUrl: dartVmServiceUrl,
timeout: timeout,
printCommunication: printCommunication,
logCommunicationToFile: logCommunicationToFile,
);
} }
return VMServiceFlutterDriver.connect( return VMServiceFlutterDriver.connect(
dartVmServiceUrl: dartVmServiceUrl, dartVmServiceUrl: dartVmServiceUrl,
......
...@@ -4,15 +4,19 @@ ...@@ -4,15 +4,19 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:file/file.dart';
import 'package:matcher/matcher.dart'; import 'package:matcher/matcher.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:vm_service/vm_service.dart' as vms; import 'package:vm_service/vm_service.dart' as vms;
import 'package:webdriver/async_io.dart' as async_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';
import '../common/message.dart'; import '../common/message.dart';
import 'common.dart';
import 'driver.dart'; import 'driver.dart';
import 'timeline.dart'; import 'timeline.dart';
...@@ -24,12 +28,22 @@ import 'timeline.dart'; ...@@ -24,12 +28,22 @@ import 'timeline.dart';
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].
WebFlutterDriver.connectedTo(this._connection) : WebFlutterDriver.connectedTo(
_startTime = DateTime.now(); this._connection, {
bool printCommunication = false,
bool logCommunicationToFile = true,
}) : _printCommunication = printCommunication,
_logCommunicationToFile = logCommunicationToFile,
_startTime = DateTime.now(),
_driverId = _nextDriverId++;
final FlutterWebConnection _connection; final FlutterWebConnection _connection;
DateTime _startTime; DateTime _startTime;
bool _accessibilityEnabled = false; bool _accessibilityEnabled = false;
static int _nextDriverId = 0;
/// The unique ID of this driver instance.
final int _driverId;
/// Start time for tracing. /// Start time for tracing.
@visibleForTesting @visibleForTesting
...@@ -44,14 +58,26 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -44,14 +58,26 @@ class WebFlutterDriver extends FlutterDriver {
@override @override
async_io.WebDriver get webDriver => _connection._driver; async_io.WebDriver get webDriver => _connection._driver;
/// Whether to print communication between host and app to `stdout`.
final bool _printCommunication;
/// Whether to log communication between host and app to `flutter_driver_commands.log`.
final bool _logCommunicationToFile;
/// Creates a driver that uses a connection provided by the given /// Creates a driver that uses a connection provided by the given
/// [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,
/// DRIVER_SESSION_CAPABILITIES and ANDROID_CHROME_ON_EMULATOR for /// DRIVER_SESSION_CAPABILITIES and ANDROID_CHROME_ON_EMULATOR for
/// configurations. /// configurations.
static Future<FlutterDriver> connectWeb( ///
{String? hostUrl, Duration? timeout}) async { /// See [FlutterDriver.connect] for more documentation.
static Future<FlutterDriver> connectWeb({
String? hostUrl,
bool printCommunication = false,
bool logCommunicationToFile = true,
Duration? timeout,
}) async {
hostUrl ??= Platform.environment['VM_SERVICE_URL']; hostUrl ??= Platform.environment['VM_SERVICE_URL'];
final Map<String, dynamic> settings = <String, dynamic>{ final Map<String, dynamic> settings = <String, dynamic>{
'support-timeline-action': Platform.environment['SUPPORT_TIMELINE_ACTION'] == 'true', 'support-timeline-action': Platform.environment['SUPPORT_TIMELINE_ACTION'] == 'true',
...@@ -63,7 +89,11 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -63,7 +89,11 @@ class WebFlutterDriver extends FlutterDriver {
}; };
final FlutterWebConnection connection = await FlutterWebConnection.connect final FlutterWebConnection connection = await FlutterWebConnection.connect
(hostUrl!, settings, timeout: timeout); (hostUrl!, settings, timeout: timeout);
return WebFlutterDriver.connectedTo(connection); return WebFlutterDriver.connectedTo(
connection,
printCommunication: printCommunication,
logCommunicationToFile: logCommunicationToFile,
);
} }
@override @override
...@@ -86,9 +116,11 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -86,9 +116,11 @@ class WebFlutterDriver extends FlutterDriver {
Future<Map<String, dynamic>> sendCommand(Command command) async { Future<Map<String, dynamic>> sendCommand(Command command) async {
Map<String, dynamic> response; Map<String, dynamic> response;
final Map<String, String> serialized = command.serialize(); final Map<String, String> serialized = command.serialize();
_logCommunication('>>> $serialized');
try { try {
final dynamic data = await _connection.sendCommand("window.\$flutterDriver('${jsonEncode(serialized)}')", command.timeout); final dynamic data = await _connection.sendCommand("window.\$flutterDriver('${jsonEncode(serialized)}')", command.timeout);
response = data != null ? (json.decode(data as String) as Map<String, dynamic>?)! : <String, dynamic>{}; response = data != null ? (json.decode(data as String) as Map<String, dynamic>?)! : <String, dynamic>{};
_logCommunication('<<< $response');
} catch (error, stackTrace) { } catch (error, stackTrace) {
throw DriverError("Failed to respond to $command due to remote error\n : \$flutterDriver('${jsonEncode(serialized)}')", throw DriverError("Failed to respond to $command due to remote error\n : \$flutterDriver('${jsonEncode(serialized)}')",
error, error,
...@@ -108,6 +140,17 @@ class WebFlutterDriver extends FlutterDriver { ...@@ -108,6 +140,17 @@ class WebFlutterDriver extends FlutterDriver {
throw UnimplementedError(); throw UnimplementedError();
} }
void _logCommunication(String message) {
if (_printCommunication) {
driverLog('WebFlutterDriver', message);
}
if (_logCommunicationToFile) {
final File file = fs.file(path.join(testOutputsDirectory, 'flutter_driver_commands_$_driverId.log'));
file.createSync(recursive: true); // no-op if file exists
file.writeAsStringSync('${DateTime.now()} $message\n', mode: FileMode.append, flush: true);
}
}
@override @override
Future<List<int>> screenshot() async { Future<List<int>> screenshot() async {
await Future<void>.delayed(const Duration(seconds: 2)); await Future<void>.delayed(const Duration(seconds: 2));
......
...@@ -4,14 +4,17 @@ ...@@ -4,14 +4,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:fake_async/fake_async.dart'; import 'package:fake_async/fake_async.dart';
import 'package:flutter_driver/src/common/error.dart'; import 'package:flutter_driver/src/common/error.dart';
import 'package:flutter_driver/src/common/health.dart'; import 'package:flutter_driver/src/common/health.dart';
import 'package:flutter_driver/src/common/layer_tree.dart'; import 'package:flutter_driver/src/common/layer_tree.dart';
import 'package:flutter_driver/src/common/wait.dart'; import 'package:flutter_driver/src/common/wait.dart';
import 'package:flutter_driver/src/driver/common.dart';
import 'package:flutter_driver/src/driver/driver.dart'; import 'package:flutter_driver/src/driver/driver.dart';
import 'package:flutter_driver/src/driver/timeline.dart'; import 'package:flutter_driver/src/driver/timeline.dart';
import 'package:path/path.dart' as path;
import 'package:vm_service/vm_service.dart' as vms; import 'package:vm_service/vm_service.dart' as vms;
import '../../common.dart'; import '../../common.dart';
...@@ -24,10 +27,87 @@ const String _kWebScriptSuffix = "')"; ...@@ -24,10 +27,87 @@ const String _kWebScriptSuffix = "')";
void main() { void main() {
final List<String> log = <String>[]; final List<String> log = <String>[];
driverLog = (String source, String message) { driverLog = (String source, String message) {
log.add('$source: $message'); log.add('$source: $message');
}; };
group('VMServiceFlutterDriver with logCommunicationToFile', () {
late FakeVmService fakeClient;
late FakeVM fakeVM;
late FakeIsolate fakeIsolate;
late VMServiceFlutterDriver driver;
int driverId = -1;
setUp(() {
fakeIsolate = FakeIsolate();
fakeVM = FakeVM(fakeIsolate);
fakeClient = FakeVmService(fakeVM);
fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{'status':'ok'});
driverId += 1;
});
group('logCommunicationToFile', () {
test('logCommunicationToFile = true', () async {
driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
final File file = File(path.join(testOutputsDirectory, 'flutter_driver_commands_$driverId.log'));
final bool exists = file.existsSync();
expect(exists, true, reason: 'Not found ${file.path}');
final String commandLog = await file.readAsString();
const String waitForCommandLog = '>>> {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}';
const String responseLog = '<<< {isError: false, response: {status: ok}}';
expect(commandLog.contains(waitForCommandLog), true, reason: '$commandLog not contains $waitForCommandLog');
expect(commandLog.contains(responseLog), true, reason: '$commandLog not contains $responseLog');
});
test('logCommunicationToFile = false', () async {
driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate, logCommunicationToFile: false);
await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
final File file = File(path.join(testOutputsDirectory, 'flutter_driver_commands_$driverId.log'));
final bool exists = file.existsSync();
expect(exists, false, reason: 'because ${file.path} exists');
});
});
});
group('VMServiceFlutterDriver with printCommunication', () {
late FakeVmService fakeClient;
late FakeVM fakeVM;
late FakeIsolate fakeIsolate;
late VMServiceFlutterDriver driver;
setUp(() async {
log.clear();
fakeIsolate = FakeIsolate();
fakeVM = FakeVM(fakeIsolate);
fakeClient = FakeVmService(fakeVM);
fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{'status':'ok'});
});
test('printCommunication = true', () async {
driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate, printCommunication: true);
await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
expect(log, <String>[
'VMServiceFlutterDriver: >>> {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}',
'VMServiceFlutterDriver: <<< {isError: false, response: {status: ok}}'
]);
});
test('printCommunication = false', () async {
driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate, printCommunication: false);
await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
expect(log, <String>[]);
});
});
group('VMServiceFlutterDriver.connect', () { group('VMServiceFlutterDriver.connect', () {
late FakeVmService fakeClient; late FakeVmService fakeClient;
late FakeVM fakeVM; late FakeVM fakeVM;
...@@ -188,8 +268,8 @@ void main() { ...@@ -188,8 +268,8 @@ void main() {
group('VMServiceFlutterDriver', () { group('VMServiceFlutterDriver', () {
late FakeVmService fakeClient; late FakeVmService fakeClient;
FakeVM fakeVM; late FakeVM fakeVM;
FakeIsolate fakeIsolate; late FakeIsolate fakeIsolate;
late VMServiceFlutterDriver driver; late VMServiceFlutterDriver driver;
setUp(() { setUp(() {
...@@ -563,8 +643,8 @@ void main() { ...@@ -563,8 +643,8 @@ void main() {
group('VMServiceFlutterDriver with custom timeout', () { group('VMServiceFlutterDriver with custom timeout', () {
late FakeVmService fakeClient; late FakeVmService fakeClient;
FakeVM fakeVM; late FakeVM fakeVM;
FakeIsolate fakeIsolate; late FakeIsolate fakeIsolate;
late VMServiceFlutterDriver driver; late VMServiceFlutterDriver driver;
setUp(() { setUp(() {
...@@ -592,6 +672,70 @@ void main() { ...@@ -592,6 +672,70 @@ void main() {
}); });
}); });
group('WebFlutterDriver with logCommunicationToFile', () {
late FakeFlutterWebConnection fakeConnection;
late WebFlutterDriver driver;
int driverId = -1;
setUp(() {
fakeConnection = FakeFlutterWebConnection();
fakeConnection.supportsTimelineAction = true;
fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'}));
driverId += 1;
});
test('logCommunicationToFile = true', () async {
driver = WebFlutterDriver.connectedTo(fakeConnection);
await driver.waitFor(find.byTooltip('logCommunicationToFile test'), timeout: _kTestTimeout);
final File file = File(path.join(testOutputsDirectory, 'flutter_driver_commands_$driverId.log'));
final bool exists = file.existsSync();
expect(exists, true, reason: 'Not found ${file.path}');
final String commandLog = await file.readAsString();
const String waitForCommandLog = '>>> {command: waitFor, timeout: 1234, finderType: ByTooltipMessage, text: logCommunicationToFile test}';
const String responseLog = '<<< {isError: false, response: {status: ok}, type: Response}';
expect(commandLog.contains(waitForCommandLog), true, reason: '$commandLog not contains $waitForCommandLog');
expect(commandLog.contains(responseLog), true, reason: '$commandLog not contains $responseLog');
});
test('logCommunicationToFile = false', () async {
driver = WebFlutterDriver.connectedTo(fakeConnection, logCommunicationToFile: false);
await driver.waitFor(find.byTooltip('logCommunicationToFile test'), timeout: _kTestTimeout);
final File file = File(path.join(testOutputsDirectory, 'flutter_driver_commands_$driverId.log'));
final bool exists = file.existsSync();
expect(exists, false, reason: 'because ${file.path} exists');
});
});
group('WebFlutterDriver with printCommunication', () {
late FakeFlutterWebConnection fakeConnection;
late WebFlutterDriver driver;
setUp(() {
log.clear();
fakeConnection = FakeFlutterWebConnection();
fakeConnection.supportsTimelineAction = true;
fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'}));
});
test('printCommunication = true', () async {
driver = WebFlutterDriver.connectedTo(fakeConnection, printCommunication: true);
await driver.waitFor(find.byTooltip('printCommunication test'), timeout: _kTestTimeout);
expect(log, <String>[
'WebFlutterDriver: >>> {command: waitFor, timeout: 1234, finderType: ByTooltipMessage, text: printCommunication test}',
'WebFlutterDriver: <<< {isError: false, response: {status: ok}, type: Response}',
]);
});
test('printCommunication = false', () async {
driver = WebFlutterDriver.connectedTo(fakeConnection, printCommunication: false);
await driver.waitFor(find.byTooltip('printCommunication test'), timeout: _kTestTimeout);
expect(log, <String>[]);
});
});
group('WebFlutterDriver', () { group('WebFlutterDriver', () {
late FakeFlutterWebConnection fakeConnection; late FakeFlutterWebConnection fakeConnection;
late WebFlutterDriver driver; late WebFlutterDriver driver;
......
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