Unverified Commit 9c3754d9 authored by Yegor's avatar Yegor Committed by GitHub

FlutterDriver: allow customizing timeouts using a multiplier (#24066)

FlutterDriver: allow customizing timeouts using a multiplier
parent eb35f892
...@@ -61,25 +61,38 @@ enum TimelineStream { ...@@ -61,25 +61,38 @@ enum TimelineStream {
const List<TimelineStream> _defaultStreams = <TimelineStream>[TimelineStream.all]; const List<TimelineStream> _defaultStreams = <TimelineStream>[TimelineStream.all];
/// Multiplies the timeout values used when establishing a connection to the
/// Flutter app under test and obtain an instance of [FlutterDriver].
///
/// This multiplier applies automatically when using the default implementation
/// of the [vmServiceConnectFunction].
///
/// See also:
///
/// * [FlutterDriver.timeoutMultiplier], which multiplies all command timeouts by this number.
double connectionTimeoutMultiplier = _kDefaultTimeoutMultiplier;
const double _kDefaultTimeoutMultiplier = 1.0;
/// Default timeout for short-running RPCs. /// Default timeout for short-running RPCs.
const Duration _kShortTimeout = Duration(seconds: 5); Duration _shortTimeout(double multiplier) => const Duration(seconds: 5) * multiplier;
/// Default timeout for awaiting an Isolate to become runnable. /// Default timeout for awaiting an Isolate to become runnable.
const Duration _kIsolateLoadRunnableTimeout = Duration(minutes: 1); Duration _isolateLoadRunnableTimeout(double multiplier) => const Duration(minutes: 1) * multiplier;
/// Time to delay before driving a Fuchsia module. /// Time to delay before driving a Fuchsia module.
const Duration _kFuchsiaDriveDelay = Duration(milliseconds: 500); Duration _fuchsiaDriveDelay(double multiplier) => const Duration(milliseconds: 500) * multiplier;
/// Default timeout for long-running RPCs. /// Default timeout for long-running RPCs.
final Duration _kLongTimeout = _kShortTimeout * 6; Duration _longTimeout(double multiplier) => _shortTimeout(multiplier) * 6;
/// Additional amount of time we give the command to finish or timeout remotely /// Additional amount of time we give the command to finish or timeout remotely
/// before timing out locally. /// before timing out locally.
final Duration _kRpcGraceTime = _kShortTimeout ~/ 2; Duration _rpcGraceTime(double multiplier) => _shortTimeout(multiplier) ~/ 2;
/// The amount of time we wait prior to making the next attempt to connect to /// The amount of time we wait prior to making the next attempt to connect to
/// the VM service. /// the VM service.
final Duration _kPauseBetweenReconnectAttempts = _kShortTimeout ~/ 5; Duration _pauseBetweenReconnectAttempts(double multiplier) => _shortTimeout(multiplier) ~/ 5;
// See https://github.com/dart-lang/sdk/blob/master/runtime/vm/timeline.cc#L32 // See https://github.com/dart-lang/sdk/blob/master/runtime/vm/timeline.cc#L32
String _timelineStreamsToString(List<TimelineStream> streams) { String _timelineStreamsToString(List<TimelineStream> streams) {
...@@ -129,6 +142,7 @@ class FlutterDriver { ...@@ -129,6 +142,7 @@ class FlutterDriver {
this._appIsolate, { this._appIsolate, {
bool printCommunication = false, bool printCommunication = false,
bool logCommunicationToFile = true, bool logCommunicationToFile = true,
this.timeoutMultiplier = _kDefaultTimeoutMultiplier,
}) : _printCommunication = printCommunication, }) : _printCommunication = printCommunication,
_logCommunicationToFile = logCommunicationToFile, _logCommunicationToFile = logCommunicationToFile,
_driverId = _nextDriverId++; _driverId = _nextDriverId++;
...@@ -155,12 +169,15 @@ class FlutterDriver { ...@@ -155,12 +169,15 @@ class FlutterDriver {
/// [logCommunicationToFile] determines whether the command communication /// [logCommunicationToFile] determines whether the command communication
/// between the test and the app should be logged to `flutter_driver_commands.log`. /// between the test and the app should be logged to `flutter_driver_commands.log`.
/// ///
/// [FlutterDriver] multiplies all command timeouts by [timeoutMultiplier].
///
/// [isolateNumber] (optional) determines the specific isolate to connect to. /// [isolateNumber] (optional) determines the specific isolate to connect to.
/// If this is left as `null`, will connect to the first isolate found /// If this is left as `null`, will connect to the first isolate found
/// running on [dartVmServiceUrl]. /// running on [dartVmServiceUrl].
/// ///
/// [isolateReadyTimeout] determines how long after we connect to the VM /// [isolateReadyTimeout] determines how long after we connect to the VM
/// service we will wait for the first isolate to become runnable. /// service we will wait for the first isolate to become runnable. Explicitly
/// specified non-null values are not affected by [timeoutMultiplier].
/// ///
/// [fuchsiaModuleTarget] (optional) If running on a Fuchsia Device, either /// [fuchsiaModuleTarget] (optional) If running on a Fuchsia Device, either
/// this or the environment variable `FUCHSIA_MODULE_TARGET` must be set. This /// this or the environment variable `FUCHSIA_MODULE_TARGET` must be set. This
...@@ -170,10 +187,12 @@ class FlutterDriver { ...@@ -170,10 +187,12 @@ class FlutterDriver {
String dartVmServiceUrl, String dartVmServiceUrl,
bool printCommunication = false, bool printCommunication = false,
bool logCommunicationToFile = true, bool logCommunicationToFile = true,
double timeoutMultiplier = _kDefaultTimeoutMultiplier,
int isolateNumber, int isolateNumber,
Duration isolateReadyTimeout = _kIsolateLoadRunnableTimeout, Duration isolateReadyTimeout,
Pattern fuchsiaModuleTarget, Pattern fuchsiaModuleTarget,
}) async { }) async {
isolateReadyTimeout ??= _isolateLoadRunnableTimeout(timeoutMultiplier);
// If running on a Fuchsia device, connect to the first Isolate whose name // If running on a Fuchsia device, connect to the first Isolate whose name
// matches FUCHSIA_MODULE_TARGET. // matches FUCHSIA_MODULE_TARGET.
// //
...@@ -191,7 +210,7 @@ class FlutterDriver { ...@@ -191,7 +210,7 @@ class FlutterDriver {
final List<fuchsia.IsolateRef> refs = final List<fuchsia.IsolateRef> refs =
await fuchsiaConnection.getMainIsolatesByPattern(fuchsiaModuleTarget); await fuchsiaConnection.getMainIsolatesByPattern(fuchsiaModuleTarget);
final fuchsia.IsolateRef ref = refs.first; final fuchsia.IsolateRef ref = refs.first;
await Future<void>.delayed(_kFuchsiaDriveDelay); await Future<void>.delayed(_fuchsiaDriveDelay(timeoutMultiplier));
isolateNumber = ref.number; isolateNumber = ref.number;
dartVmServiceUrl = ref.dartVm.uri.toString(); dartVmServiceUrl = ref.dartVm.uri.toString();
await fuchsiaConnection.stop(); await fuchsiaConnection.stop();
...@@ -213,6 +232,7 @@ class FlutterDriver { ...@@ -213,6 +232,7 @@ class FlutterDriver {
// Connect to Dart VM services // Connect to Dart VM services
_log.info('Connecting to Flutter application at $dartVmServiceUrl'); _log.info('Connecting to Flutter application at $dartVmServiceUrl');
connectionTimeoutMultiplier = timeoutMultiplier;
final VMServiceClientConnection connection = final VMServiceClientConnection connection =
await vmServiceConnectFunction(dartVmServiceUrl); await vmServiceConnectFunction(dartVmServiceUrl);
final VMServiceClient client = connection.client; final VMServiceClient client = connection.client;
...@@ -243,7 +263,7 @@ class FlutterDriver { ...@@ -243,7 +263,7 @@ class FlutterDriver {
isolate.pauseEvent is! VMPauseExceptionEvent && isolate.pauseEvent is! VMPauseExceptionEvent &&
isolate.pauseEvent is! VMPauseInterruptedEvent && isolate.pauseEvent is! VMPauseInterruptedEvent &&
isolate.pauseEvent is! VMResumeEvent) { isolate.pauseEvent is! VMResumeEvent) {
await Future<void>.delayed(_kShortTimeout ~/ 10); await Future<void>.delayed(_shortTimeout(timeoutMultiplier) ~/ 10);
isolate = await isolateRef.loadRunnable(); isolate = await isolateRef.loadRunnable();
} }
...@@ -251,6 +271,7 @@ class FlutterDriver { ...@@ -251,6 +271,7 @@ class FlutterDriver {
client, connection.peer, isolate, client, connection.peer, isolate,
printCommunication: printCommunication, printCommunication: printCommunication,
logCommunicationToFile: logCommunicationToFile, logCommunicationToFile: logCommunicationToFile,
timeoutMultiplier: timeoutMultiplier,
); );
// Attempts to resume the isolate, but does not crash if it fails because // Attempts to resume the isolate, but does not crash if it fails because
...@@ -311,7 +332,7 @@ class FlutterDriver { ...@@ -311,7 +332,7 @@ class FlutterDriver {
_log.trace('Waiting for service extension'); _log.trace('Waiting for service extension');
// We will never receive the extension event if the user does not // We will never receive the extension event if the user does not
// register it. If that happens time out. // register it. If that happens time out.
await whenServiceExtensionReady.timeout(_kLongTimeout * 2); await whenServiceExtensionReady.timeout(_longTimeout(timeoutMultiplier) * 2);
} on TimeoutException catch (_) { } on TimeoutException catch (_) {
throw DriverError( throw DriverError(
'Timed out waiting for Flutter Driver extension to become available. ' 'Timed out waiting for Flutter Driver extension to become available. '
...@@ -352,7 +373,7 @@ class FlutterDriver { ...@@ -352,7 +373,7 @@ class FlutterDriver {
'registered.' 'registered.'
); );
await enableIsolateStreams(); await enableIsolateStreams();
await waitForServiceExtension().timeout(_kLongTimeout * 2); await waitForServiceExtension().timeout(_longTimeout(timeoutMultiplier) * 2);
return driver.checkHealth(); return driver.checkHealth();
} }
} }
...@@ -369,17 +390,30 @@ class FlutterDriver { ...@@ -369,17 +390,30 @@ class FlutterDriver {
/// The unique ID of this driver instance. /// The unique ID of this driver instance.
final int _driverId; final int _driverId;
/// Client connected to the Dart VM running the Flutter application /// Client connected to the Dart VM running the Flutter application
final VMServiceClient _serviceClient; final VMServiceClient _serviceClient;
/// JSON-RPC client useful for sending raw JSON requests. /// JSON-RPC client useful for sending raw JSON requests.
final rpc.Peer _peer; final rpc.Peer _peer;
/// The main isolate hosting the Flutter application /// The main isolate hosting the Flutter application
final VMIsolate _appIsolate; final VMIsolate _appIsolate;
/// Whether to print communication between host and app to `stdout`. /// Whether to print communication between host and app to `stdout`.
final bool _printCommunication; final bool _printCommunication;
/// Whether to log communication between host and app to `flutter_driver_commands.log`. /// Whether to log communication between host and app to `flutter_driver_commands.log`.
final bool _logCommunicationToFile; final bool _logCommunicationToFile;
/// [FlutterDriver] multiplies all command timeouts by this number.
///
/// The right amount of time a driver command should be given to complete
/// depends on various environmental factors, such as the speed of the
/// device or the emulator, connection speed and latency, and others. Use
/// this multiplier to tailor the timeouts to your environment.
final double timeoutMultiplier;
Future<Map<String, dynamic>> _sendCommand(Command command) async { Future<Map<String, dynamic>> _sendCommand(Command command) async {
Map<String, dynamic> response; Map<String, dynamic> response;
try { try {
...@@ -387,7 +421,7 @@ class FlutterDriver { ...@@ -387,7 +421,7 @@ class FlutterDriver {
_logCommunication('>>> $serialized'); _logCommunication('>>> $serialized');
response = await _appIsolate response = await _appIsolate
.invokeExtension(_flutterExtensionMethodName, serialized) .invokeExtension(_flutterExtensionMethodName, serialized)
.timeout(command.timeout + _kRpcGraceTime); .timeout(command.timeout + _rpcGraceTime(timeoutMultiplier));
_logCommunication('<<< $response'); _logCommunication('<<< $response');
} on TimeoutException catch (error, stackTrace) { } on TimeoutException catch (error, stackTrace) {
throw DriverError( throw DriverError(
...@@ -419,26 +453,31 @@ class FlutterDriver { ...@@ -419,26 +453,31 @@ class FlutterDriver {
/// Checks the status of the Flutter Driver extension. /// Checks the status of the Flutter Driver extension.
Future<Health> checkHealth({Duration timeout}) async { Future<Health> checkHealth({Duration timeout}) async {
timeout ??= _shortTimeout(timeoutMultiplier);
return Health.fromJson(await _sendCommand(GetHealth(timeout: timeout))); return Health.fromJson(await _sendCommand(GetHealth(timeout: timeout)));
} }
/// Returns a dump of the render tree. /// Returns a dump of the render tree.
Future<RenderTree> getRenderTree({Duration timeout}) async { Future<RenderTree> getRenderTree({Duration timeout}) async {
timeout ??= _shortTimeout(timeoutMultiplier);
return RenderTree.fromJson(await _sendCommand(GetRenderTree(timeout: timeout))); return RenderTree.fromJson(await _sendCommand(GetRenderTree(timeout: timeout)));
} }
/// Taps at the center of the widget located by [finder]. /// Taps at the center of the widget located by [finder].
Future<void> tap(SerializableFinder finder, {Duration timeout}) async { Future<void> tap(SerializableFinder finder, {Duration timeout}) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(Tap(finder, timeout: timeout)); await _sendCommand(Tap(finder, timeout: timeout));
} }
/// Waits until [finder] locates the target. /// Waits until [finder] locates the target.
Future<void> waitFor(SerializableFinder finder, {Duration timeout}) async { Future<void> waitFor(SerializableFinder finder, {Duration timeout}) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(WaitFor(finder, timeout: timeout)); await _sendCommand(WaitFor(finder, timeout: timeout));
} }
/// Waits until [finder] can no longer locate the target. /// Waits until [finder] can no longer locate the target.
Future<void> waitForAbsent(SerializableFinder finder, {Duration timeout}) async { Future<void> waitForAbsent(SerializableFinder finder, {Duration timeout}) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(WaitForAbsent(finder, timeout: timeout)); await _sendCommand(WaitForAbsent(finder, timeout: timeout));
} }
...@@ -447,6 +486,7 @@ class FlutterDriver { ...@@ -447,6 +486,7 @@ class FlutterDriver {
/// Use this method when you need to wait for the moment when the application /// Use this method when you need to wait for the moment when the application
/// becomes "stable", for example, prior to taking a [screenshot]. /// becomes "stable", for example, prior to taking a [screenshot].
Future<void> waitUntilNoTransientCallbacks({Duration timeout}) async { Future<void> waitUntilNoTransientCallbacks({Duration timeout}) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(WaitUntilNoTransientCallbacks(timeout: timeout)); await _sendCommand(WaitUntilNoTransientCallbacks(timeout: timeout));
} }
...@@ -464,6 +504,7 @@ class FlutterDriver { ...@@ -464,6 +504,7 @@ class FlutterDriver {
/// The move events are generated at a given [frequency] in Hz (or events per /// The move events are generated at a given [frequency] in Hz (or events per
/// second). It defaults to 60Hz. /// second). It defaults to 60Hz.
Future<void> scroll(SerializableFinder finder, double dx, double dy, Duration duration, { int frequency = 60, Duration timeout }) async { Future<void> scroll(SerializableFinder finder, double dx, double dy, Duration duration, { int frequency = 60, Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(Scroll(finder, dx, dy, duration, frequency, timeout: timeout)); await _sendCommand(Scroll(finder, dx, dy, duration, frequency, timeout: timeout));
} }
...@@ -475,6 +516,7 @@ class FlutterDriver { ...@@ -475,6 +516,7 @@ class FlutterDriver {
/// then this method may fail because [finder] doesn't actually exist. /// then this method may fail because [finder] doesn't actually exist.
/// The [scrollUntilVisible] method can be used in this case. /// The [scrollUntilVisible] method can be used in this case.
Future<void> scrollIntoView(SerializableFinder finder, { double alignment = 0.0, Duration timeout }) async { Future<void> scrollIntoView(SerializableFinder finder, { double alignment = 0.0, Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(ScrollIntoView(finder, alignment: alignment, timeout: timeout)); await _sendCommand(ScrollIntoView(finder, alignment: alignment, timeout: timeout));
} }
...@@ -531,6 +573,7 @@ class FlutterDriver { ...@@ -531,6 +573,7 @@ class FlutterDriver {
/// Returns the text in the `Text` widget located by [finder]. /// Returns the text in the `Text` widget located by [finder].
Future<String> getText(SerializableFinder finder, { Duration timeout }) async { Future<String> getText(SerializableFinder finder, { Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
return GetTextResult.fromJson(await _sendCommand(GetText(finder, timeout: timeout))).text; return GetTextResult.fromJson(await _sendCommand(GetText(finder, timeout: timeout))).text;
} }
...@@ -567,6 +610,7 @@ class FlutterDriver { ...@@ -567,6 +610,7 @@ class FlutterDriver {
/// }); /// });
/// ``` /// ```
Future<void> enterText(String text, { Duration timeout }) async { Future<void> enterText(String text, { Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(EnterText(text, timeout: timeout)); await _sendCommand(EnterText(text, timeout: timeout));
} }
...@@ -585,6 +629,7 @@ class FlutterDriver { ...@@ -585,6 +629,7 @@ class FlutterDriver {
/// channel will be mocked out. /// channel will be mocked out.
Future<void> setTextEntryEmulation({ @required bool enabled, Duration timeout }) async { Future<void> setTextEntryEmulation({ @required bool enabled, Duration timeout }) async {
assert(enabled != null); assert(enabled != null);
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(SetTextEntryEmulation(enabled, timeout: timeout)); await _sendCommand(SetTextEntryEmulation(enabled, timeout: timeout));
} }
...@@ -595,6 +640,7 @@ class FlutterDriver { ...@@ -595,6 +640,7 @@ class FlutterDriver {
/// callback in [enableFlutterDriverExtension] that can successfully handle /// callback in [enableFlutterDriverExtension] that can successfully handle
/// these requests. /// these requests.
Future<String> requestData(String message, { Duration timeout }) async { Future<String> requestData(String message, { Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
return RequestDataResult.fromJson(await _sendCommand(RequestData(message, timeout: timeout))).message; return RequestDataResult.fromJson(await _sendCommand(RequestData(message, timeout: timeout))).message;
} }
...@@ -602,7 +648,8 @@ class FlutterDriver { ...@@ -602,7 +648,8 @@ class FlutterDriver {
/// ///
/// Returns true when the call actually changed the state from on to off or /// Returns true when the call actually changed the state from on to off or
/// vice versa. /// vice versa.
Future<bool> setSemantics(bool enabled, { Duration timeout = _kShortTimeout }) async { Future<bool> setSemantics(bool enabled, { Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
final SetSemanticsResult result = SetSemanticsResult.fromJson(await _sendCommand(SetSemantics(enabled, timeout: timeout))); final SetSemanticsResult result = SetSemanticsResult.fromJson(await _sendCommand(SetSemantics(enabled, timeout: timeout)));
return result.changedState; return result.changedState;
} }
...@@ -615,7 +662,8 @@ class FlutterDriver { ...@@ -615,7 +662,8 @@ class FlutterDriver {
/// ///
/// Semantics must be enabled to use this method, either using a platform /// Semantics must be enabled to use this method, either using a platform
/// specific shell command or [setSemantics]. /// specific shell command or [setSemantics].
Future<int> getSemanticsId(SerializableFinder finder, { Duration timeout = _kShortTimeout}) async { Future<int> getSemanticsId(SerializableFinder finder, { Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
final Map<String, dynamic> jsonResponse = await _sendCommand(GetSemanticsId(finder, timeout: timeout)); final Map<String, dynamic> jsonResponse = await _sendCommand(GetSemanticsId(finder, timeout: timeout));
final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse); final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse);
return result.id; return result.id;
...@@ -623,7 +671,7 @@ class FlutterDriver { ...@@ -623,7 +671,7 @@ class FlutterDriver {
/// Take a screenshot. The image will be returned as a PNG. /// Take a screenshot. The image will be returned as a PNG.
Future<List<int>> screenshot({ Duration timeout }) async { Future<List<int>> screenshot({ Duration timeout }) async {
timeout ??= _kLongTimeout; timeout ??= _longTimeout(timeoutMultiplier);
// HACK: this artificial delay here is to deal with a race between the // HACK: this artificial delay here is to deal with a race between the
// driver script and the GPU thread. The issue is that driver API // driver script and the GPU thread. The issue is that driver API
...@@ -683,7 +731,8 @@ class FlutterDriver { ...@@ -683,7 +731,8 @@ class FlutterDriver {
/// ] /// ]
/// ///
/// [getFlagList]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#getflaglist /// [getFlagList]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#getflaglist
Future<List<Map<String, dynamic>>> getVmFlags({ Duration timeout = _kShortTimeout }) async { Future<List<Map<String, dynamic>>> getVmFlags({ Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
final Map<String, dynamic> result = await _peer.sendRequest('getFlagList').timeout(timeout); final Map<String, dynamic> result = await _peer.sendRequest('getFlagList').timeout(timeout);
return result['flags']; return result['flags'];
} }
...@@ -691,8 +740,9 @@ class FlutterDriver { ...@@ -691,8 +740,9 @@ class FlutterDriver {
/// Starts recording performance traces. /// Starts recording performance traces.
Future<void> startTracing({ Future<void> startTracing({
List<TimelineStream> streams = _defaultStreams, List<TimelineStream> streams = _defaultStreams,
Duration timeout = _kShortTimeout, Duration timeout,
}) async { }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
assert(streams != null && streams.isNotEmpty); assert(streams != null && streams.isNotEmpty);
try { try {
await _peer.sendRequest(_setVMTimelineFlagsMethodName, <String, String>{ await _peer.sendRequest(_setVMTimelineFlagsMethodName, <String, String>{
...@@ -708,7 +758,8 @@ class FlutterDriver { ...@@ -708,7 +758,8 @@ class FlutterDriver {
} }
/// Stops recording performance traces and downloads the timeline. /// Stops recording performance traces and downloads the timeline.
Future<Timeline> stopTracingAndDownloadTimeline({ Duration timeout = _kShortTimeout }) async { Future<Timeline> stopTracingAndDownloadTimeline({ Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
try { try {
await _peer await _peer
.sendRequest(_setVMTimelineFlagsMethodName, <String, String>{'recordedStreams': '[]'}) .sendRequest(_setVMTimelineFlagsMethodName, <String, String>{'recordedStreams': '[]'})
...@@ -751,7 +802,8 @@ class FlutterDriver { ...@@ -751,7 +802,8 @@ class FlutterDriver {
} }
/// Clears all timeline events recorded up until now. /// Clears all timeline events recorded up until now.
Future<void> clearTimeline({ Duration timeout = _kShortTimeout }) async { Future<void> clearTimeline({ Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
try { try {
await _peer await _peer
.sendRequest(_clearVMTimelineMethodName, <String, String>{}) .sendRequest(_clearVMTimelineMethodName, <String, String>{})
...@@ -782,6 +834,7 @@ class FlutterDriver { ...@@ -782,6 +834,7 @@ class FlutterDriver {
/// ensure that no action is performed while the app is undergoing a /// ensure that no action is performed while the app is undergoing a
/// transition to avoid flakiness. /// transition to avoid flakiness.
Future<T> runUnsynchronized<T>(Future<T> action(), { Duration timeout }) async { Future<T> runUnsynchronized<T>(Future<T> action(), { Duration timeout }) async {
timeout ??= _shortTimeout(timeoutMultiplier);
await _sendCommand(SetFrameSync(false, timeout: timeout)); await _sendCommand(SetFrameSync(false, timeout: timeout));
T result; T result;
try { try {
...@@ -841,6 +894,11 @@ typedef VMServiceConnectFunction = Future<VMServiceClientConnection> Function(St ...@@ -841,6 +894,11 @@ typedef VMServiceConnectFunction = Future<VMServiceClientConnection> Function(St
/// ///
/// Overwrite this function if you require a custom method for connecting to /// Overwrite this function if you require a custom method for connecting to
/// the VM service. /// the VM service.
///
/// See also:
///
/// * [connectionTimeoutMultiplier], which controls the timeouts while
/// establishing a connection using the default connection function.
VMServiceConnectFunction vmServiceConnectFunction = _waitAndConnect; VMServiceConnectFunction vmServiceConnectFunction = _waitAndConnect;
/// Restores [vmServiceConnectFunction] to its default value. /// Restores [vmServiceConnectFunction] to its default value.
...@@ -863,8 +921,8 @@ Future<VMServiceClientConnection> _waitAndConnect(String url) async { ...@@ -863,8 +921,8 @@ Future<VMServiceClientConnection> _waitAndConnect(String url) async {
WebSocket ws1; WebSocket ws1;
WebSocket ws2; WebSocket ws2;
try { try {
ws1 = await WebSocket.connect(uri.toString()).timeout(_kShortTimeout); ws1 = await WebSocket.connect(uri.toString()).timeout(_shortTimeout(connectionTimeoutMultiplier));
ws2 = await WebSocket.connect(uri.toString()).timeout(_kShortTimeout); ws2 = await WebSocket.connect(uri.toString()).timeout(_shortTimeout(connectionTimeoutMultiplier));
return VMServiceClientConnection( return VMServiceClientConnection(
VMServiceClient(IOWebSocketChannel(ws1).cast()), VMServiceClient(IOWebSocketChannel(ws1).cast()),
rpc.Peer(IOWebSocketChannel(ws2).cast())..listen() rpc.Peer(IOWebSocketChannel(ws2).cast())..listen()
...@@ -873,9 +931,9 @@ Future<VMServiceClientConnection> _waitAndConnect(String url) async { ...@@ -873,9 +931,9 @@ Future<VMServiceClientConnection> _waitAndConnect(String url) async {
await ws1?.close(); await ws1?.close();
await ws2?.close(); await ws2?.close();
if (timer.elapsed < _kLongTimeout * 2) { if (timer.elapsed < _longTimeout(connectionTimeoutMultiplier) * 2) {
_log.info('Waiting for application to start'); _log.info('Waiting for application to start');
await Future<void>.delayed(_kPauseBetweenReconnectAttempts); await Future<void>.delayed(_pauseBetweenReconnectAttempts(connectionTimeoutMultiplier));
return attemptConnection(); return attemptConnection();
} else { } else {
_log.critical( _log.critical(
......
...@@ -17,6 +17,7 @@ import 'common.dart'; ...@@ -17,6 +17,7 @@ import 'common.dart';
/// Magical timeout value that's different from the default. /// Magical timeout value that's different from the default.
const Duration _kTestTimeout = Duration(milliseconds: 1234); const Duration _kTestTimeout = Duration(milliseconds: 1234);
const String _kSerializedTestTimeout = '1234'; const String _kSerializedTestTimeout = '1234';
const Duration _kDefaultCommandTimeout = Duration(seconds: 5);
void main() { void main() {
group('FlutterDriver.connect', () { group('FlutterDriver.connect', () {
...@@ -386,6 +387,43 @@ void main() { ...@@ -386,6 +387,43 @@ void main() {
}); });
}); });
}); });
group('FlutterDriver with custom timeout', () {
const double kTestMultiplier = 3.0;
MockVMServiceClient mockClient;
MockPeer mockPeer;
MockIsolate mockIsolate;
FlutterDriver driver;
setUp(() {
mockClient = MockVMServiceClient();
mockPeer = MockPeer();
mockIsolate = MockIsolate();
driver = FlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate, timeoutMultiplier: kTestMultiplier);
});
test('multiplies the timeout', () async {
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
expect(i.positionalArguments[1], <String, String>{
'command': 'get_health',
'timeout': '${(_kDefaultCommandTimeout * kTestMultiplier).inMilliseconds}',
});
return makeMockResponse(<String, dynamic>{'status': 'ok'});
});
await driver.checkHealth();
});
test('does not multiply explicit timeouts', () async {
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
expect(i.positionalArguments[1], <String, String>{
'command': 'get_health',
'timeout': _kSerializedTestTimeout,
});
return makeMockResponse(<String, dynamic>{'status': 'ok'});
});
await driver.checkHealth(timeout: _kTestTimeout);
});
});
} }
Future<Map<String, dynamic>> makeMockResponse( Future<Map<String, dynamic>> makeMockResponse(
......
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