Unverified Commit 74f6fa45 authored by Marcin Jeleński's avatar Marcin Jeleński Committed by GitHub

Flutter Driver - Create widget finders from serialized finders extensions (#67456)

parent cb0bdb49
// 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 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'find.dart';
/// A factory which creates [Finder]s from [SerializableFinder]s.
mixin CreateFinderFactory {
/// Creates the flutter widget finder from [SerializableFinder].
Finder createFinder(SerializableFinder finder) {
switch (finder.finderType) {
case 'ByText':
return _createByTextFinder(finder as ByText);
case 'ByTooltipMessage':
return _createByTooltipMessageFinder(finder as ByTooltipMessage);
case 'BySemanticsLabel':
return _createBySemanticsLabelFinder(finder as BySemanticsLabel);
case 'ByValueKey':
return _createByValueKeyFinder(finder as ByValueKey);
case 'ByType':
return _createByTypeFinder(finder as ByType);
case 'PageBack':
return _createPageBackFinder();
case 'Ancestor':
return _createAncestorFinder(finder as Ancestor);
case 'Descendant':
return _createDescendantFinder(finder as Descendant);
default:
return null;
}
}
Finder _createByTextFinder(ByText arguments) {
return find.text(arguments.text);
}
Finder _createByTooltipMessageFinder(ByTooltipMessage arguments) {
return find.byElementPredicate((Element element) {
final Widget widget = element.widget;
if (widget is Tooltip) {
return widget.message == arguments.text;
}
return false;
}, description: 'widget with text tooltip "${arguments.text}"');
}
Finder _createBySemanticsLabelFinder(BySemanticsLabel arguments) {
return find.byElementPredicate((Element element) {
if (element is! RenderObjectElement) {
return false;
}
final String semanticsLabel = element.renderObject?.debugSemantics?.label;
if (semanticsLabel == null) {
return false;
}
final Pattern label = arguments.label;
return label is RegExp
? label.hasMatch(semanticsLabel)
: label == semanticsLabel;
}, description: 'widget with semantic label "${arguments.label}"');
}
Finder _createByValueKeyFinder(ByValueKey arguments) {
switch (arguments.keyValueType) {
case 'int':
return find.byKey(ValueKey<int>(arguments.keyValue as int));
case 'String':
return find.byKey(ValueKey<String>(arguments.keyValue as String));
default:
throw 'Unsupported ByValueKey type: ${arguments.keyValueType}';
}
}
Finder _createByTypeFinder(ByType arguments) {
return find.byElementPredicate((Element element) {
return element.widget.runtimeType.toString() == arguments.type;
}, description: 'widget with runtimeType "${arguments.type}"');
}
Finder _createPageBackFinder() {
return find.byElementPredicate((Element element) {
final Widget widget = element.widget;
if (widget is Tooltip) {
return widget.message == 'Back';
}
if (widget is CupertinoNavigationBarBackButton) {
return true;
}
return false;
}, description: 'Material or Cupertino back button');
}
Finder _createAncestorFinder(Ancestor arguments) {
final Finder finder = find.ancestor(
of: createFinder(arguments.of),
matching: createFinder(arguments.matching),
matchRoot: arguments.matchRoot,
);
return arguments.firstMatchOnly ? finder.first : finder;
}
Finder _createDescendantFinder(Descendant arguments) {
final Finder finder = find.descendant(
of: createFinder(arguments.of),
matching: createFinder(arguments.matching),
matchRoot: arguments.matchRoot,
);
return arguments.firstMatchOnly ? finder.first : finder;
}
}
...@@ -24,7 +24,7 @@ mixin DeserializeFinderFactory { ...@@ -24,7 +24,7 @@ mixin DeserializeFinderFactory {
case 'Descendant': return Descendant.deserialize(json, this); case 'Descendant': return Descendant.deserialize(json, this);
case 'Ancestor': return Ancestor.deserialize(json, this); case 'Ancestor': return Ancestor.deserialize(json, this);
} }
throw DriverError('Unsupported search specification type $finderType'); return null;
} }
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_driver/src/common/create_finder_factory.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -123,7 +124,7 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, ...@@ -123,7 +124,7 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// return Some(json['title']); /// return Some(json['title']);
/// } /// }
/// ///
/// Finder createFinder(SerializableFinder finder) { /// Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
/// Some someFinder = finder as Some; /// Some someFinder = finder as Some;
/// ///
/// return find.byElementPredicate((Element element) { /// return find.byElementPredicate((Element element) {
...@@ -156,11 +157,13 @@ abstract class FinderExtension { ...@@ -156,11 +157,13 @@ abstract class FinderExtension {
String get finderType; String get finderType;
/// Deserializes the finder from JSON generated by [SerializableFinder.serialize]. /// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
/// [finderFactory] could be used to deserialize nested finders.
SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory); SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory);
/// Signature for functions that run the given finder and return the [Element] /// Signature for functions that run the given finder and return the [Element]
/// found, if any, or null otherwise. /// found, if any, or null otherwise.
Finder createFinder(SerializableFinder finder); /// [finderFactory] could be used to create nested finders.
Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory);
} }
/// The class that manages communication between a Flutter Driver test and the /// The class that manages communication between a Flutter Driver test and the
...@@ -169,7 +172,7 @@ abstract class FinderExtension { ...@@ -169,7 +172,7 @@ abstract class FinderExtension {
/// This is not normally used directly. It is instantiated automatically when /// This is not normally used directly. It is instantiated automatically when
/// calling [enableFlutterDriverExtension]. /// calling [enableFlutterDriverExtension].
@visibleForTesting @visibleForTesting
class FlutterDriverExtension with DeserializeFinderFactory { class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory {
/// Creates an object to manage a Flutter Driver connection. /// Creates an object to manage a Flutter Driver connection.
FlutterDriverExtension( FlutterDriverExtension(
this._requestDataHandler, this._requestDataHandler,
...@@ -354,112 +357,39 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -354,112 +357,39 @@ class FlutterDriverExtension with DeserializeFinderFactory {
return finder; return finder;
} }
Finder _createByTextFinder(ByText arguments) { @override
return find.text(arguments.text); SerializableFinder deserializeFinder(Map<String, String> json) {
} final SerializableFinder standard = super.deserializeFinder(json);
if (standard != null) {
Finder _createByTooltipMessageFinder(ByTooltipMessage arguments) { return standard;
return find.byElementPredicate((Element element) {
final Widget widget = element.widget;
if (widget is Tooltip)
return widget.message == arguments.text;
return false;
}, description: 'widget with text tooltip "${arguments.text}"');
}
Finder _createBySemanticsLabelFinder(BySemanticsLabel arguments) {
return find.byElementPredicate((Element element) {
if (element is! RenderObjectElement) {
return false;
}
final String semanticsLabel = element.renderObject?.debugSemantics?.label;
if (semanticsLabel == null) {
return false;
}
final Pattern label = arguments.label;
return label is RegExp
? label.hasMatch(semanticsLabel)
: label == semanticsLabel;
}, description: 'widget with semantic label "${arguments.label}"');
}
Finder _createByValueKeyFinder(ByValueKey arguments) {
switch (arguments.keyValueType) {
case 'int':
return find.byKey(ValueKey<int>(arguments.keyValue as int));
case 'String':
return find.byKey(ValueKey<String>(arguments.keyValue as String));
default:
throw 'Unsupported ByValueKey type: ${arguments.keyValueType}';
} }
}
Finder _createByTypeFinder(ByType arguments) { final String finderType = json['finderType'];
return find.byElementPredicate((Element element) { if (_finderExtensions.containsKey(finderType)) {
return element.widget.runtimeType.toString() == arguments.type; return _finderExtensions[finderType].deserialize(json, this);
}, description: 'widget with runtimeType "${arguments.type}"'); }
}
Finder _createPageBackFinder() {
return find.byElementPredicate((Element element) {
final Widget widget = element.widget;
if (widget is Tooltip)
return widget.message == 'Back';
if (widget is CupertinoNavigationBarBackButton)
return true;
return false;
}, description: 'Material or Cupertino back button');
}
Finder _createAncestorFinder(Ancestor arguments) { throw DriverError('Unsupported search specification type $finderType');
final Finder finder = find.ancestor(
of: _createFinder(arguments.of),
matching: _createFinder(arguments.matching),
matchRoot: arguments.matchRoot,
);
return arguments.firstMatchOnly ? finder.first : finder;
} }
Finder _createDescendantFinder(Descendant arguments) { @override
final Finder finder = find.descendant( Finder createFinder(SerializableFinder finder) {
of: _createFinder(arguments.of), final Finder standard = super.createFinder(finder);
matching: _createFinder(arguments.matching), if(standard != null) {
matchRoot: arguments.matchRoot, return standard;
); }
return arguments.firstMatchOnly ? finder.first : finder;
}
Finder _createFinder(SerializableFinder finder) { if (_finderExtensions.containsKey(finder.finderType)) {
switch (finder.finderType) { return _finderExtensions[finder.finderType].createFinder(finder, this);
case 'ByText':
return _createByTextFinder(finder as ByText);
case 'ByTooltipMessage':
return _createByTooltipMessageFinder(finder as ByTooltipMessage);
case 'BySemanticsLabel':
return _createBySemanticsLabelFinder(finder as BySemanticsLabel);
case 'ByValueKey':
return _createByValueKeyFinder(finder as ByValueKey);
case 'ByType':
return _createByTypeFinder(finder as ByType);
case 'PageBack':
return _createPageBackFinder();
case 'Ancestor':
return _createAncestorFinder(finder as Ancestor);
case 'Descendant':
return _createDescendantFinder(finder as Descendant);
default:
if (_finderExtensions.containsKey(finder.finderType)) {
return _finderExtensions[finder.finderType].createFinder(finder);
} else {
throw 'Unsupported finder type: ${finder.finderType}';
}
} }
throw DriverError('Unsupported search specification type ${finder.finderType}');
} }
Future<TapResult> _tap(Command command) async { Future<TapResult> _tap(Command command) async {
final Tap tapCommand = command as Tap; final Tap tapCommand = command as Tap;
final Finder computedFinder = await _waitForElement( final Finder computedFinder = await _waitForElement(
_createFinder(tapCommand.finder).hitTestable() createFinder(tapCommand.finder).hitTestable()
); );
await _prober.tap(computedFinder); await _prober.tap(computedFinder);
return const TapResult(); return const TapResult();
...@@ -467,13 +397,13 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -467,13 +397,13 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<WaitForResult> _waitFor(Command command) async { Future<WaitForResult> _waitFor(Command command) async {
final WaitFor waitForCommand = command as WaitFor; final WaitFor waitForCommand = command as WaitFor;
await _waitForElement(_createFinder(waitForCommand.finder)); await _waitForElement(createFinder(waitForCommand.finder));
return const WaitForResult(); return const WaitForResult();
} }
Future<WaitForAbsentResult> _waitForAbsent(Command command) async { Future<WaitForAbsentResult> _waitForAbsent(Command command) async {
final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent; final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent;
await _waitForAbsentElement(_createFinder(waitForAbsentCommand.finder)); await _waitForAbsentElement(createFinder(waitForAbsentCommand.finder));
return const WaitForAbsentResult(); return const WaitForAbsentResult();
} }
...@@ -528,7 +458,7 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -528,7 +458,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<GetSemanticsIdResult> _getSemanticsId(Command command) async { Future<GetSemanticsIdResult> _getSemanticsId(Command command) async {
final GetSemanticsId semanticsCommand = command as GetSemanticsId; final GetSemanticsId semanticsCommand = command as GetSemanticsId;
final Finder target = await _waitForElement(_createFinder(semanticsCommand.finder)); final Finder target = await _waitForElement(createFinder(semanticsCommand.finder));
final Iterable<Element> elements = target.evaluate(); final Iterable<Element> elements = target.evaluate();
if (elements.length > 1) { if (elements.length > 1) {
throw StateError('Found more than one element with the same ID: $elements'); throw StateError('Found more than one element with the same ID: $elements');
...@@ -547,7 +477,7 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -547,7 +477,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<GetOffsetResult> _getOffset(Command command) async { Future<GetOffsetResult> _getOffset(Command command) async {
final GetOffset getOffsetCommand = command as GetOffset; final GetOffset getOffsetCommand = command as GetOffset;
final Finder finder = await _waitForElement(_createFinder(getOffsetCommand.finder)); final Finder finder = await _waitForElement(createFinder(getOffsetCommand.finder));
final Element element = finder.evaluate().single; final Element element = finder.evaluate().single;
final RenderBox box = element.renderObject as RenderBox; final RenderBox box = element.renderObject as RenderBox;
Offset localPoint; Offset localPoint;
...@@ -574,7 +504,7 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -574,7 +504,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command) async { Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command) async {
final GetDiagnosticsTree diagnosticsCommand = command as GetDiagnosticsTree; final GetDiagnosticsTree diagnosticsCommand = command as GetDiagnosticsTree;
final Finder finder = await _waitForElement(_createFinder(diagnosticsCommand.finder)); final Finder finder = await _waitForElement(createFinder(diagnosticsCommand.finder));
final Element element = finder.evaluate().single; final Element element = finder.evaluate().single;
DiagnosticsNode diagnosticsNode; DiagnosticsNode diagnosticsNode;
switch (diagnosticsCommand.diagnosticsType) { switch (diagnosticsCommand.diagnosticsType) {
...@@ -593,7 +523,7 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -593,7 +523,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<ScrollResult> _scroll(Command command) async { Future<ScrollResult> _scroll(Command command) async {
final Scroll scrollCommand = command as Scroll; final Scroll scrollCommand = command as Scroll;
final Finder target = await _waitForElement(_createFinder(scrollCommand.finder)); final Finder target = await _waitForElement(createFinder(scrollCommand.finder));
final int totalMoves = scrollCommand.duration.inMicroseconds * scrollCommand.frequency ~/ Duration.microsecondsPerSecond; final int totalMoves = scrollCommand.duration.inMicroseconds * scrollCommand.frequency ~/ Duration.microsecondsPerSecond;
final Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble(); final Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble();
final Duration pause = scrollCommand.duration ~/ totalMoves; final Duration pause = scrollCommand.duration ~/ totalMoves;
...@@ -614,14 +544,14 @@ class FlutterDriverExtension with DeserializeFinderFactory { ...@@ -614,14 +544,14 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<ScrollResult> _scrollIntoView(Command command) async { Future<ScrollResult> _scrollIntoView(Command command) async {
final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView; final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView;
final Finder target = await _waitForElement(_createFinder(scrollIntoViewCommand.finder)); final Finder target = await _waitForElement(createFinder(scrollIntoViewCommand.finder));
await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment ?? 0.0); await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment ?? 0.0);
return const ScrollResult(); return const ScrollResult();
} }
Future<GetTextResult> _getText(Command command) async { Future<GetTextResult> _getText(Command command) async {
final GetText getTextCommand = command as GetText; final GetText getTextCommand = command as GetText;
final Finder target = await _waitForElement(_createFinder(getTextCommand.finder)); final Finder target = await _waitForElement(createFinder(getTextCommand.finder));
final Widget widget = target.evaluate().single.widget; final Widget widget = target.evaluate().single.widget;
String text; String text;
......
...@@ -18,6 +18,9 @@ import 'package:flutter_driver/src/common/wait.dart'; ...@@ -18,6 +18,9 @@ import 'package:flutter_driver/src/common/wait.dart';
import 'package:flutter_driver/src/extension/extension.dart'; import 'package:flutter_driver/src/extension/extension.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'stubs/stub_finder.dart';
import 'stubs/stub_finder_extension.dart';
Future<void> silenceDriverLogger(AsyncCallback callback) async { Future<void> silenceDriverLogger(AsyncCallback callback) async {
final DriverLogCallback oldLogger = driverLog; final DriverLogCallback oldLogger = driverLog;
driverLog = (String source, String message) { }; driverLog = (String source, String message) { };
...@@ -30,18 +33,18 @@ Future<void> silenceDriverLogger(AsyncCallback callback) async { ...@@ -30,18 +33,18 @@ Future<void> silenceDriverLogger(AsyncCallback callback) async {
void main() { void main() {
group('waitUntilNoTransientCallbacks', () { group('waitUntilNoTransientCallbacks', () {
FlutterDriverExtension extension; FlutterDriverExtension driverExtension;
Map<String, dynamic> result; Map<String, dynamic> result;
int messageId = 0; int messageId = 0;
final List<String> log = <String>[]; final List<String> log = <String>[];
setUp(() { setUp(() {
result = null; result = null;
extension = FlutterDriverExtension((String message) async { log.add(message); return (messageId += 1).toString(); }, false); driverExtension = FlutterDriverExtension((String message) async { log.add(message); return (messageId += 1).toString(); }, false);
}); });
testWidgets('returns immediately when transient callback queue is empty', (WidgetTester tester) async { testWidgets('returns immediately when transient callback queue is empty', (WidgetTester tester) async {
extension.call(const WaitUntilNoTransientCallbacks().serialize()) driverExtension.call(const WaitUntilNoTransientCallbacks().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -61,7 +64,7 @@ void main() { ...@@ -61,7 +64,7 @@ void main() {
// Intentionally blank. We only care about existence of a callback. // Intentionally blank. We only care about existence of a callback.
}); });
extension.call(const WaitUntilNoTransientCallbacks().serialize()) driverExtension.call(const WaitUntilNoTransientCallbacks().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -83,7 +86,7 @@ void main() { ...@@ -83,7 +86,7 @@ void main() {
testWidgets('handler', (WidgetTester tester) async { testWidgets('handler', (WidgetTester tester) async {
expect(log, isEmpty); expect(log, isEmpty);
final Map<String, dynamic> response = await extension.call(const RequestData('hello').serialize()); final Map<String, dynamic> response = await driverExtension.call(const RequestData('hello').serialize());
final RequestDataResult result = RequestDataResult.fromJson(response['response'] as Map<String, dynamic>); final RequestDataResult result = RequestDataResult.fromJson(response['response'] as Map<String, dynamic>);
expect(log, <String>['hello']); expect(log, <String>['hello']);
expect(result.message, '1'); expect(result.message, '1');
...@@ -91,18 +94,18 @@ void main() { ...@@ -91,18 +94,18 @@ void main() {
}); });
group('waitForCondition', () { group('waitForCondition', () {
FlutterDriverExtension extension; FlutterDriverExtension driverExtension;
Map<String, dynamic> result; Map<String, dynamic> result;
int messageId = 0; int messageId = 0;
final List<String> log = <String>[]; final List<String> log = <String>[];
setUp(() { setUp(() {
result = null; result = null;
extension = FlutterDriverExtension((String message) async { log.add(message); return (messageId += 1).toString(); }, false); driverExtension = FlutterDriverExtension((String message) async { log.add(message); return (messageId += 1).toString(); }, false);
}); });
testWidgets('waiting for NoTransientCallbacks returns immediately when transient callback queue is empty', (WidgetTester tester) async { testWidgets('waiting for NoTransientCallbacks returns immediately when transient callback queue is empty', (WidgetTester tester) async {
extension.call(const WaitForCondition(NoTransientCallbacks()).serialize()) driverExtension.call(const WaitForCondition(NoTransientCallbacks()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -122,7 +125,7 @@ void main() { ...@@ -122,7 +125,7 @@ void main() {
// Intentionally blank. We only care about existence of a callback. // Intentionally blank. We only care about existence of a callback.
}); });
extension.call(const WaitForCondition(NoTransientCallbacks()).serialize()) driverExtension.call(const WaitForCondition(NoTransientCallbacks()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -144,7 +147,7 @@ void main() { ...@@ -144,7 +147,7 @@ void main() {
testWidgets('waiting for NoPendingFrame returns immediately when frame is synced', ( testWidgets('waiting for NoPendingFrame returns immediately when frame is synced', (
WidgetTester tester) async { WidgetTester tester) async {
extension.call(const WaitForCondition(NoPendingFrame()).serialize()) driverExtension.call(const WaitForCondition(NoPendingFrame()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -162,7 +165,7 @@ void main() { ...@@ -162,7 +165,7 @@ void main() {
testWidgets('waiting for NoPendingFrame returns until no pending scheduled frame', (WidgetTester tester) async { testWidgets('waiting for NoPendingFrame returns until no pending scheduled frame', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrame(); SchedulerBinding.instance.scheduleFrame();
extension.call(const WaitForCondition(NoPendingFrame()).serialize()) driverExtension.call(const WaitForCondition(NoPendingFrame()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -186,7 +189,7 @@ void main() { ...@@ -186,7 +189,7 @@ void main() {
'waiting for combined conditions returns immediately', (WidgetTester tester) async { 'waiting for combined conditions returns immediately', (WidgetTester tester) async {
const SerializableWaitCondition combinedCondition = const SerializableWaitCondition combinedCondition =
CombinedCondition(<SerializableWaitCondition>[NoTransientCallbacks(), NoPendingFrame()]); CombinedCondition(<SerializableWaitCondition>[NoTransientCallbacks(), NoPendingFrame()]);
extension.call(const WaitForCondition(combinedCondition).serialize()) driverExtension.call(const WaitForCondition(combinedCondition).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -210,7 +213,7 @@ void main() { ...@@ -210,7 +213,7 @@ void main() {
const SerializableWaitCondition combinedCondition = const SerializableWaitCondition combinedCondition =
CombinedCondition(<SerializableWaitCondition>[NoTransientCallbacks(), NoPendingFrame()]); CombinedCondition(<SerializableWaitCondition>[NoTransientCallbacks(), NoPendingFrame()]);
extension.call(const WaitForCondition(combinedCondition).serialize()) driverExtension.call(const WaitForCondition(combinedCondition).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -239,7 +242,7 @@ void main() { ...@@ -239,7 +242,7 @@ void main() {
const SerializableWaitCondition combinedCondition = const SerializableWaitCondition combinedCondition =
CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]); CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]);
extension.call(const WaitForCondition(combinedCondition).serialize()) driverExtension.call(const WaitForCondition(combinedCondition).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -261,7 +264,7 @@ void main() { ...@@ -261,7 +264,7 @@ void main() {
testWidgets( testWidgets(
"waiting for NoPendingPlatformMessages returns immediately when there're no platform messages", (WidgetTester tester) async { "waiting for NoPendingPlatformMessages returns immediately when there're no platform messages", (WidgetTester tester) async {
extension driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize()) .call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
...@@ -289,7 +292,7 @@ void main() { ...@@ -289,7 +292,7 @@ void main() {
}); });
channel.invokeMethod<String>('sayHello', 'hello'); channel.invokeMethod<String>('sayHello', 'hello');
extension driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize()) .call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
...@@ -334,7 +337,7 @@ void main() { ...@@ -334,7 +337,7 @@ void main() {
channel1.invokeMethod<String>('sayHello', 'hello'); channel1.invokeMethod<String>('sayHello', 'hello');
channel2.invokeMethod<String>('sayHello', 'hello'); channel2.invokeMethod<String>('sayHello', 'hello');
extension driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize()) .call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
...@@ -383,7 +386,7 @@ void main() { ...@@ -383,7 +386,7 @@ void main() {
channel1.invokeMethod<String>('sayHello', 'hello'); channel1.invokeMethod<String>('sayHello', 'hello');
// Calls the waiting API before the second channel message is sent. // Calls the waiting API before the second channel message is sent.
extension driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize()) .call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
...@@ -433,7 +436,7 @@ void main() { ...@@ -433,7 +436,7 @@ void main() {
channel1.invokeMethod<String>('sayHello', 'hello'); channel1.invokeMethod<String>('sayHello', 'hello');
extension driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize()) .call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
...@@ -462,9 +465,9 @@ void main() { ...@@ -462,9 +465,9 @@ void main() {
}); });
group('getSemanticsId', () { group('getSemanticsId', () {
FlutterDriverExtension extension; FlutterDriverExtension driverExtension;
setUp(() { setUp(() {
extension = FlutterDriverExtension((String arg) async => '', true); driverExtension = FlutterDriverExtension((String arg) async => '', true);
}); });
testWidgets('works when semantics are enabled', (WidgetTester tester) async { testWidgets('works when semantics are enabled', (WidgetTester tester) async {
...@@ -473,7 +476,7 @@ void main() { ...@@ -473,7 +476,7 @@ void main() {
const Text('hello', textDirection: TextDirection.ltr)); const Text('hello', textDirection: TextDirection.ltr));
final Map<String, String> arguments = GetSemanticsId(const ByText('hello')).serialize(); final Map<String, String> arguments = GetSemanticsId(const ByText('hello')).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(response['response'] as Map<String, dynamic>); final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(response['response'] as Map<String, dynamic>);
expect(result.id, 1); expect(result.id, 1);
...@@ -485,7 +488,7 @@ void main() { ...@@ -485,7 +488,7 @@ void main() {
const Text('hello', textDirection: TextDirection.ltr)); const Text('hello', textDirection: TextDirection.ltr));
final Map<String, String> arguments = GetSemanticsId(const ByText('hello')).serialize(); final Map<String, String> arguments = GetSemanticsId(const ByText('hello')).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
expect(response['isError'], true); expect(response['isError'], true);
expect(response['response'], contains('Bad state: No semantics data found')); expect(response['response'], contains('Bad state: No semantics data found'));
...@@ -504,7 +507,7 @@ void main() { ...@@ -504,7 +507,7 @@ void main() {
); );
final Map<String, String> arguments = GetSemanticsId(const ByText('hello')).serialize(); final Map<String, String> arguments = GetSemanticsId(const ByText('hello')).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
expect(response['isError'], true); expect(response['isError'], true);
expect(response['response'], contains('Bad state: Found more than one element with the same ID')); expect(response['response'], contains('Bad state: Found more than one element with the same ID'));
...@@ -513,11 +516,11 @@ void main() { ...@@ -513,11 +516,11 @@ void main() {
}); });
testWidgets('getOffset', (WidgetTester tester) async { testWidgets('getOffset', (WidgetTester tester) async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<Offset> getOffset(OffsetType offset) async { Future<Offset> getOffset(OffsetType offset) async {
final Map<String, String> arguments = GetOffset(ByValueKey(1), offset).serialize(); final Map<String, String> arguments = GetOffset(ByValueKey(1), offset).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
final GetOffsetResult result = GetOffsetResult.fromJson(response['response'] as Map<String, dynamic>); final GetOffsetResult result = GetOffsetResult.fromJson(response['response'] as Map<String, dynamic>);
return Offset(result.dx, result.dy); return Offset(result.dx, result.dy);
} }
...@@ -545,11 +548,11 @@ void main() { ...@@ -545,11 +548,11 @@ void main() {
testWidgets('getText', (WidgetTester tester) async { testWidgets('getText', (WidgetTester tester) async {
await silenceDriverLogger(() async { await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<String> getTextInternal(SerializableFinder search) async { Future<String> getTextInternal(SerializableFinder search) async {
final Map<String, String> arguments = GetText(search, timeout: const Duration(seconds: 1)).serialize(); final Map<String, String> arguments = GetText(search, timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> result = await extension.call(arguments); final Map<String, dynamic> result = await driverExtension.call(arguments);
if (result['isError'] as bool) { if (result['isError'] as bool) {
return null; return null;
} }
...@@ -607,7 +610,7 @@ void main() { ...@@ -607,7 +610,7 @@ void main() {
// Check if error thrown for other types // Check if error thrown for other types
final Map<String, String> arguments = GetText(ByValueKey('column'), timeout: const Duration(seconds: 1)).serialize(); final Map<String, String> arguments = GetText(ByValueKey('column'), timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
expect(response['isError'], true); expect(response['isError'], true);
expect(response['response'], contains('is currently not supported by getText')); expect(response['response'], contains('is currently not supported by getText'));
}); });
...@@ -615,7 +618,7 @@ void main() { ...@@ -615,7 +618,7 @@ void main() {
testWidgets('descendant finder', (WidgetTester tester) async { testWidgets('descendant finder', (WidgetTester tester) async {
await silenceDriverLogger(() async { await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<String> getDescendantText({ String of, bool matchRoot = false}) async { Future<String> getDescendantText({ String of, bool matchRoot = false}) async {
final Map<String, String> arguments = GetText(Descendant( final Map<String, String> arguments = GetText(Descendant(
...@@ -623,7 +626,7 @@ void main() { ...@@ -623,7 +626,7 @@ void main() {
matching: ByValueKey('text2'), matching: ByValueKey('text2'),
matchRoot: matchRoot, matchRoot: matchRoot,
), timeout: const Duration(seconds: 1)).serialize(); ), timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> result = await extension.call(arguments); final Map<String, dynamic> result = await driverExtension.call(arguments);
if (result['isError'] as bool) { if (result['isError'] as bool) {
return null; return null;
} }
...@@ -660,7 +663,7 @@ void main() { ...@@ -660,7 +663,7 @@ void main() {
testWidgets('descendant finder firstMatchOnly', (WidgetTester tester) async { testWidgets('descendant finder firstMatchOnly', (WidgetTester tester) async {
await silenceDriverLogger(() async { await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<String> getDescendantText() async { Future<String> getDescendantText() async {
final Map<String, String> arguments = GetText(Descendant( final Map<String, String> arguments = GetText(Descendant(
...@@ -668,7 +671,7 @@ void main() { ...@@ -668,7 +671,7 @@ void main() {
matching: const ByType('Text'), matching: const ByType('Text'),
firstMatchOnly: true, firstMatchOnly: true,
), timeout: const Duration(seconds: 1)).serialize(); ), timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> result = await extension.call(arguments); final Map<String, dynamic> result = await driverExtension.call(arguments);
if (result['isError'] as bool) { if (result['isError'] as bool) {
return null; return null;
} }
...@@ -694,7 +697,7 @@ void main() { ...@@ -694,7 +697,7 @@ void main() {
testWidgets('ancestor finder', (WidgetTester tester) async { testWidgets('ancestor finder', (WidgetTester tester) async {
await silenceDriverLogger(() async { await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<Offset> getAncestorTopLeft({ String of, String matching, bool matchRoot = false}) async { Future<Offset> getAncestorTopLeft({ String of, String matching, bool matchRoot = false}) async {
final Map<String, String> arguments = GetOffset(Ancestor( final Map<String, String> arguments = GetOffset(Ancestor(
...@@ -702,7 +705,7 @@ void main() { ...@@ -702,7 +705,7 @@ void main() {
matching: ByValueKey(matching), matching: ByValueKey(matching),
matchRoot: matchRoot, matchRoot: matchRoot,
), OffsetType.topLeft, timeout: const Duration(seconds: 1)).serialize(); ), OffsetType.topLeft, timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
if (response['isError'] as bool) { if (response['isError'] as bool) {
return null; return null;
} }
...@@ -764,7 +767,7 @@ void main() { ...@@ -764,7 +767,7 @@ void main() {
testWidgets('ancestor finder firstMatchOnly', (WidgetTester tester) async { testWidgets('ancestor finder firstMatchOnly', (WidgetTester tester) async {
await silenceDriverLogger(() async { await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<Offset> getAncestorTopLeft() async { Future<Offset> getAncestorTopLeft() async {
final Map<String, String> arguments = GetOffset(Ancestor( final Map<String, String> arguments = GetOffset(Ancestor(
...@@ -772,7 +775,7 @@ void main() { ...@@ -772,7 +775,7 @@ void main() {
matching: const ByType('Container'), matching: const ByType('Container'),
firstMatchOnly: true, firstMatchOnly: true,
), OffsetType.topLeft, timeout: const Duration(seconds: 1)).serialize(); ), OffsetType.topLeft, timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
if (response['isError'] as bool) { if (response['isError'] as bool) {
return null; return null;
} }
...@@ -812,11 +815,11 @@ void main() { ...@@ -812,11 +815,11 @@ void main() {
}); });
testWidgets('GetDiagnosticsTree', (WidgetTester tester) async { testWidgets('GetDiagnosticsTree', (WidgetTester tester) async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true); final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<Map<String, Object>> getDiagnosticsTree(DiagnosticsType type, SerializableFinder finder, { int depth = 0, bool properties = true }) async { Future<Map<String, Object>> getDiagnosticsTree(DiagnosticsType type, SerializableFinder finder, { int depth = 0, bool properties = true }) async {
final Map<String, String> arguments = GetDiagnosticsTree(finder, type, subtreeDepth: depth, includeProperties: properties).serialize(); final Map<String, String> arguments = GetDiagnosticsTree(finder, type, subtreeDepth: depth, includeProperties: properties).serialize();
final Map<String, dynamic> response = await extension.call(arguments); final Map<String, dynamic> response = await driverExtension.call(arguments);
final DiagnosticsTreeResult result = DiagnosticsTreeResult(response['response'] as Map<String, dynamic>); final DiagnosticsTreeResult result = DiagnosticsTreeResult(response['response'] as Map<String, dynamic>);
return result.json; return result.json;
} }
...@@ -877,18 +880,120 @@ void main() { ...@@ -877,18 +880,120 @@ void main() {
expect(children.single['children'], isEmpty); expect(children.single['children'], isEmpty);
}); });
group('extension finders', () {
final Widget debugTree = Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Column(
key: const ValueKey<String>('Column'),
children: <Widget>[
const Text('Foo', key: ValueKey<String>('Text1')),
const Text('Bar', key: ValueKey<String>('Text2')),
FlatButton(
child: const Text('Whatever'),
key: const ValueKey<String>('Button'),
onPressed: () {},
),
],
),
),
);
testWidgets('unknown extension finder', (WidgetTester tester) async {
final FlutterDriverExtension driverExtension = FlutterDriverExtension(
(String arg) async => '',
true,
finders: <FinderExtension>[],
);
Future<Map<String, dynamic>> getText(SerializableFinder finder) async {
final Map<String, String> arguments = GetText(finder, timeout: const Duration(seconds: 1)).serialize();
return await driverExtension.call(arguments);
}
await tester.pumpWidget(debugTree);
final Map<String, dynamic> result = await getText(StubFinder('Text1'));
expect(result['isError'], true);
expect(result['response'] is String, true);
expect(result['response'] as String, contains('Unsupported search specification type Stub'));
});
testWidgets('simple extension finder', (WidgetTester tester) async {
final FlutterDriverExtension driverExtension = FlutterDriverExtension(
(String arg) async => '',
true,
finders: <FinderExtension>[
StubFinderExtension(),
],
);
Future<GetTextResult> getText(SerializableFinder finder) async {
final Map<String, String> arguments = GetText(finder, timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> response = await driverExtension.call(arguments);
return GetTextResult.fromJson(response['response'] as Map<String, dynamic>);
}
await tester.pumpWidget(debugTree);
final GetTextResult result = await getText(StubFinder('Text1'));
expect(result.text, 'Foo');
});
testWidgets('complex extension finder', (WidgetTester tester) async {
final FlutterDriverExtension driverExtension = FlutterDriverExtension(
(String arg) async => '',
true,
finders: <FinderExtension>[
StubFinderExtension(),
],
);
Future<GetTextResult> getText(SerializableFinder finder) async {
final Map<String, String> arguments = GetText(finder, timeout: const Duration(seconds: 1)).serialize();
final Map<String, dynamic> response = await driverExtension.call(arguments);
return GetTextResult.fromJson(response['response'] as Map<String, dynamic>);
}
await tester.pumpWidget(debugTree);
final GetTextResult result = await getText(Descendant(of: StubFinder('Column'), matching: StubFinder('Text1')));
expect(result.text, 'Foo');
});
testWidgets('extension finder with command', (WidgetTester tester) async {
final FlutterDriverExtension driverExtension = FlutterDriverExtension(
(String arg) async => '',
true,
finders: <FinderExtension>[
StubFinderExtension(),
],
);
Future<Map<String, dynamic>> tap(SerializableFinder finder) async {
final Map<String, String> arguments = Tap(finder, timeout: const Duration(seconds: 1)).serialize();
return await driverExtension.call(arguments);
}
await tester.pumpWidget(debugTree);
final Map<String, dynamic> result = await tap(StubFinder('Button'));
expect(result['isError'], false);
});
});
group('waitUntilFrameSync', () { group('waitUntilFrameSync', () {
FlutterDriverExtension extension; FlutterDriverExtension driverExtension;
Map<String, dynamic> result; Map<String, dynamic> result;
setUp(() { setUp(() {
extension = FlutterDriverExtension((String arg) async => '', true); driverExtension = FlutterDriverExtension((String arg) async => '', true);
result = null; result = null;
}); });
testWidgets('returns immediately when frame is synced', ( testWidgets('returns immediately when frame is synced', (
WidgetTester tester) async { WidgetTester tester) async {
extension.call(const WaitUntilNoPendingFrame().serialize()) driverExtension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -909,7 +1014,7 @@ void main() { ...@@ -909,7 +1014,7 @@ void main() {
// Intentionally blank. We only care about existence of a callback. // Intentionally blank. We only care about existence of a callback.
}); });
extension.call(const WaitUntilNoPendingFrame().serialize()) driverExtension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
...@@ -933,7 +1038,7 @@ void main() { ...@@ -933,7 +1038,7 @@ void main() {
'waits until no pending scheduled frame', (WidgetTester tester) async { 'waits until no pending scheduled frame', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrame(); SchedulerBinding.instance.scheduleFrame();
extension.call(const WaitUntilNoPendingFrame().serialize()) driverExtension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) { .then<void>(expectAsync1((Map<String, dynamic> r) {
result = r; result = r;
})); }));
......
// 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 'package:flutter_driver/flutter_driver.dart';
class StubFinder extends SerializableFinder {
StubFinder(this.keyString);
final String keyString;
@override
String get finderType => 'Stub';
@override
Map<String, String> serialize() {
return super.serialize()..addAll(<String, String>{'keyString': keyString});
}
}
// 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 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_driver/src/common/create_finder_factory.dart';
import 'package:flutter_test/src/finders.dart';
import 'package:flutter_driver/src/common/find.dart';
import 'stub_finder.dart';
class StubFinderExtension extends FinderExtension {
@override
Finder createFinder(
SerializableFinder finder,
CreateFinderFactory finderFactory,
) {
return find.byWidgetPredicate((Widget widget) {
final Key key = widget.key;
if (key is! ValueKey<String>) {
return false;
}
return (key as ValueKey<String>).value == (finder as StubFinder).keyString;
});
}
@override
SerializableFinder deserialize(
Map<String, String> params,
DeserializeFinderFactory finderFactory,
) {
return StubFinder(params['keyString']);
}
@override
String get finderType => 'Stub';
}
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