[web] validate WebDriver responses (#96884)
Validate WebDriver responses
This commit is contained in:
parent
5775100d00
commit
99a09be203
@ -105,25 +105,60 @@ class WebFlutterDriver extends FlutterDriver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DriverError _createMalformedExtensionResponseError(Object? data) {
|
||||||
|
throw DriverError(
|
||||||
|
'Received malformed response from the FlutterDriver extension.\n'
|
||||||
|
'Expected a JSON map containing a "response" field and, optionally, an '
|
||||||
|
'"isError" field, but got ${data.runtimeType}: $data'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> sendCommand(Command command) async {
|
Future<Map<String, dynamic>> sendCommand(Command command) async {
|
||||||
Map<String, dynamic> response;
|
final Map<String, dynamic> response;
|
||||||
|
final Object? data;
|
||||||
final Map<String, String> serialized = command.serialize();
|
final Map<String, String> serialized = command.serialize();
|
||||||
_logCommunication('>>> $serialized');
|
_logCommunication('>>> $serialized');
|
||||||
try {
|
try {
|
||||||
final dynamic data = await _connection.sendCommand("window.\$flutterDriver('${jsonEncode(serialized)}')", command.timeout);
|
data = await _connection.sendCommand("window.\$flutterDriver('${jsonEncode(serialized)}')", command.timeout);
|
||||||
response = data != null ? (json.decode(data as String) as Map<String, dynamic>?)! : <String, dynamic>{};
|
|
||||||
|
// The returned data is expected to be a string. If it's null or anything
|
||||||
|
// other than a string, something's wrong.
|
||||||
|
if (data is! String) {
|
||||||
|
throw _createMalformedExtensionResponseError(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object? decoded = json.decode(data);
|
||||||
|
if (decoded is! Map<String, dynamic>) {
|
||||||
|
throw _createMalformedExtensionResponseError(data);
|
||||||
|
} else {
|
||||||
|
response = decoded;
|
||||||
|
}
|
||||||
|
|
||||||
_logCommunication('<<< $response');
|
_logCommunication('<<< $response');
|
||||||
|
} on DriverError catch(_) {
|
||||||
|
rethrow;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
throw DriverError(
|
throw DriverError(
|
||||||
"Failed to respond to $command due to remote error\n : \$flutterDriver('${jsonEncode(serialized)}')",
|
'FlutterDriver command ${command.runtimeType} failed due to a remote error.\n'
|
||||||
|
'Command sent: ${jsonEncode(serialized)}',
|
||||||
error,
|
error,
|
||||||
stackTrace
|
stackTrace
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (response['isError'] == true)
|
|
||||||
throw DriverError('Error in Flutter application: ${response['response']}');
|
final Object? isError = response['isError'];
|
||||||
return response['response'] as Map<String, dynamic>;
|
final Object? responseData = response['response'];
|
||||||
|
if (isError is! bool?) {
|
||||||
|
throw _createMalformedExtensionResponseError(data);
|
||||||
|
} else if (isError == true) {
|
||||||
|
throw DriverError('Error in Flutter application: $responseData');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseData is! Map<String, dynamic>) {
|
||||||
|
throw _createMalformedExtensionResponseError(data);
|
||||||
|
}
|
||||||
|
return responseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -745,8 +745,8 @@ void main() {
|
|||||||
const String waitForCommandLog = '>>> {command: waitFor, timeout: 1234, finderType: ByTooltipMessage, text: logCommunicationToFile test}';
|
const String waitForCommandLog = '>>> {command: waitFor, timeout: 1234, finderType: ByTooltipMessage, text: logCommunicationToFile test}';
|
||||||
const String responseLog = '<<< {isError: false, response: {status: ok}, type: Response}';
|
const String responseLog = '<<< {isError: false, response: {status: ok}, type: Response}';
|
||||||
|
|
||||||
expect(commandLog.contains(waitForCommandLog), true, reason: '$commandLog not contains $waitForCommandLog');
|
expect(commandLog, contains(waitForCommandLog), reason: '$commandLog not contains $waitForCommandLog');
|
||||||
expect(commandLog.contains(responseLog), true, reason: '$commandLog not contains $responseLog');
|
expect(commandLog, contains(responseLog), reason: '$commandLog not contains $responseLog');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('logCommunicationToFile = false', () async {
|
test('logCommunicationToFile = false', () async {
|
||||||
|
139
packages/flutter_driver/test/src/web_tests/web_driver_test.dart
Normal file
139
packages/flutter_driver/test/src/web_tests/web_driver_test.dart
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2014 The Flutter 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 'package:flutter_driver/src/common/error.dart';
|
||||||
|
import 'package:flutter_driver/src/common/health.dart';
|
||||||
|
import 'package:flutter_driver/src/driver/web_driver.dart';
|
||||||
|
import 'package:webdriver/src/common/log.dart';
|
||||||
|
|
||||||
|
import '../../common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('WebDriver', () {
|
||||||
|
late FakeFlutterWebConnection fakeConnection;
|
||||||
|
late WebFlutterDriver driver;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fakeConnection = FakeFlutterWebConnection();
|
||||||
|
driver = WebFlutterDriver.connectedTo(fakeConnection);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand succeeds', () async {
|
||||||
|
fakeConnection.fakeResponse = '''
|
||||||
|
{
|
||||||
|
"isError": false,
|
||||||
|
"response": {
|
||||||
|
"test": "hello"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
final Map<String, Object?> response = await driver.sendCommand(const GetHealth());
|
||||||
|
expect(response['test'], 'hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand fails on communication error', () async {
|
||||||
|
fakeConnection.communicationError = Error();
|
||||||
|
expect(
|
||||||
|
() => driver.sendCommand(const GetHealth()),
|
||||||
|
_throwsDriverErrorWithMessage(
|
||||||
|
'FlutterDriver command GetHealth failed due to a remote error.\n'
|
||||||
|
'Command sent: {"command":"get_health"}'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand fails on null', () async {
|
||||||
|
fakeConnection.fakeResponse = null;
|
||||||
|
expect(
|
||||||
|
() => driver.sendCommand(const GetHealth()),
|
||||||
|
_throwsDriverErrorWithDataString('Null', 'null'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand fails when response data is not a string', () async {
|
||||||
|
fakeConnection.fakeResponse = 1234;
|
||||||
|
expect(
|
||||||
|
() => driver.sendCommand(const GetHealth()),
|
||||||
|
_throwsDriverErrorWithDataString('int', '1234'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand fails when isError is true', () async {
|
||||||
|
fakeConnection.fakeResponse = '''
|
||||||
|
{
|
||||||
|
"isError": true,
|
||||||
|
"response": "test error message"
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
expect(
|
||||||
|
() => driver.sendCommand(const GetHealth()),
|
||||||
|
_throwsDriverErrorWithMessage(
|
||||||
|
'Error in Flutter application: test error message'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand fails when isError is not bool', () async {
|
||||||
|
fakeConnection.fakeResponse = '{ "isError": 5 }';
|
||||||
|
expect(
|
||||||
|
() => driver.sendCommand(const GetHealth()),
|
||||||
|
_throwsDriverErrorWithDataString('String', '{ "isError": 5 }'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sendCommand fails when "response" field is not a JSON map', () async {
|
||||||
|
fakeConnection.fakeResponse = '{ "response": 5 }';
|
||||||
|
expect(
|
||||||
|
() => driver.sendCommand(const GetHealth()),
|
||||||
|
_throwsDriverErrorWithDataString('String', '{ "response": 5 }'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher _throwsDriverErrorWithMessage(String expectedMessage) {
|
||||||
|
return throwsA(allOf(
|
||||||
|
isA<DriverError>(),
|
||||||
|
predicate<DriverError>((DriverError error) {
|
||||||
|
final String actualMessage = error.message;
|
||||||
|
return actualMessage == expectedMessage;
|
||||||
|
}, 'contains message: $expectedMessage'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher _throwsDriverErrorWithDataString(String dataType, String dataString) {
|
||||||
|
return _throwsDriverErrorWithMessage(
|
||||||
|
'Received malformed response from the FlutterDriver extension.\n'
|
||||||
|
'Expected a JSON map containing a "response" field and, optionally, an '
|
||||||
|
'"isError" field, but got $dataType: $dataString'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeFlutterWebConnection implements FlutterWebConnection {
|
||||||
|
@override
|
||||||
|
bool supportsTimelineAction = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<LogEntry> get logs => throw UnimplementedError();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<int>> screenshot() {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object? fakeResponse;
|
||||||
|
Error? communicationError;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Object?> sendCommand(String script, Duration? duration) async {
|
||||||
|
if (communicationError != null) {
|
||||||
|
throw communicationError!;
|
||||||
|
}
|
||||||
|
return fakeResponse;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user