[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
|
||||
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();
|
||||
_logCommunication('>>> $serialized');
|
||||
try {
|
||||
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>{};
|
||||
data = await _connection.sendCommand("window.\$flutterDriver('${jsonEncode(serialized)}')", command.timeout);
|
||||
|
||||
// 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');
|
||||
} on DriverError catch(_) {
|
||||
rethrow;
|
||||
} catch (error, stackTrace) {
|
||||
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,
|
||||
stackTrace
|
||||
);
|
||||
}
|
||||
if (response['isError'] == true)
|
||||
throw DriverError('Error in Flutter application: ${response['response']}');
|
||||
return response['response'] as Map<String, dynamic>;
|
||||
|
||||
final Object? isError = response['isError'];
|
||||
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
|
||||
|
@ -745,8 +745,8 @@ void main() {
|
||||
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');
|
||||
expect(commandLog, contains(waitForCommandLog), reason: '$commandLog not contains $waitForCommandLog');
|
||||
expect(commandLog, contains(responseLog), reason: '$commandLog not contains $responseLog');
|
||||
});
|
||||
|
||||
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