Unverified Commit 759ddb1c authored by Marcin Jeleński's avatar Marcin Jeleński Committed by GitHub

Reland "Flutter Driver - Create widget finders from serialized finders...

Reland "Flutter Driver - Create widget finders from serialized finders extensions" with null safety (#67711)

* Flutter Driver - Create widget finders from serialized finders extensions
parent 24abe59f
// 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 'error.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) {
final String finderType = finder.finderType;
switch (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:
throw DriverError('Unsupported search specification type $finderType');
}
}
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;
}
}
......@@ -16,6 +16,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../common/create_finder_factory.dart';
import '../common/diagnostics_tree.dart';
import '../common/error.dart';
import '../common/find.dart';
......@@ -123,7 +124,7 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// return Some(json['title']);
/// }
///
/// Finder createFinder(SerializableFinder finder) {
/// Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
/// Some someFinder = finder as Some;
///
/// return find.byElementPredicate((Element element) {
......@@ -156,11 +157,13 @@ abstract class FinderExtension {
String get finderType;
/// 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);
/// Signature for functions that run the given finder and return the [Element]
/// 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
......@@ -169,7 +172,7 @@ abstract class FinderExtension {
/// This is not normally used directly. It is instantiated automatically when
/// calling [enableFlutterDriverExtension].
@visibleForTesting
class FlutterDriverExtension with DeserializeFinderFactory {
class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory {
/// Creates an object to manage a Flutter Driver connection.
FlutterDriverExtension(
this._requestDataHandler,
......@@ -354,112 +357,29 @@ class FlutterDriverExtension with DeserializeFinderFactory {
return finder;
}
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}"');
@override
SerializableFinder deserializeFinder(Map<String, String> json) {
final String? finderType = json['finderType'];
if (_finderExtensions.containsKey(finderType)) {
return _finderExtensions[finderType]!.deserialize(json, this);
}
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');
return super.deserializeFinder(json);
}
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;
}
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:
@override
Finder createFinder(SerializableFinder finder) {
if (_finderExtensions.containsKey(finder.finderType)) {
return _finderExtensions[finder.finderType]!.createFinder(finder);
} else {
throw 'Unsupported finder type: ${finder.finderType}';
}
return _finderExtensions[finder.finderType]!.createFinder(finder, this);
}
return super.createFinder(finder);
}
Future<TapResult> _tap(Command command) async {
final Tap tapCommand = command as Tap;
final Finder computedFinder = await _waitForElement(
_createFinder(tapCommand.finder).hitTestable()
createFinder(tapCommand.finder).hitTestable()
);
await _prober.tap(computedFinder);
return const TapResult();
......@@ -467,13 +387,13 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<WaitForResult> _waitFor(Command command) async {
final WaitFor waitForCommand = command as WaitFor;
await _waitForElement(_createFinder(waitForCommand.finder));
await _waitForElement(createFinder(waitForCommand.finder));
return const WaitForResult();
}
Future<WaitForAbsentResult> _waitForAbsent(Command command) async {
final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent;
await _waitForAbsentElement(_createFinder(waitForAbsentCommand.finder));
await _waitForAbsentElement(createFinder(waitForAbsentCommand.finder));
return const WaitForAbsentResult();
}
......@@ -528,7 +448,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<GetSemanticsIdResult> _getSemanticsId(Command command) async {
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();
if (elements.length > 1) {
throw StateError('Found more than one element with the same ID: $elements');
......@@ -547,7 +467,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<GetOffsetResult> _getOffset(Command command) async {
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 RenderBox box = (element.renderObject as RenderBox?)!;
Offset localPoint;
......@@ -574,7 +494,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command) async {
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;
DiagnosticsNode diagnosticsNode;
switch (diagnosticsCommand.diagnosticsType) {
......@@ -593,7 +513,7 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<ScrollResult> _scroll(Command command) async {
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 Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble();
final Duration pause = scrollCommand.duration ~/ totalMoves;
......@@ -614,18 +534,14 @@ class FlutterDriverExtension with DeserializeFinderFactory {
Future<ScrollResult> _scrollIntoView(Command command) async {
final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView;
final Finder target = await _waitForElement(_createFinder(scrollIntoViewCommand.finder));
await Scrollable.ensureVisible(
target.evaluate().single,
duration: const Duration(milliseconds: 100),
alignment: scrollIntoViewCommand.alignment,
);
final Finder target = await _waitForElement(createFinder(scrollIntoViewCommand.finder));
await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment);
return const ScrollResult();
}
Future<GetTextResult> _getText(Command command) async {
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;
String? text;
......
......@@ -20,6 +20,9 @@ import 'package:flutter_driver/src/common/wait.dart';
import 'package:flutter_driver/src/extension/extension.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 {
final DriverLogCallback oldLogger = driverLog;
driverLog = (String source, String message) { };
......@@ -32,18 +35,18 @@ Future<void> silenceDriverLogger(AsyncCallback callback) async {
void main() {
group('waitUntilNoTransientCallbacks', () {
FlutterDriverExtension extension;
FlutterDriverExtension driverExtension;
Map<String, dynamic> result;
int messageId = 0;
final List<String> log = <String>[];
setUp(() {
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 {
extension.call(const WaitUntilNoTransientCallbacks().serialize())
driverExtension.call(const WaitUntilNoTransientCallbacks().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -63,7 +66,7 @@ void main() {
// 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) {
result = r;
}));
......@@ -85,7 +88,7 @@ void main() {
testWidgets('handler', (WidgetTester tester) async {
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>);
expect(log, <String>['hello']);
expect(result.message, '1');
......@@ -93,18 +96,18 @@ void main() {
});
group('waitForCondition', () {
FlutterDriverExtension extension;
FlutterDriverExtension driverExtension;
Map<String, dynamic> result;
int messageId = 0;
final List<String> log = <String>[];
setUp(() {
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 {
extension.call(const WaitForCondition(NoTransientCallbacks()).serialize())
driverExtension.call(const WaitForCondition(NoTransientCallbacks()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -124,7 +127,7 @@ void main() {
// 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) {
result = r;
}));
......@@ -146,7 +149,7 @@ void main() {
testWidgets('waiting for NoPendingFrame returns immediately when frame is synced', (
WidgetTester tester) async {
extension.call(const WaitForCondition(NoPendingFrame()).serialize())
driverExtension.call(const WaitForCondition(NoPendingFrame()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -164,7 +167,7 @@ void main() {
testWidgets('waiting for NoPendingFrame returns until no pending scheduled frame', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrame();
extension.call(const WaitForCondition(NoPendingFrame()).serialize())
driverExtension.call(const WaitForCondition(NoPendingFrame()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -188,7 +191,7 @@ void main() {
'waiting for combined conditions returns immediately', (WidgetTester tester) async {
const SerializableWaitCondition combinedCondition =
CombinedCondition(<SerializableWaitCondition>[NoTransientCallbacks(), NoPendingFrame()]);
extension.call(const WaitForCondition(combinedCondition).serialize())
driverExtension.call(const WaitForCondition(combinedCondition).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -212,7 +215,7 @@ void main() {
const SerializableWaitCondition combinedCondition =
CombinedCondition(<SerializableWaitCondition>[NoTransientCallbacks(), NoPendingFrame()]);
extension.call(const WaitForCondition(combinedCondition).serialize())
driverExtension.call(const WaitForCondition(combinedCondition).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -241,7 +244,7 @@ void main() {
const SerializableWaitCondition combinedCondition =
CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]);
extension.call(const WaitForCondition(combinedCondition).serialize())
driverExtension.call(const WaitForCondition(combinedCondition).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -263,7 +266,7 @@ void main() {
testWidgets(
"waiting for NoPendingPlatformMessages returns immediately when there're no platform messages", (WidgetTester tester) async {
extension
driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
......@@ -291,7 +294,7 @@ void main() {
});
channel.invokeMethod<String>('sayHello', 'hello');
extension
driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
......@@ -336,7 +339,7 @@ void main() {
channel1.invokeMethod<String>('sayHello', 'hello');
channel2.invokeMethod<String>('sayHello', 'hello');
extension
driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
......@@ -385,7 +388,7 @@ void main() {
channel1.invokeMethod<String>('sayHello', 'hello');
// Calls the waiting API before the second channel message is sent.
extension
driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
......@@ -435,7 +438,7 @@ void main() {
channel1.invokeMethod<String>('sayHello', 'hello');
extension
driverExtension
.call(const WaitForCondition(NoPendingPlatformMessages()).serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
......@@ -464,9 +467,9 @@ void main() {
});
group('getSemanticsId', () {
FlutterDriverExtension extension;
FlutterDriverExtension driverExtension;
setUp(() {
extension = FlutterDriverExtension((String arg) async => '', true);
driverExtension = FlutterDriverExtension((String arg) async => '', true);
});
testWidgets('works when semantics are enabled', (WidgetTester tester) async {
......@@ -475,7 +478,7 @@ void main() {
const Text('hello', textDirection: TextDirection.ltr));
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>);
expect(result.id, 1);
......@@ -487,7 +490,7 @@ void main() {
const Text('hello', textDirection: TextDirection.ltr));
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['response'], contains('Bad state: No semantics data found'));
......@@ -506,7 +509,7 @@ void main() {
);
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['response'], contains('Bad state: Found more than one element with the same ID'));
......@@ -515,11 +518,11 @@ void main() {
});
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 {
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>);
return Offset(result.dx, result.dy);
}
......@@ -547,11 +550,11 @@ void main() {
testWidgets('getText', (WidgetTester tester) 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 {
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) {
return null;
}
......@@ -609,7 +612,7 @@ void main() {
// Check if error thrown for other types
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['response'], contains('is currently not supported by getText'));
});
......@@ -617,7 +620,7 @@ void main() {
testWidgets('descendant finder', (WidgetTester tester) 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 {
final Map<String, String> arguments = GetText(Descendant(
......@@ -625,7 +628,7 @@ void main() {
matching: ByValueKey('text2'),
matchRoot: matchRoot,
), 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) {
return null;
}
......@@ -662,7 +665,7 @@ void main() {
testWidgets('descendant finder firstMatchOnly', (WidgetTester tester) async {
await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<String> getDescendantText() async {
final Map<String, String> arguments = GetText(Descendant(
......@@ -670,7 +673,7 @@ void main() {
matching: const ByType('Text'),
firstMatchOnly: true,
), 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) {
return null;
}
......@@ -696,7 +699,7 @@ void main() {
testWidgets('ancestor finder', (WidgetTester tester) 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 {
final Map<String, String> arguments = GetOffset(Ancestor(
......@@ -704,7 +707,7 @@ void main() {
matching: ByValueKey(matching),
matchRoot: matchRoot,
), 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) {
return null;
}
......@@ -766,7 +769,7 @@ void main() {
testWidgets('ancestor finder firstMatchOnly', (WidgetTester tester) async {
await silenceDriverLogger(() async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
final FlutterDriverExtension driverExtension = FlutterDriverExtension((String arg) async => '', true);
Future<Offset> getAncestorTopLeft() async {
final Map<String, String> arguments = GetOffset(Ancestor(
......@@ -774,7 +777,7 @@ void main() {
matching: const ByType('Container'),
firstMatchOnly: true,
), 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) {
return null;
}
......@@ -814,11 +817,11 @@ void main() {
});
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 {
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>);
return result.json;
}
......@@ -879,18 +882,120 @@ void main() {
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', () {
FlutterDriverExtension extension;
FlutterDriverExtension driverExtension;
Map<String, dynamic> result;
setUp(() {
extension = FlutterDriverExtension((String arg) async => '', true);
driverExtension = FlutterDriverExtension((String arg) async => '', true);
result = null;
});
testWidgets('returns immediately when frame is synced', (
WidgetTester tester) async {
extension.call(const WaitUntilNoPendingFrame().serialize())
driverExtension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
......@@ -911,7 +1016,7 @@ void main() {
// 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) {
result = r;
}));
......@@ -935,7 +1040,7 @@ void main() {
'waits until no pending scheduled frame', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrame();
extension.call(const WaitUntilNoPendingFrame().serialize())
driverExtension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> 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.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