Added Driver API that waits until frame sync. (#36334)

This commit is contained in:
adazh
2019-07-18 15:03:19 -07:00
committed by GitHub
parent db6c362bef
commit dd51afd161
3 changed files with 117 additions and 0 deletions
@@ -119,6 +119,19 @@ class WaitUntilNoTransientCallbacks extends Command {
String get kind => 'waitUntilNoTransientCallbacks';
}
/// A Flutter Driver command that waits until the frame is synced.
class WaitUntilFrameSync extends Command {
/// Creates a command that waits until there's no pending frame scheduled.
const WaitUntilFrameSync({ Duration timeout }) : super(timeout: timeout);
/// Deserializes this command from the value generated by [serialize].
WaitUntilFrameSync.deserialize(Map<String, String> json)
: super.deserialize(json);
@override
String get kind => 'waitUntilFrameSync';
}
/// Base class for Flutter Driver finders, objects that describe how the driver
/// should search for elements.
abstract class SerializableFinder {
@@ -113,6 +113,7 @@ class FlutterDriverExtension {
'waitFor': _waitFor,
'waitForAbsent': _waitForAbsent,
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
'waitUntilFrameSync': _waitUntilFrameSync,
'get_semantics_id': _getSemanticsId,
'get_offset': _getOffset,
'get_diagnostics_tree': _getDiagnosticsTree,
@@ -133,6 +134,7 @@ class FlutterDriverExtension {
'waitFor': (Map<String, String> params) => WaitFor.deserialize(params),
'waitForAbsent': (Map<String, String> params) => WaitForAbsent.deserialize(params),
'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params),
'waitUntilFrameSync': (Map<String, String> params) => WaitUntilFrameSync.deserialize(params),
'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params),
'get_offset': (Map<String, String> params) => GetOffset.deserialize(params),
'get_diagnostics_tree': (Map<String, String> params) => GetDiagnosticsTree.deserialize(params),
@@ -369,6 +371,31 @@ class FlutterDriverExtension {
return null;
}
/// Returns a future that waits until frame is synced.
///
/// Specifically, it checks:
/// * Whether the count of transient callbacks is zero.
/// * Whether there's no pending request for scheduling a new frame.
///
/// We consider the frame is synced when both conditions are met.
///
/// This method relies on a Flutter Driver mechanism called "frame sync",
/// which waits for transient animations to finish. Persistent animations will
/// cause this to wait forever.
///
/// If a test needs to interact with the app while animations are running, it
/// should avoid this method and instead disable the frame sync using
/// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
/// details on how to do this. Note, disabling frame sync will require the
/// test author to use some other method to avoid flakiness.
Future<Result> _waitUntilFrameSync(Command command) async {
await _waitUntilFrame(() {
return SchedulerBinding.instance.transientCallbackCount == 0
&& !SchedulerBinding.instance.hasScheduledFrame;
});
return null;
}
Future<GetSemanticsIdResult> _getSemanticsId(Command command) async {
final GetSemanticsId semanticsCommand = command;
final Finder target = await _waitForElement(_createFinder(semanticsCommand.finder));
@@ -333,4 +333,81 @@ void main() {
children = result['children'];
expect(children.single['children'], isEmpty);
});
group('waitUntilFrameSync', () {
FlutterDriverExtension extension;
Map<String, dynamic> result;
setUp(() {
extension = FlutterDriverExtension((String arg) async => '', true);
result = null;
});
testWidgets('returns immediately when frame is synced', (
WidgetTester tester) async {
extension.call(const WaitUntilFrameSync().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
await tester.idle();
expect(
result,
<String, dynamic>{
'isError': false,
'response': null,
},
);
});
testWidgets(
'waits until no transient callbacks', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrameCallback((_) {
// Intentionally blank. We only care about existence of a callback.
});
extension.call(const WaitUntilFrameSync().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
// Nothing should happen until the next frame.
await tester.idle();
expect(result, isNull);
// NOW we should receive the result.
await tester.pump();
expect(
result,
<String, dynamic>{
'isError': false,
'response': null,
},
);
});
testWidgets(
'waits until no pending scheduled frame', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrame();
extension.call(const WaitUntilFrameSync().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
// Nothing should happen until the next frame.
await tester.idle();
expect(result, isNull);
// NOW we should receive the result.
await tester.pump();
expect(
result,
<String, dynamic>{
'isError': false,
'response': null,
},
);
});
});
}