blob: 7f8db7b0e23fac3775941ffa452e005de91b0a74 [file] [log] [blame]
// 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 'dart:convert';
import 'message.dart';
/// A Flutter Driver command that waits until a given [condition] is satisfied.
class WaitForCondition extends Command {
/// Creates a command that waits for the given [condition] is met.
///
/// The [condition] argument must not be null.
const WaitForCondition(this.condition, {Duration? timeout})
: assert(condition != null),
super(timeout: timeout);
/// Deserializes this command from the value generated by [serialize].
///
/// The [json] argument cannot be null.
WaitForCondition.deserialize(Map<String, String> json)
: assert(json != null),
condition = _deserialize(json),
super.deserialize(json);
/// The condition that this command shall wait for.
final SerializableWaitCondition condition;
@override
Map<String, String> serialize() => super.serialize()..addAll(condition.serialize());
@override
String get kind => 'waitForCondition';
@override
bool get requiresRootWidgetAttached => condition.requiresRootWidgetAttached;
}
/// A Flutter Driver command that waits until there are no more transient callbacks in the queue.
///
/// This command has been deprecated in favor of [WaitForCondition]. Construct
/// a command that waits until no transient callbacks as follows:
///
/// ```dart
/// WaitForCondition noTransientCallbacks = WaitForCondition(NoTransientCallbacks());
/// ```
@Deprecated(
'This command has been deprecated in favor of WaitForCondition. '
'Use WaitForCondition command with NoTransientCallbacks. '
'This feature was deprecated after v1.9.3.'
)
class WaitUntilNoTransientCallbacks extends Command {
/// Creates a command that waits for there to be no transient callbacks.
const WaitUntilNoTransientCallbacks({ Duration? timeout }) : super(timeout: timeout);
/// Deserializes this command from the value generated by [serialize].
WaitUntilNoTransientCallbacks.deserialize(Map<String, String> json)
: super.deserialize(json);
@override
String get kind => 'waitUntilNoTransientCallbacks';
}
/// A Flutter Driver command that waits until the frame is synced.
///
/// This command has been deprecated in favor of [WaitForCondition]. Construct
/// a command that waits until no pending frame as follows:
///
/// ```dart
/// WaitForCondition noPendingFrame = WaitForCondition(NoPendingFrame());
/// ```
@Deprecated(
'This command has been deprecated in favor of WaitForCondition. '
'Use WaitForCondition command with NoPendingFrame. '
'This feature was deprecated after v1.9.3.'
)
class WaitUntilNoPendingFrame extends Command {
/// Creates a command that waits until there's no pending frame scheduled.
const WaitUntilNoPendingFrame({ Duration? timeout }) : super(timeout: timeout);
/// Deserializes this command from the value generated by [serialize].
WaitUntilNoPendingFrame.deserialize(Map<String, String> json)
: super.deserialize(json);
@override
String get kind => 'waitUntilNoPendingFrame';
}
/// A Flutter Driver command that waits until the Flutter engine rasterizes the
/// first frame.
///
/// {@template flutter.frame_rasterized_vs_presented}
/// Usually, the time that a frame is rasterized is very close to the time that
/// it gets presented on the display. Specifically, rasterization is the last
/// expensive phase of a frame that's still in Flutter's control.
/// {@endtemplate}
///
/// This command has been deprecated in favor of [WaitForCondition]. Construct
/// a command that waits until no pending frame as follows:
///
/// ```dart
/// WaitForCondition firstFrameRasterized = WaitForCondition(FirstFrameRasterized());
/// ```
@Deprecated(
'This command has been deprecated in favor of WaitForCondition. '
'Use WaitForCondition command with FirstFrameRasterized. '
'This feature was deprecated after v1.9.3.'
)
class WaitUntilFirstFrameRasterized extends Command {
/// Creates this command.
const WaitUntilFirstFrameRasterized({ Duration? timeout }) : super(timeout: timeout);
/// Deserializes this command from the value generated by [serialize].
WaitUntilFirstFrameRasterized.deserialize(Map<String, String> json)
: super.deserialize(json);
@override
String get kind => 'waitUntilFirstFrameRasterized';
}
/// Thrown to indicate a serialization error.
class SerializationException implements Exception {
/// Creates a [SerializationException] with an optional error message.
const SerializationException([this.message]);
/// The error message, possibly null.
final String? message;
@override
String toString() => 'SerializationException($message)';
}
/// Base class for Flutter Driver wait conditions, objects that describe conditions
/// the driver can wait for.
///
/// This class is sent from the driver script running on the host to the driver
/// extension on device to perform waiting on a given condition. In the extension,
/// it will be converted to a `WaitCondition` that actually defines the wait logic.
///
/// If you subclass this, you also need to implement a `WaitCondition` in the extension.
abstract class SerializableWaitCondition {
/// A const constructor to allow subclasses to be const.
const SerializableWaitCondition();
/// Identifies the name of the wait condition.
String get conditionName;
/// Serializes the object to JSON.
Map<String, String> serialize() {
return <String, String>{
'conditionName': conditionName
};
}
/// Whether this command requires the widget tree to be initialized before
/// the command may be run.
///
/// This defaults to true to force the application under test to call [runApp]
/// before attempting to remotely drive the application. Subclasses may
/// override this to return false if they allow invocation before the
/// application has started.
///
/// See also:
///
/// * [WidgetsBinding.isRootWidgetAttached], which indicates whether the
/// widget tree has been initialized.
bool get requiresRootWidgetAttached => true;
}
/// A condition that waits until no transient callbacks are scheduled.
class NoTransientCallbacks extends SerializableWaitCondition {
/// Creates a [NoTransientCallbacks] condition.
const NoTransientCallbacks();
/// Factory constructor to parse a [NoTransientCallbacks] instance from the
/// given JSON map.
///
/// The [json] argument must not be null.
factory NoTransientCallbacks.deserialize(Map<String, String> json) {
assert(json != null);
if (json['conditionName'] != 'NoTransientCallbacksCondition')
throw SerializationException('Error occurred during deserializing the NoTransientCallbacksCondition JSON string: $json');
return const NoTransientCallbacks();
}
@override
String get conditionName => 'NoTransientCallbacksCondition';
}
/// A condition that waits until no pending frame is scheduled.
class NoPendingFrame extends SerializableWaitCondition {
/// Creates a [NoPendingFrame] condition.
const NoPendingFrame();
/// Factory constructor to parse a [NoPendingFrame] instance from the given
/// JSON map.
///
/// The [json] argument must not be null.
factory NoPendingFrame.deserialize(Map<String, String> json) {
assert(json != null);
if (json['conditionName'] != 'NoPendingFrameCondition')
throw SerializationException('Error occurred during deserializing the NoPendingFrameCondition JSON string: $json');
return const NoPendingFrame();
}
@override
String get conditionName => 'NoPendingFrameCondition';
}
/// A condition that waits until the Flutter engine has rasterized the first frame.
class FirstFrameRasterized extends SerializableWaitCondition {
/// Creates a [FirstFrameRasterized] condition.
const FirstFrameRasterized();
/// Factory constructor to parse a [FirstFrameRasterized] instance from the
/// given JSON map.
///
/// The [json] argument must not be null.
factory FirstFrameRasterized.deserialize(Map<String, String> json) {
assert(json != null);
if (json['conditionName'] != 'FirstFrameRasterizedCondition')
throw SerializationException('Error occurred during deserializing the FirstFrameRasterizedCondition JSON string: $json');
return const FirstFrameRasterized();
}
@override
String get conditionName => 'FirstFrameRasterizedCondition';
@override
bool get requiresRootWidgetAttached => false;
}
/// A condition that waits until there are no pending platform messages.
class NoPendingPlatformMessages extends SerializableWaitCondition {
/// Creates a [NoPendingPlatformMessages] condition.
const NoPendingPlatformMessages();
/// Factory constructor to parse a [NoPendingPlatformMessages] instance from the
/// given JSON map.
///
/// The [json] argument must not be null.
factory NoPendingPlatformMessages.deserialize(Map<String, String> json) {
assert(json != null);
if (json['conditionName'] != 'NoPendingPlatformMessagesCondition')
throw SerializationException('Error occurred during deserializing the NoPendingPlatformMessagesCondition JSON string: $json');
return const NoPendingPlatformMessages();
}
@override
String get conditionName => 'NoPendingPlatformMessagesCondition';
}
/// A combined condition that waits until all the given [conditions] are met.
class CombinedCondition extends SerializableWaitCondition {
/// Creates a [CombinedCondition] condition.
///
/// The [conditions] argument must not be null.
const CombinedCondition(this.conditions)
: assert(conditions != null);
/// Factory constructor to parse a [CombinedCondition] instance from the
/// given JSON map.
///
/// The [jsonMap] argument must not be null.
factory CombinedCondition.deserialize(Map<String, String> jsonMap) {
assert(jsonMap != null);
if (jsonMap['conditionName'] != 'CombinedCondition')
throw SerializationException('Error occurred during deserializing the CombinedCondition JSON string: $jsonMap');
if (jsonMap['conditions'] == null) {
return const CombinedCondition(<SerializableWaitCondition>[]);
}
final List<SerializableWaitCondition> conditions = <SerializableWaitCondition>[];
for (final Map<String, dynamic> condition in (json.decode(jsonMap['conditions']!) as List<dynamic>).cast<Map<String, dynamic>>()) {
conditions.add(_deserialize(condition.cast<String, String>()));
}
return CombinedCondition(conditions);
}
/// A list of conditions it waits for.
final List<SerializableWaitCondition> conditions;
@override
String get conditionName => 'CombinedCondition';
@override
Map<String, String> serialize() {
final Map<String, String> jsonMap = super.serialize();
final List<Map<String, String>> jsonConditions = conditions.map(
(SerializableWaitCondition condition) {
assert(condition != null);
return condition.serialize();
}).toList();
jsonMap['conditions'] = json.encode(jsonConditions);
return jsonMap;
}
}
/// Parses a [SerializableWaitCondition] or its subclass from the given [json] map.
///
/// The [json] argument must not be null.
SerializableWaitCondition _deserialize(Map<String, String> json) {
assert(json != null);
final String conditionName = json['conditionName']!;
switch (conditionName) {
case 'NoTransientCallbacksCondition':
return NoTransientCallbacks.deserialize(json);
case 'NoPendingFrameCondition':
return NoPendingFrame.deserialize(json);
case 'FirstFrameRasterizedCondition':
return FirstFrameRasterized.deserialize(json);
case 'NoPendingPlatformMessagesCondition':
return NoPendingPlatformMessages.deserialize(json);
case 'CombinedCondition':
return CombinedCondition.deserialize(json);
}
throw SerializationException(
'Unsupported wait condition $conditionName in the JSON string $json');
}