mirror of
https://github.com/encounter/flutter.git
synced 2026-03-30 11:10:35 -07:00
Added Driver API that waits until frame sync. (#36334)
This commit is contained in:
@@ -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,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user