| // 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. | |
| /// @docImport 'package:flutter/widgets.dart'; | |
| library; | |
| 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. | |
| const WaitForCondition(this.condition, {super.timeout}); | |
| /// Deserializes this command from the value generated by [serialize]. | |
| /// | |
| /// The [path] is the current path to the [json] object from the parent object. | |
| WaitForCondition.deserialize(super.json, {String? path}) | |
| : condition = _deserialize(json, path: path), | |
| super.deserialize(); | |
| /// 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; | |
| } | |
| /// 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. | |
| factory NoTransientCallbacks.deserialize(Map<String, String> json) { | |
| 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. | |
| factory NoPendingFrame.deserialize(Map<String, String> json) { | |
| 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. | |
| factory FirstFrameRasterized.deserialize(Map<String, String> json) { | |
| 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. | |
| factory NoPendingPlatformMessages.deserialize(Map<String, String> json) { | |
| 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. | |
| const CombinedCondition(this.conditions); | |
| /// Factory constructor to parse a [CombinedCondition] instance from the | |
| /// given JSON map. | |
| factory CombinedCondition.deserialize(Map<String, String> jsonMap, {String? path}) { | |
| if (jsonMap['conditionName'] != 'CombinedCondition') { | |
| throw SerializationException( | |
| 'Error occurred during deserializing the CombinedCondition JSON string: $jsonMap', | |
| ); | |
| } | |
| if (jsonMap['conditions'] == null) { | |
| return const CombinedCondition(<SerializableWaitCondition>[]); | |
| } | |
| final conditions = <SerializableWaitCondition>[]; | |
| var i = 0; | |
| 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>(), | |
| path: path == null ? 'conditions[$i]' : '$path.conditions[$i]', | |
| ), | |
| ); | |
| i++; | |
| } | |
| 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, | |
| ) { | |
| return condition.serialize(); | |
| }).toList(); | |
| jsonMap['conditions'] = json.encode(jsonConditions); | |
| return jsonMap; | |
| } | |
| } | |
| /// Parses a [SerializableWaitCondition] or its subclass from the given [json] map. | |
| SerializableWaitCondition _deserialize(Map<String, String> json, {String? path}) { | |
| final String? conditionName = json['conditionName']; | |
| if (conditionName == null) { | |
| throw ArgumentError.notNull(path == null ? 'conditionName' : '$path.conditionName'); | |
| } | |
| return switch (conditionName) { | |
| 'NoTransientCallbacksCondition' => NoTransientCallbacks.deserialize(json), | |
| 'NoPendingFrameCondition' => NoPendingFrame.deserialize(json), | |
| 'FirstFrameRasterizedCondition' => FirstFrameRasterized.deserialize(json), | |
| 'NoPendingPlatformMessagesCondition' => NoPendingPlatformMessages.deserialize(json), | |
| 'CombinedCondition' => CombinedCondition.deserialize(json, path: path), | |
| final String condition => throw SerializationException( | |
| 'Unsupported wait condition $condition in the JSON string $json', | |
| ), | |
| }; | |
| } |