1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// 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;
}
/// 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');
}