mirror of
https://github.com/flutter/flutter
synced 2024-09-19 16:21:58 +00:00
[driver] refactor API to finder objects (#3365)
This commit is contained in:
parent
a91bc0ba9c
commit
9ce995f65e
|
@ -22,7 +22,7 @@ void main() {
|
|||
test('measure', () async {
|
||||
Timeline timeline = await driver.traceAction(() async {
|
||||
// Find the scrollable stock list
|
||||
ObjectRef stockList = await driver.findByValueKey('main-scroll');
|
||||
SerializableFinder stockList = find.byValueKey('main-scroll');
|
||||
expect(stockList, isNotNull);
|
||||
|
||||
// Scroll down
|
||||
|
|
|
@ -22,12 +22,12 @@ void main() {
|
|||
test('measure', () async {
|
||||
Timeline timeline = await driver.traceAction(() async {
|
||||
// Find the scrollable stock list
|
||||
ObjectRef stockList = await driver.findByValueKey('Gallery List');
|
||||
SerializableFinder stockList = find.byValueKey('Gallery List');
|
||||
expect(stockList, isNotNull);
|
||||
|
||||
await driver.tap(await driver.findByText('Demos'));
|
||||
await driver.tap(await driver.findByText('Components'));
|
||||
await driver.tap(await driver.findByText('Style'));
|
||||
await driver.tap(find.text('Demos'));
|
||||
await driver.tap(find.text('Components'));
|
||||
await driver.tap(find.text('Style'));
|
||||
|
||||
// TODO(eseidel): These are very artifical scrolls, we should use better
|
||||
// https://github.com/flutter/flutter/issues/3316
|
||||
|
|
|
@ -23,7 +23,7 @@ void main() {
|
|||
test('measure', () async {
|
||||
Timeline timeline = await driver.traceAction(() async {
|
||||
// Find the scrollable stock list
|
||||
ObjectRef stockList = await driver.findByValueKey('stock-list');
|
||||
SerializableFinder stockList = find.byValueKey('stock-list');
|
||||
expect(stockList, isNotNull);
|
||||
|
||||
// Scroll down
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
library flutter_driver;
|
||||
|
||||
export 'src/driver.dart' show
|
||||
find,
|
||||
CommonFinders,
|
||||
EvaluatorFunction,
|
||||
FlutterDriver;
|
||||
|
||||
export 'src/error.dart' show
|
||||
|
@ -21,7 +24,7 @@ export 'src/error.dart' show
|
|||
flutterDriverLog;
|
||||
|
||||
export 'src/find.dart' show
|
||||
ObjectRef,
|
||||
SerializableFinder,
|
||||
GetTextResult;
|
||||
|
||||
export 'src/health.dart' show
|
||||
|
@ -31,7 +34,6 @@ export 'src/health.dart' show
|
|||
export 'src/message.dart' show
|
||||
Message,
|
||||
Command,
|
||||
ObjectRef,
|
||||
CommandWithTarget,
|
||||
Result;
|
||||
|
||||
|
|
|
@ -20,6 +20,14 @@ import 'timeline.dart';
|
|||
|
||||
final Logger _log = new Logger('FlutterDriver');
|
||||
|
||||
/// A convenient accessor to frequently used finders.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// driver.tap(find.byText('Save'));
|
||||
/// driver.scroll(find.byValueKey(42));
|
||||
const CommonFinders find = const CommonFinders._();
|
||||
|
||||
/// Computes a value.
|
||||
///
|
||||
/// If computation is asynchronous, the function may return a [Future].
|
||||
|
@ -162,14 +170,15 @@ class FlutterDriver {
|
|||
Future<Map<String, dynamic>> _sendCommand(Command command) async {
|
||||
Map<String, String> parameters = <String, String>{'command': command.kind}
|
||||
..addAll(command.serialize());
|
||||
return _appIsolate.invokeExtension(_kFlutterExtensionMethod, parameters)
|
||||
.then((Map<String, dynamic> result) => result, onError: (dynamic error, dynamic stackTrace) {
|
||||
throw new DriverError(
|
||||
'Failed to fulfill ${command.runtimeType} due to remote error',
|
||||
error,
|
||||
stackTrace
|
||||
);
|
||||
});
|
||||
try {
|
||||
return await _appIsolate.invokeExtension(_kFlutterExtensionMethod, parameters);
|
||||
} catch (error, stackTrace) {
|
||||
throw new DriverError(
|
||||
'Failed to fulfill ${command.runtimeType} due to remote error',
|
||||
error,
|
||||
stackTrace
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the status of the Flutter Driver extension.
|
||||
|
@ -177,23 +186,14 @@ class FlutterDriver {
|
|||
return Health.fromJson(await _sendCommand(new GetHealth()));
|
||||
}
|
||||
|
||||
/// Finds the UI element with the given [key].
|
||||
Future<ObjectRef> findByValueKey(dynamic key) async {
|
||||
return ObjectRef.fromJson(await _sendCommand(new Find(new ByValueKey(key))));
|
||||
/// Taps at the center of the widget located by [finder].
|
||||
Future<Null> tap(SerializableFinder finder) async {
|
||||
return await _sendCommand(new Tap(finder)).then((Map<String, dynamic> _) => null);
|
||||
}
|
||||
|
||||
/// Finds the UI element for the tooltip with the given [message].
|
||||
Future<ObjectRef> findByTooltipMessage(String message) async {
|
||||
return ObjectRef.fromJson(await _sendCommand(new Find(new ByTooltipMessage(message))));
|
||||
}
|
||||
|
||||
/// Finds the text element with the given [text].
|
||||
Future<ObjectRef> findByText(String text) async {
|
||||
return ObjectRef.fromJson(await _sendCommand(new Find(new ByText(text))));
|
||||
}
|
||||
|
||||
Future<Null> tap(ObjectRef ref) async {
|
||||
return await _sendCommand(new Tap(ref)).then((Map<String, dynamic> _) => null);
|
||||
/// Whether at least one widget identified by [finder] exists on the UI.
|
||||
Future<bool> exists(SerializableFinder finder) async {
|
||||
return await _sendCommand(new Exists(finder)).then((Map<String, dynamic> _) => null);
|
||||
}
|
||||
|
||||
/// Tell the driver to perform a scrolling action.
|
||||
|
@ -209,13 +209,13 @@ class FlutterDriver {
|
|||
///
|
||||
/// The move events are generated at a given [frequency] in Hz (or events per
|
||||
/// second). It defaults to 60Hz.
|
||||
Future<Null> scroll(ObjectRef ref, double dx, double dy, Duration duration, {int frequency: 60}) async {
|
||||
return await _sendCommand(new Scroll(ref, dx, dy, duration, frequency)).then((Map<String, dynamic> _) => null);
|
||||
Future<Null> scroll(SerializableFinder finder, double dx, double dy, Duration duration, {int frequency: 60}) async {
|
||||
return await _sendCommand(new Scroll(finder, dx, dy, duration, frequency)).then((Map<String, dynamic> _) => null);
|
||||
}
|
||||
|
||||
Future<String> getText(ObjectRef ref) async {
|
||||
GetTextResult result = GetTextResult.fromJson(await _sendCommand(new GetText(ref)));
|
||||
return result.text;
|
||||
/// Returns the text in the `Text` widget located by [finder].
|
||||
Future<String> getText(SerializableFinder finder) async {
|
||||
return GetTextResult.fromJson(await _sendCommand(new GetText(finder))).text;
|
||||
}
|
||||
|
||||
/// Starts recording performance traces.
|
||||
|
@ -358,3 +358,17 @@ Future<VMServiceClientConnection> _waitAndConnect(String url) async {
|
|||
|
||||
return attemptConnection();
|
||||
}
|
||||
|
||||
/// Provides convenient accessors to frequently used finders.
|
||||
class CommonFinders {
|
||||
const CommonFinders._();
|
||||
|
||||
/// Finds [Text] widgets containing string equal to [text].
|
||||
SerializableFinder text(String text) => new ByText(text);
|
||||
|
||||
/// Finds widgets by [key].
|
||||
SerializableFinder byValueKey(dynamic key) => new ByValueKey(key);
|
||||
|
||||
/// Finds widgets with a tooltip with the given [message].
|
||||
SerializableFinder byTooltip(String message) => new ByTooltipMessage(message);
|
||||
}
|
||||
|
|
|
@ -47,34 +47,39 @@ typedef Future<Result> CommandHandlerCallback(Command c);
|
|||
/// Deserializes JSON map to a command object.
|
||||
typedef Command CommandDeserializerCallback(Map<String, String> params);
|
||||
|
||||
/// Runs the finder and returns the [Element] found, or `null`.
|
||||
typedef Future<Element> FinderCallback(SerializableFinder finder);
|
||||
|
||||
class FlutterDriverExtension {
|
||||
static final Logger _log = new Logger('FlutterDriverExtension');
|
||||
|
||||
FlutterDriverExtension() {
|
||||
_commandHandlers = {
|
||||
_commandHandlers = <String, CommandHandlerCallback>{
|
||||
'get_health': getHealth,
|
||||
'find': find,
|
||||
'tap': tap,
|
||||
'get_text': getText,
|
||||
'scroll': scroll,
|
||||
};
|
||||
|
||||
_commandDeserializers = {
|
||||
_commandDeserializers = <String, CommandDeserializerCallback>{
|
||||
'get_health': GetHealth.deserialize,
|
||||
'find': Find.deserialize,
|
||||
'tap': Tap.deserialize,
|
||||
'get_text': GetText.deserialize,
|
||||
'scroll': Scroll.deserialize,
|
||||
};
|
||||
|
||||
_finders = <String, FinderCallback>{
|
||||
'ByValueKey': _findByValueKey,
|
||||
'ByTooltipMessage': _findByTooltipMessage,
|
||||
'ByText': _findByText,
|
||||
};
|
||||
}
|
||||
|
||||
final Instrumentation prober = new Instrumentation();
|
||||
|
||||
Map<String, CommandHandlerCallback> _commandHandlers =
|
||||
<String, CommandHandlerCallback>{};
|
||||
|
||||
Map<String, CommandDeserializerCallback> _commandDeserializers =
|
||||
<String, CommandDeserializerCallback>{};
|
||||
Map<String, CommandHandlerCallback> _commandHandlers;
|
||||
Map<String, CommandDeserializerCallback> _commandDeserializers;
|
||||
Map<String, FinderCallback> _finders;
|
||||
|
||||
Future<ServiceExtensionResponse> call(Map<String, String> params) async {
|
||||
try {
|
||||
|
@ -107,36 +112,18 @@ class FlutterDriverExtension {
|
|||
|
||||
Future<Health> getHealth(GetHealth command) async => new Health(HealthStatus.ok);
|
||||
|
||||
Future<ObjectRef> find(Find command) async {
|
||||
SearchSpecification searchSpec = command.searchSpec;
|
||||
switch(searchSpec.runtimeType) {
|
||||
case ByValueKey: return findByValueKey(searchSpec);
|
||||
case ByTooltipMessage: return findByTooltipMessage(searchSpec);
|
||||
case ByText: return findByText(searchSpec);
|
||||
}
|
||||
throw new DriverError('Unsupported search specification type ${searchSpec.runtimeType}');
|
||||
}
|
||||
|
||||
/// Runs object [locator] repeatedly until it returns a non-`null` value.
|
||||
///
|
||||
/// [descriptionGetter] describes the object to be waited for. It is used in
|
||||
/// the warning printed should timeout happen.
|
||||
Future<ObjectRef> _waitForObject(String descriptionGetter(), Object locator()) async {
|
||||
Object object = await retry(locator, _kDefaultTimeout, _kDefaultPauseBetweenRetries, predicate: (Object object) {
|
||||
/// Runs object [finder] repeatedly until it finds an [Element].
|
||||
Future<Element> _waitForElement(String descriptionGetter(), Element locator()) {
|
||||
return retry(locator, _kDefaultTimeout, _kDefaultPauseBetweenRetries, predicate: (dynamic object) {
|
||||
return object != null;
|
||||
}).catchError((Object error, Object stackTrace) {
|
||||
_log.warning('Timed out waiting for ${descriptionGetter()}');
|
||||
return null;
|
||||
});
|
||||
|
||||
ObjectRef elemRef = object != null
|
||||
? new ObjectRef(_registerObject(object))
|
||||
: new ObjectRef.notFound();
|
||||
return new Future<ObjectRef>.value(elemRef);
|
||||
}
|
||||
|
||||
Future<ObjectRef> findByValueKey(ByValueKey byKey) async {
|
||||
return _waitForObject(
|
||||
Future<Element> _findByValueKey(ByValueKey byKey) async {
|
||||
return _waitForElement(
|
||||
() => 'element with key "${byKey.keyValue}" of type ${byKey.keyValueType}',
|
||||
() {
|
||||
return prober.findElementByKey(new ValueKey<dynamic>(byKey.keyValue));
|
||||
|
@ -144,8 +131,8 @@ class FlutterDriverExtension {
|
|||
);
|
||||
}
|
||||
|
||||
Future<ObjectRef> findByTooltipMessage(ByTooltipMessage byTooltipMessage) async {
|
||||
return _waitForObject(
|
||||
Future<Element> _findByTooltipMessage(ByTooltipMessage byTooltipMessage) async {
|
||||
return _waitForElement(
|
||||
() => 'tooltip with message "${byTooltipMessage.text}" on it',
|
||||
() {
|
||||
return prober.findElement((Element element) {
|
||||
|
@ -160,22 +147,31 @@ class FlutterDriverExtension {
|
|||
);
|
||||
}
|
||||
|
||||
Future<ObjectRef> findByText(ByText byText) async {
|
||||
return await _waitForObject(
|
||||
Future<Element> _findByText(ByText byText) async {
|
||||
return await _waitForElement(
|
||||
() => 'text "${byText.text}"',
|
||||
() {
|
||||
return prober.findText(byText.text);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Element> _runFinder(SerializableFinder finder) {
|
||||
FinderCallback cb = _finders[finder.finderType];
|
||||
|
||||
if (cb == null)
|
||||
throw 'Unsupported finder type: ${finder.finderType}';
|
||||
|
||||
return cb(finder);
|
||||
}
|
||||
|
||||
Future<TapResult> tap(Tap command) async {
|
||||
Element target = await _dereferenceOrDie(command.targetRef);
|
||||
Element target = await _runFinder(command.finder);
|
||||
prober.tap(target);
|
||||
return new TapResult();
|
||||
}
|
||||
|
||||
Future<ScrollResult> scroll(Scroll command) async {
|
||||
Element target = await _dereferenceOrDie(command.targetRef);
|
||||
Element target = await _runFinder(command.finder);
|
||||
final int totalMoves = command.duration.inMicroseconds * command.frequency ~/ Duration.MICROSECONDS_PER_SECOND;
|
||||
Offset delta = new Offset(command.dx, command.dy) / totalMoves.toDouble();
|
||||
Duration pause = command.duration ~/ totalMoves;
|
||||
|
@ -198,30 +194,9 @@ class FlutterDriverExtension {
|
|||
}
|
||||
|
||||
Future<GetTextResult> getText(GetText command) async {
|
||||
Element target = await _dereferenceOrDie(command.targetRef);
|
||||
Element target = await _runFinder(command.finder);
|
||||
// TODO(yjbanov): support more ways to read text
|
||||
Text text = target.widget;
|
||||
return new GetTextResult(text.data);
|
||||
}
|
||||
|
||||
int _refCounter = 1;
|
||||
final Map<String, Object> _objectRefs = <String, Object>{};
|
||||
String _registerObject(Object obj) {
|
||||
if (obj == null)
|
||||
throw new ArgumentError('Cannot register null object');
|
||||
String refKey = '${_refCounter++}';
|
||||
_objectRefs[refKey] = obj;
|
||||
return refKey;
|
||||
}
|
||||
|
||||
dynamic _dereference(String reference) => _objectRefs[reference];
|
||||
|
||||
Future<dynamic> _dereferenceOrDie(String reference) {
|
||||
Element object = _dereference(reference);
|
||||
|
||||
if (object == null)
|
||||
return new Future<String>.error('Object reference not found ($reference).');
|
||||
|
||||
return new Future<Element>.value(object);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,46 +11,86 @@ DriverError _createInvalidKeyValueTypeError(String invalidType) {
|
|||
return new DriverError('Unsupported key value type $invalidType. Flutter Driver only supports ${_supportedKeyValueTypes.join(", ")}');
|
||||
}
|
||||
|
||||
/// Command to find an element.
|
||||
class Find extends Command {
|
||||
@override
|
||||
final String kind = 'find';
|
||||
|
||||
Find(this.searchSpec);
|
||||
|
||||
final SearchSpecification searchSpec;
|
||||
|
||||
@override
|
||||
Map<String, String> serialize() => searchSpec.serialize();
|
||||
|
||||
static Find deserialize(Map<String, String> json) {
|
||||
return new Find(SearchSpecification.deserialize(json));
|
||||
/// A command aimed at an object to be located by [finder].
|
||||
///
|
||||
/// Implementations must provide a concrete [kind]. If additional data is
|
||||
/// required beyond the [finder] the implementation may override [serialize]
|
||||
/// and add more keys to the returned map.
|
||||
abstract class CommandWithTarget extends Command {
|
||||
CommandWithTarget(this.finder) {
|
||||
if (finder == null)
|
||||
throw new DriverError('${this.runtimeType} target cannot be null');
|
||||
}
|
||||
|
||||
/// Locates the object or objects targeted by this command.
|
||||
final SerializableFinder finder;
|
||||
|
||||
/// This method is meant to be overridden if data in addition to [finder]
|
||||
/// is serialized to JSON.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// Map<String, String> toJson() => super.toJson()..addAll({
|
||||
/// 'foo': this.foo,
|
||||
/// });
|
||||
@override
|
||||
Map<String, String> serialize() => finder.serialize();
|
||||
}
|
||||
|
||||
/// Checks if the widget identified by the given finder exists.
|
||||
class Exists extends CommandWithTarget {
|
||||
@override
|
||||
final String kind = 'exists';
|
||||
|
||||
Exists(SerializableFinder finder) : super(finder);
|
||||
|
||||
static Exists deserialize(Map<String, String> json) {
|
||||
return new Exists(SerializableFinder.deserialize(json));
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> serialize() => super.serialize();
|
||||
}
|
||||
|
||||
class ExistsResult extends Result {
|
||||
ExistsResult(this.exists);
|
||||
|
||||
static ExistsResult fromJson(Map<String, dynamic> json) {
|
||||
return new ExistsResult(json['exists']);
|
||||
}
|
||||
|
||||
/// Whether the widget was found on the UI or not.
|
||||
final bool exists;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'exists': exists,
|
||||
};
|
||||
}
|
||||
|
||||
/// Describes how to the driver should search for elements.
|
||||
abstract class SearchSpecification {
|
||||
String get searchSpecType;
|
||||
abstract class SerializableFinder {
|
||||
String get finderType;
|
||||
|
||||
static SearchSpecification deserialize(Map<String, String> json) {
|
||||
String searchSpecType = json['searchSpecType'];
|
||||
switch(searchSpecType) {
|
||||
static SerializableFinder deserialize(Map<String, String> json) {
|
||||
String finderType = json['finderType'];
|
||||
switch(finderType) {
|
||||
case 'ByValueKey': return ByValueKey.deserialize(json);
|
||||
case 'ByTooltipMessage': return ByTooltipMessage.deserialize(json);
|
||||
case 'ByText': return ByText.deserialize(json);
|
||||
}
|
||||
throw new DriverError('Unsupported search specification type $searchSpecType');
|
||||
throw new DriverError('Unsupported search specification type $finderType');
|
||||
}
|
||||
|
||||
Map<String, String> serialize() => {
|
||||
'searchSpecType': searchSpecType,
|
||||
'finderType': finderType,
|
||||
};
|
||||
}
|
||||
|
||||
/// Tells [Find] to search by tooltip text.
|
||||
class ByTooltipMessage extends SearchSpecification {
|
||||
/// Finds widgets by tooltip text.
|
||||
class ByTooltipMessage extends SerializableFinder {
|
||||
@override
|
||||
final String searchSpecType = 'ByTooltipMessage';
|
||||
final String finderType = 'ByTooltipMessage';
|
||||
|
||||
ByTooltipMessage(this.text);
|
||||
|
||||
|
@ -67,10 +107,10 @@ class ByTooltipMessage extends SearchSpecification {
|
|||
}
|
||||
}
|
||||
|
||||
/// Tells [Find] to search for `Text` widget by text.
|
||||
class ByText extends SearchSpecification {
|
||||
/// Finds widgets by [text] inside a `Text` widget.
|
||||
class ByText extends SerializableFinder {
|
||||
@override
|
||||
final String searchSpecType = 'ByText';
|
||||
final String finderType = 'ByText';
|
||||
|
||||
ByText(this.text);
|
||||
|
||||
|
@ -86,10 +126,10 @@ class ByText extends SearchSpecification {
|
|||
}
|
||||
}
|
||||
|
||||
/// Tells [Find] to search by `ValueKey`.
|
||||
class ByValueKey extends SearchSpecification {
|
||||
/// Finds widgets by `ValueKey`.
|
||||
class ByValueKey extends SerializableFinder {
|
||||
@override
|
||||
final String searchSpecType = 'ByValueKey';
|
||||
final String finderType = 'ByValueKey';
|
||||
|
||||
ByValueKey(dynamic keyValue)
|
||||
: this.keyValue = keyValue,
|
||||
|
@ -132,14 +172,14 @@ class ByValueKey extends SearchSpecification {
|
|||
|
||||
/// Command to read the text from a given element.
|
||||
class GetText extends CommandWithTarget {
|
||||
/// [targetRef] identifies an element that contains a piece of text.
|
||||
GetText(ObjectRef targetRef) : super(targetRef);
|
||||
/// [finder] looks for an element that contains a piece of text.
|
||||
GetText(SerializableFinder finder) : super(finder);
|
||||
|
||||
@override
|
||||
final String kind = 'get_text';
|
||||
|
||||
static GetText deserialize(Map<String, String> json) {
|
||||
return new GetText(new ObjectRef(json['targetRef']));
|
||||
return new GetText(SerializableFinder.deserialize(json));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'message.dart';
|
||||
import 'find.dart';
|
||||
|
||||
class Tap extends CommandWithTarget {
|
||||
@override
|
||||
final String kind = 'tap';
|
||||
|
||||
Tap(ObjectRef targetRef) : super(targetRef);
|
||||
Tap(SerializableFinder finder) : super(finder);
|
||||
|
||||
static Tap deserialize(Map<String, String> json) {
|
||||
return new Tap(new ObjectRef(json['targetRef']));
|
||||
return new Tap(SerializableFinder.deserialize(json));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -34,16 +35,16 @@ class Scroll extends CommandWithTarget {
|
|||
final String kind = 'scroll';
|
||||
|
||||
Scroll(
|
||||
ObjectRef targetRef,
|
||||
SerializableFinder finder,
|
||||
this.dx,
|
||||
this.dy,
|
||||
this.duration,
|
||||
this.frequency
|
||||
) : super(targetRef);
|
||||
) : super(finder);
|
||||
|
||||
static Scroll deserialize(Map<String, dynamic> json) {
|
||||
return new Scroll(
|
||||
new ObjectRef(json['targetRef']),
|
||||
SerializableFinder.deserialize(json),
|
||||
double.parse(json['dx']),
|
||||
double.parse(json['dy']),
|
||||
new Duration(microseconds: int.parse(json['duration'])),
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'error.dart';
|
||||
|
||||
/// An object sent from the Flutter Driver to a Flutter application to instruct
|
||||
/// the application to perform a task.
|
||||
abstract class Command {
|
||||
|
@ -20,58 +18,3 @@ abstract class Result { // ignore: one_member_abstracts
|
|||
/// Serializes this message to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
}
|
||||
|
||||
/// A serializable reference to an object that lives in the application isolate.
|
||||
class ObjectRef extends Result {
|
||||
ObjectRef(this.objectReferenceKey);
|
||||
|
||||
ObjectRef.notFound() : this(null);
|
||||
|
||||
static ObjectRef fromJson(Map<String, dynamic> json) {
|
||||
return json['objectReferenceKey'] != null
|
||||
? new ObjectRef(json['objectReferenceKey'])
|
||||
: null;
|
||||
}
|
||||
|
||||
/// Identifier used to dereference an object.
|
||||
///
|
||||
/// This value is generated by the application-side isolate. Flutter driver
|
||||
/// tests should not generate these keys.
|
||||
final String objectReferenceKey;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'objectReferenceKey': objectReferenceKey,
|
||||
};
|
||||
}
|
||||
|
||||
/// A command aimed at an object represented by [targetRef].
|
||||
///
|
||||
/// Implementations must provide a concrete [kind]. If additional data is
|
||||
/// required beyond the [targetRef] the implementation may override [serialize]
|
||||
/// and add more keys to the returned map.
|
||||
abstract class CommandWithTarget extends Command {
|
||||
CommandWithTarget(ObjectRef ref) : this.targetRef = ref?.objectReferenceKey {
|
||||
if (ref == null)
|
||||
throw new DriverError('${this.runtimeType} target cannot be null');
|
||||
|
||||
if (ref.objectReferenceKey == null)
|
||||
throw new DriverError('${this.runtimeType} target reference cannot be null');
|
||||
}
|
||||
|
||||
/// Refers to the object targeted by this command.
|
||||
final String targetRef;
|
||||
|
||||
/// This method is meant to be overridden if data in addition to [targetRef]
|
||||
/// is serialized to JSON.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// Map<String, String> toJson() => super.toJson()..addAll({
|
||||
/// 'foo': this.foo,
|
||||
/// });
|
||||
@override
|
||||
Map<String, String> serialize() => <String, String>{
|
||||
'targetRef': targetRef,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import 'dart:async';
|
|||
import 'package:flutter_driver/src/driver.dart';
|
||||
import 'package:flutter_driver/src/error.dart';
|
||||
import 'package:flutter_driver/src/health.dart';
|
||||
import 'package:flutter_driver/src/message.dart';
|
||||
import 'package:flutter_driver/src/timeline.dart';
|
||||
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
@ -120,27 +119,23 @@ void main() {
|
|||
await driver.close();
|
||||
});
|
||||
|
||||
group('findByValueKey', () {
|
||||
group('ByValueKey', () {
|
||||
test('restricts value types', () async {
|
||||
expect(driver.findByValueKey(null),
|
||||
expect(() => find.byValueKey(null),
|
||||
throwsA(new isInstanceOf<DriverError>()));
|
||||
});
|
||||
|
||||
test('finds by ValueKey', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], {
|
||||
'command': 'find',
|
||||
'searchSpecType': 'ByValueKey',
|
||||
'command': 'tap',
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': 'foo',
|
||||
'keyValueType': 'String'
|
||||
});
|
||||
return new Future<Map<String, dynamic>>.value(<String, dynamic>{
|
||||
'objectReferenceKey': '123',
|
||||
});
|
||||
return new Future<Null>.value();
|
||||
});
|
||||
ObjectRef result = await driver.findByValueKey('foo');
|
||||
expect(result, isNotNull);
|
||||
expect(result.objectReferenceKey, '123');
|
||||
await driver.tap(find.byValueKey('foo'));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -149,20 +144,16 @@ void main() {
|
|||
expect(driver.tap(null), throwsA(new isInstanceOf<DriverError>()));
|
||||
});
|
||||
|
||||
test('requires a valid target reference', () async {
|
||||
expect(driver.tap(new ObjectRef.notFound()),
|
||||
throwsA(new isInstanceOf<DriverError>()));
|
||||
});
|
||||
|
||||
test('sends the tap command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'tap',
|
||||
'targetRef': '123'
|
||||
'finderType': 'ByText',
|
||||
'text': 'foo',
|
||||
});
|
||||
return new Future<Map<String, dynamic>>.value();
|
||||
});
|
||||
await driver.tap(new ObjectRef('123'));
|
||||
await driver.tap(find.text('foo'));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -171,22 +162,19 @@ void main() {
|
|||
expect(driver.getText(null), throwsA(new isInstanceOf<DriverError>()));
|
||||
});
|
||||
|
||||
test('requires a valid target reference', () async {
|
||||
expect(driver.getText(new ObjectRef.notFound()),
|
||||
throwsA(new isInstanceOf<DriverError>()));
|
||||
});
|
||||
|
||||
test('sends the getText command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'get_text',
|
||||
'targetRef': '123'
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': '123',
|
||||
'keyValueType': 'int'
|
||||
});
|
||||
return new Future<Map<String, dynamic>>.value({
|
||||
'text': 'hello'
|
||||
});
|
||||
});
|
||||
String result = await driver.getText(new ObjectRef('123'));
|
||||
String result = await driver.getText(find.byValueKey(123));
|
||||
expect(result, 'hello');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,14 +23,14 @@ void main() {
|
|||
|
||||
test('tap on the floating action button; verify counter', () async {
|
||||
// Find floating action button (fab) to tap on
|
||||
ObjectRef fab = await driver.findByTooltipMessage('Increment');
|
||||
expect(fab, isNotNull);
|
||||
SerializableFinder fab = find.byTooltip('Increment');
|
||||
expect(await driver.exists(fab), isTrue);
|
||||
|
||||
// Tap on the fab
|
||||
await driver.tap(fab);
|
||||
|
||||
// Wait for text to change to the desired value
|
||||
expect(await driver.findByText('Button tapped 1 time.'), isNotNull);
|
||||
expect(await driver.exists(find.text('Button tapped 1 time.')), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue