Unverified Commit 7dd265ff authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add getSemanticsId command to flutter_driver (#19047)

parent 41646c95
......@@ -252,3 +252,46 @@ class ByType extends SerializableFinder {
return new ByType(json['type']);
}
}
/// A Flutter driver command that retrieves a semantics id using a specified finder.
///
/// This command requires assertions to be enabled on the device.
///
/// If the object returned by the finder does not have its own semantics node,
/// then the semantics node of the first ancestor is returned instead.
///
/// Throws an error if a finder returns multiple objects or if there are no
/// semantics nodes.
///
/// Semantics must be enabled to use this method, either using a platform
/// specific shell command or [FlutterDriver.setSemantics].
class GetSemanticsId extends CommandWithTarget {
/// Creates a command which finds a Widget and then looks up the semantic id.
GetSemanticsId(SerializableFinder finder, {Duration timeout}) : super(finder, timeout: timeout);
/// Creates a command from a json map.
GetSemanticsId.deserialize(Map<String, String> json)
: super.deserialize(json);
@override
String get kind => 'get_semantics_id';
}
/// The result of a [GetSemanticsId] command.
class GetSemanticsIdResult extends Result {
/// Creates a new [GetSemanticsId] result.
GetSemanticsIdResult(this.id);
/// The semantics id of the node;
final int id;
/// Deserializes this result from JSON.
static GetSemanticsIdResult fromJson(Map<String, dynamic> json) {
return new GetSemanticsIdResult(json['id']);
}
@override
Map<String, dynamic> toJson() => <String, dynamic>{'id': id};
}
......@@ -571,6 +571,20 @@ class FlutterDriver {
return result.changedState;
}
/// Retrieves the semantics node id for the object returned by `finder`, or
/// the nearest ancestor with a semantics node.
///
/// Throws an error if `finder` returns multiple elements or a semantics
/// node is not found.
///
/// Semantics must be enabled to use this method, either using a platform
/// specific shell command or [setSemantics].
Future<int> getSemanticsId(SerializableFinder finder, { Duration timeout = _kShortTimeout}) async {
final Map<String, dynamic> jsonResponse = await _sendCommand(new GetSemanticsId(finder, timeout: timeout));
final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse);
return result.id;
}
/// Take a screenshot. The image will be returned as a PNG.
Future<List<int>> screenshot({ Duration timeout }) async {
timeout ??= _kLongTimeout;
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:flutter/semantics.dart';
import 'package:meta/meta.dart';
import 'package:flutter/foundation.dart';
......@@ -105,6 +106,7 @@ class FlutterDriverExtension {
'waitFor': _waitFor,
'waitForAbsent': _waitForAbsent,
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
'get_semantics_id': _getSemanticsId,
});
_commandDeserializers.addAll(<String, CommandDeserializerCallback>{
......@@ -122,6 +124,7 @@ class FlutterDriverExtension {
'waitFor': (Map<String, String> params) => new WaitFor.deserialize(params),
'waitForAbsent': (Map<String, String> params) => new WaitForAbsent.deserialize(params),
'waitUntilNoTransientCallbacks': (Map<String, String> params) => new WaitUntilNoTransientCallbacks.deserialize(params),
'get_semantics_id': (Map<String, String> params) => new GetSemanticsId.deserialize(params),
});
_finders.addAll(<String, FinderConstructor>{
......@@ -298,6 +301,21 @@ class FlutterDriverExtension {
await _waitUntilFrame(() => SchedulerBinding.instance.transientCallbackCount == 0);
}
Future<GetSemanticsIdResult> _getSemanticsId(Command command) async {
final GetSemanticsId semanticsCommand = command;
final Finder target = await _waitForElement(_createFinder(semanticsCommand.finder));
final Element element = target.evaluate().single;
RenderObject renderObject = element.renderObject;
SemanticsNode node;
while (renderObject != null && node == null) {
node = renderObject.debugSemantics;
renderObject = renderObject.parent;
}
if (node == null)
throw new StateError('No semantics data found');
return new GetSemanticsIdResult(node.id);
}
Future<ScrollResult> _scroll(Command command) async {
final Scroll scrollCommand = command;
final Finder target = await _waitForElement(_createFinder(scrollCommand.finder));
......
......@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/src/common/find.dart';
import 'package:flutter_driver/src/common/request_data.dart';
import 'package:flutter_driver/src/extension/extension.dart';
......@@ -68,4 +70,54 @@ void main() {
expect(result.message, '1');
});
});
group('getSemanticsId', () {
FlutterDriverExtension extension;
setUp(() {
extension = new FlutterDriverExtension((String arg) async {});
});
testWidgets('works when semantics are enabled', (WidgetTester tester) async {
final SemanticsHandle semantics = RendererBinding.instance.pipelineOwner.ensureSemantics();
await tester.pumpWidget(
const Text('hello', textDirection: TextDirection.ltr));
final Map<String, Object> arguments = new GetSemanticsId(new ByText('hello')).serialize();
final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson((await extension.call(arguments))['response']);
expect(result.id, 1);
semantics.dispose();
});
testWidgets('throws state error if no data is found', (WidgetTester tester) async {
await tester.pumpWidget(
const Text('hello', textDirection: TextDirection.ltr));
final Map<String, Object> arguments = new GetSemanticsId(new ByText('hello')).serialize();
final Map<String, Object> response = await extension.call(arguments);
expect(response['isError'], true);
expect(response['response'], contains('Bad state: No semantics data found'));
});
testWidgets('throws state error multiple matches are found', (WidgetTester tester) async {
final SemanticsHandle semantics = RendererBinding.instance.pipelineOwner.ensureSemantics();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(children: const <Widget>[
const SizedBox(width: 100.0, height: 100.0, child: const Text('hello')),
const SizedBox(width: 100.0, height: 100.0, child: const Text('hello')),
]),
),
);
final Map<String, Object> arguments = new GetSemanticsId(new ByText('hello')).serialize();
final Map<String, Object> response = await extension.call(arguments);
expect(response['isError'], true);
expect(response['response'], contains('Bad state: Too many elements'));
semantics.dispose();
});
});
}
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