Unverified Commit 4f5d9013 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Add Driver command to get diagnostics tree (#34440)

parent b106fd0d
// Copyright 2019 The Chromium 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 'enum_util.dart';
import 'find.dart';
import 'message.dart';
/// [DiagnosticsNode] tree types that can be requested by [GetDiagnosticsTree].
enum DiagnosticsType {
/// The [DiagnosticsNode] tree formed by [RenderObject]s.
renderObject,
/// The [DiagnosticsNode] tree formed by [Widget]s.
widget,
}
EnumIndex<DiagnosticsType> _diagnosticsTypeIndex = EnumIndex<DiagnosticsType>(DiagnosticsType.values);
/// A Flutter Driver command to retrieve the json-serialized [DiagnosticsNode]
/// tree of the object identified by [finder].
///
/// The [DiagnosticsType] of the [DiagnosticsNode] tree returned is specified by
/// [diagnosticsType].
class GetDiagnosticsTree extends CommandWithTarget {
/// Creates a [GetDiagnosticsTree] Flutter Driver command.
GetDiagnosticsTree(SerializableFinder finder, this.diagnosticsType, {
this.subtreeDepth = 0,
this.includeProperties = true,
Duration timeout,
}) : assert(subtreeDepth != null),
assert(includeProperties != null),
super(finder, timeout: timeout);
/// Deserializes this command from the value generated by [serialize].
GetDiagnosticsTree.deserialize(Map<String, dynamic> json)
: subtreeDepth = int.parse(json['subtreeDepth']),
includeProperties = json['includeProperties'] == 'true',
diagnosticsType = _diagnosticsTypeIndex.lookupBySimpleName(json['diagnosticsType']),
super.deserialize(json);
/// How many levels of children to include in the JSON result.
///
/// Defaults to zero, which will only return the [DiagnosticsNode] information
/// of the object identified by [finder].
final int subtreeDepth;
/// Whether the properties of a [DiagnosticsNode] should be included.
final bool includeProperties;
/// The type of [DiagnosticsNode] tree that is requested.
final DiagnosticsType diagnosticsType;
@override
Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
'subtreeDepth': subtreeDepth.toString(),
'includeProperties': includeProperties.toString(),
'diagnosticsType': _diagnosticsTypeIndex.toSimpleName(diagnosticsType),
});
@override
String get kind => 'get_diagnostics_tree';
}
/// The result of a [GetDiagnosticsTree] command.
class DiagnosticsTreeResult extends Result {
/// Creates a [DiagnosticsTreeResult].
const DiagnosticsTreeResult(this.json);
/// The json encoded [DiagnosticsNode] tree requested by the
/// [GetDiagnosticsTree] command.
final Map<String, Object> json;
@override
Map<String, dynamic> toJson() => json;
}
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_driver/src/common/enum_util.dart'; import 'enum_util.dart';
import 'find.dart'; import 'find.dart';
import 'message.dart'; import 'message.dart';
......
...@@ -15,6 +15,7 @@ import 'package:path/path.dart' as p; ...@@ -15,6 +15,7 @@ import 'package:path/path.dart' as p;
import 'package:vm_service_client/vm_service_client.dart'; import 'package:vm_service_client/vm_service_client.dart';
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
import '../common/diagnostics_tree.dart';
import '../common/error.dart'; import '../common/error.dart';
import '../common/find.dart'; import '../common/find.dart';
import '../common/frame_sync.dart'; import '../common/frame_sync.dart';
...@@ -522,6 +523,75 @@ class FlutterDriver { ...@@ -522,6 +523,75 @@ class FlutterDriver {
return _getOffset(finder, OffsetType.center, timeout: timeout); return _getOffset(finder, OffsetType.center, timeout: timeout);
} }
/// Returns a JSON map of the [DiagnosticsNode] that is associated with the
/// [RenderObject] identified by `finder`.
///
/// The `subtreeDepth` argument controls how many layers of children will be
/// included in the result. It defaults to zero, which means that no children
/// of the [RenderObject] identified by `finder` will be part of the result.
///
/// The `includeProperties` argument controls whether properties of the
/// [DiagnosticsNode]s will be included in the result. It defaults to true.
///
/// [RenderObject]s are responsible for positioning, layout, and painting on
/// the screen, based on the configuration from a [Widget]. Callers that need
/// information about size or position should use this method.
///
/// A widget may indirectly create multiple [RenderObject]s, which each
/// implement some aspect of the widget configuration. A 1:1 relationship
/// should not be assumed.
///
/// See also:
///
/// * [getWidgetDiagnostics], which gets the [DiagnosticsNode] of a [Widget].
Future<Map<String, Object>> getRenderObjectDiagnostics(
SerializableFinder finder, {
int subtreeDepth = 0,
bool includeProperties = true,
Duration timeout,
}) async {
return _sendCommand(GetDiagnosticsTree(
finder,
DiagnosticsType.renderObject,
subtreeDepth: subtreeDepth,
includeProperties: includeProperties,
timeout: timeout,
));
}
/// Returns a JSON map of the [DiagnosticsNode] that is associated with the
/// [Widget] identified by `finder`.
///
/// The `subtreeDepth` argument controls how many layers of children will be
/// included in the result. It defaults to zero, which means that no children
/// of the [Widget] identified by `finder` will be part of the result.
///
/// The `includeProperties` argument controls whether properties of the
/// [DiagnosticsNode]s will be included in the result. It defaults to true.
///
/// [Widget]s describe configuration for the rendering tree. Individual
/// widgets may create multiple [RenderObject]s to actually layout and paint
/// the desired configuration.
///
/// See also:
///
/// * [getRenderObjectDiagnostics], which gets the [DiagnosticsNode] of a
/// [RenderObject].
Future<Map<String, Object>> getWidgetDiagnostics(
SerializableFinder finder, {
int subtreeDepth = 0,
bool includeProperties = true,
Duration timeout,
}) async {
return _sendCommand(GetDiagnosticsTree(
finder,
DiagnosticsType.renderObject,
subtreeDepth: subtreeDepth,
includeProperties: includeProperties,
timeout: timeout,
));
}
/// Tell the driver to perform a scrolling action. /// Tell the driver to perform a scrolling action.
/// ///
/// A scrolling action begins with a "pointer down" event, which commonly maps /// A scrolling action begins with a "pointer down" event, which commonly maps
......
...@@ -17,6 +17,7 @@ import 'package:flutter/services.dart'; ...@@ -17,6 +17,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../common/diagnostics_tree.dart';
import '../common/error.dart'; import '../common/error.dart';
import '../common/find.dart'; import '../common/find.dart';
import '../common/frame_sync.dart'; import '../common/frame_sync.dart';
...@@ -114,6 +115,7 @@ class FlutterDriverExtension { ...@@ -114,6 +115,7 @@ class FlutterDriverExtension {
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks, 'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
'get_semantics_id': _getSemanticsId, 'get_semantics_id': _getSemanticsId,
'get_offset': _getOffset, 'get_offset': _getOffset,
'get_diagnostics_tree': _getDiagnosticsTree,
}); });
_commandDeserializers.addAll(<String, CommandDeserializerCallback>{ _commandDeserializers.addAll(<String, CommandDeserializerCallback>{
...@@ -133,6 +135,7 @@ class FlutterDriverExtension { ...@@ -133,6 +135,7 @@ class FlutterDriverExtension {
'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params), 'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params),
'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params), 'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params),
'get_offset': (Map<String, String> params) => GetOffset.deserialize(params), 'get_offset': (Map<String, String> params) => GetOffset.deserialize(params),
'get_diagnostics_tree': (Map<String, String> params) => GetDiagnosticsTree.deserialize(params),
}); });
_finders.addAll(<String, FinderConstructor>{ _finders.addAll(<String, FinderConstructor>{
...@@ -408,6 +411,25 @@ class FlutterDriverExtension { ...@@ -408,6 +411,25 @@ class FlutterDriverExtension {
return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy); return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy);
} }
Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command) async {
final GetDiagnosticsTree diagnosticsCommand = command;
final Finder finder = await _waitForElement(_createFinder(diagnosticsCommand.finder));
final Element element = finder.evaluate().single;
DiagnosticsNode diagnosticsNode;
switch (diagnosticsCommand.diagnosticsType) {
case DiagnosticsType.renderObject:
diagnosticsNode = element.renderObject.toDiagnosticsNode();
break;
case DiagnosticsType.widget:
diagnosticsNode = element.toDiagnosticsNode();
break;
}
return DiagnosticsTreeResult(diagnosticsNode.toJsonMap(DiagnosticsSerializationDelegate(
subtreeDepth: diagnosticsCommand.subtreeDepth,
includeProperties: diagnosticsCommand.includeProperties,
)));
}
Future<ScrollResult> _scroll(Command command) async { Future<ScrollResult> _scroll(Command command) async {
final Scroll scrollCommand = command; final Scroll scrollCommand = command;
final Finder target = await _waitForElement(_createFinder(scrollCommand.finder)); final Finder target = await _waitForElement(_createFinder(scrollCommand.finder));
......
...@@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_driver/flutter_driver.dart'; import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_driver/src/common/diagnostics_tree.dart';
import 'package:flutter_driver/src/common/find.dart'; import 'package:flutter_driver/src/common/find.dart';
import 'package:flutter_driver/src/common/geometry.dart'; import 'package:flutter_driver/src/common/geometry.dart';
import 'package:flutter_driver/src/common/request_data.dart'; import 'package:flutter_driver/src/common/request_data.dart';
...@@ -267,4 +268,69 @@ void main() { ...@@ -267,4 +268,69 @@ void main() {
await tester.pump(const Duration(seconds: 2)); await tester.pump(const Duration(seconds: 2));
expect(await result, null); expect(await result, null);
}); });
testWidgets('GetDiagnosticsTree', (WidgetTester tester) async {
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
Future<Map<String, Object>> getDiagnosticsTree(DiagnosticsType type, SerializableFinder finder, { int depth = 0, bool properties = true }) async {
final Map<String, Object> arguments = GetDiagnosticsTree(finder, type, subtreeDepth: depth, includeProperties: properties).serialize();
final DiagnosticsTreeResult result = DiagnosticsTreeResult((await extension.call(arguments))['response']);
return result.json;
}
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: const Text('Hello World', key: ValueKey<String>('Text'))
),
),
);
// Widget
Map<String, Object> result = await getDiagnosticsTree(DiagnosticsType.widget, ByValueKey('Text'), depth: 0);
expect(result['children'], isNull); // depth: 0
expect(result['widgetRuntimeType'], 'Text');
List<Map<String, Object>> properties = result['properties'];
Map<String, Object> stringProperty = properties.singleWhere((Map<String, Object> property) => property['name'] == 'data');
expect(stringProperty['description'], '"Hello World"');
expect(stringProperty['propertyType'], 'String');
result = await getDiagnosticsTree(DiagnosticsType.widget, ByValueKey('Text'), depth: 0, properties: false);
expect(result['widgetRuntimeType'], 'Text');
expect(result['properties'], isNull); // properties: false
result = await getDiagnosticsTree(DiagnosticsType.widget, ByValueKey('Text'), depth: 1);
List<Map<String, Object>> children = result['children'];
expect(children.single['children'], isNull);
result = await getDiagnosticsTree(DiagnosticsType.widget, ByValueKey('Text'), depth: 100);
children = result['children'];
expect(children.single['children'], isEmpty);
// RenderObject
result = await getDiagnosticsTree(DiagnosticsType.renderObject, ByValueKey('Text'), depth: 0);
expect(result['children'], isNull); // depth: 0
expect(result['properties'], isNotNull);
expect(result['description'], startsWith('RenderParagraph'));
result = await getDiagnosticsTree(DiagnosticsType.renderObject, ByValueKey('Text'), depth: 0, properties: false);
expect(result['properties'], isNull); // properties: false
expect(result['description'], startsWith('RenderParagraph'));
result = await getDiagnosticsTree(DiagnosticsType.renderObject, ByValueKey('Text'), depth: 1);
children = result['children'];
final Map<String, Object> textSpan = children.single;
expect(textSpan['description'], 'TextSpan');
properties = textSpan['properties'];
stringProperty = properties.singleWhere((Map<String, Object> property) => property['name'] == 'text');
expect(stringProperty['description'], '"Hello World"');
expect(stringProperty['propertyType'], 'String');
expect(children.single['children'], isNull);
result = await getDiagnosticsTree(DiagnosticsType.renderObject, ByValueKey('Text'), depth: 100);
children = result['children'];
expect(children.single['children'], isEmpty);
});
} }
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