Unverified Commit 99a09be2 authored by Yegor's avatar Yegor Committed by GitHub

[web] validate WebDriver responses (#96884)

Validate WebDriver responses
parent 5775100d
......@@ -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 {
......
// 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;
}
}
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