Unverified Commit dd51afd1 authored by adazh's avatar adazh Committed by GitHub

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

parent db6c362b
......@@ -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,
},
);
});
});
}
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