Merge pull request #2310 from yjbanov/stocks-scroll-perf-test
driver.scroll action; scroll perf test for Stocks
This commit is contained in:
commit
60b8127155
@ -18,6 +18,7 @@ class StockList extends StatelessComponent {
|
|||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ScrollableList(
|
return new ScrollableList(
|
||||||
|
key: const ValueKey('stock-list'),
|
||||||
itemExtent: StockRow.kHeight,
|
itemExtent: StockRow.kHeight,
|
||||||
children: stocks.map((Stock stock) {
|
children: stocks.map((Stock stock) {
|
||||||
return new StockRow(
|
return new StockRow(
|
||||||
|
@ -7,3 +7,5 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
path: ../../packages/flutter_test
|
path: ../../packages/flutter_test
|
||||||
|
flutter_driver:
|
||||||
|
path: ../../packages/flutter_driver
|
||||||
|
13
examples/stocks/test_driver/scroll_perf.dart
Normal file
13
examples/stocks/test_driver/scroll_perf.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2016 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 'package:flutter_driver/driver_extension.dart';
|
||||||
|
import 'package:flutter_driver/src/error.dart';
|
||||||
|
import 'package:stocks/main.dart' as app;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
flutterDriverLog.listen(print);
|
||||||
|
enableFlutterDriverExtension();
|
||||||
|
app.main();
|
||||||
|
}
|
40
examples/stocks/test_driver/scroll_perf_test.dart
Normal file
40
examples/stocks/test_driver/scroll_perf_test.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2016 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 'dart:async';
|
||||||
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
group('scrolling performance test', () {
|
||||||
|
FlutterDriver driver;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
driver = await FlutterDriver.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
if (driver != null)
|
||||||
|
driver.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tap on the floating action button; verify counter', () async {
|
||||||
|
// Find the scrollable stock list
|
||||||
|
ObjectRef stockList = await driver.findByValueKey('stock-list');
|
||||||
|
expect(stockList, isNotNull);
|
||||||
|
|
||||||
|
// Scroll down 5 times
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300));
|
||||||
|
await new Future.delayed(new Duration(milliseconds: 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll up 5 times
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300));
|
||||||
|
await new Future.delayed(new Duration(milliseconds: 500));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -148,7 +148,7 @@ class FlutterDriver {
|
|||||||
final VMIsolateRef _appIsolate;
|
final VMIsolateRef _appIsolate;
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _sendCommand(Command command) async {
|
Future<Map<String, dynamic>> _sendCommand(Command command) async {
|
||||||
Map<String, dynamic> json = <String, dynamic>{'kind': command.kind}
|
Map<String, dynamic> json = <String, dynamic>{'command': command.kind}
|
||||||
..addAll(command.toJson());
|
..addAll(command.toJson());
|
||||||
return _appIsolate.invokeExtension(_kFlutterExtensionMethod, json)
|
return _appIsolate.invokeExtension(_kFlutterExtensionMethod, json)
|
||||||
.then((Map<String, dynamic> result) => result, onError: (error, stackTrace) {
|
.then((Map<String, dynamic> result) => result, onError: (error, stackTrace) {
|
||||||
@ -184,6 +184,23 @@ class FlutterDriver {
|
|||||||
return await _sendCommand(new Tap(ref)).then((_) => null);
|
return await _sendCommand(new Tap(ref)).then((_) => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tell the driver to perform a scrolling action.
|
||||||
|
///
|
||||||
|
/// A scrolling action begins with a "pointer down" event, which commonly maps
|
||||||
|
/// to finger press on the touch screen or mouse button press. A series of
|
||||||
|
/// "pointer move" events follow. The action is completed by a "pointer up"
|
||||||
|
/// event.
|
||||||
|
///
|
||||||
|
/// [dx] and [dy] specify the total offset for the entire scrolling action.
|
||||||
|
///
|
||||||
|
/// [duration] specifies the lenght of the action.
|
||||||
|
///
|
||||||
|
/// 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((_) => null);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> getText(ObjectRef ref) async {
|
Future<String> getText(ObjectRef ref) async {
|
||||||
GetTextResult result = GetTextResult.fromJson(await _sendCommand(new GetText(ref)));
|
GetTextResult result = GetTextResult.fromJson(await _sendCommand(new GetText(ref)));
|
||||||
return result.text;
|
return result.text;
|
||||||
|
@ -7,7 +7,9 @@ import 'dart:convert';
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter_test/src/instrumentation.dart';
|
import 'package:flutter_test/src/instrumentation.dart';
|
||||||
|
import 'package:flutter_test/src/test_pointer.dart';
|
||||||
|
|
||||||
import 'error.dart';
|
import 'error.dart';
|
||||||
import 'find.dart';
|
import 'find.dart';
|
||||||
@ -54,6 +56,7 @@ class FlutterDriverExtension {
|
|||||||
'find': find,
|
'find': find,
|
||||||
'tap': tap,
|
'tap': tap,
|
||||||
'get_text': getText,
|
'get_text': getText,
|
||||||
|
'scroll': scroll,
|
||||||
};
|
};
|
||||||
|
|
||||||
_commandDeserializers = {
|
_commandDeserializers = {
|
||||||
@ -61,6 +64,7 @@ class FlutterDriverExtension {
|
|||||||
'find': Find.fromJson,
|
'find': Find.fromJson,
|
||||||
'tap': Tap.fromJson,
|
'tap': Tap.fromJson,
|
||||||
'get_text': GetText.fromJson,
|
'get_text': GetText.fromJson,
|
||||||
|
'scroll': Scroll.fromJson,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +78,7 @@ class FlutterDriverExtension {
|
|||||||
|
|
||||||
Future<ServiceExtensionResponse> call(Map<String, String> params) async {
|
Future<ServiceExtensionResponse> call(Map<String, String> params) async {
|
||||||
try {
|
try {
|
||||||
String commandKind = params['kind'];
|
String commandKind = params['command'];
|
||||||
CommandHandlerCallback commandHandler = _commandHandlers[commandKind];
|
CommandHandlerCallback commandHandler = _commandHandlers[commandKind];
|
||||||
CommandDeserializerCallback commandDeserializer =
|
CommandDeserializerCallback commandDeserializer =
|
||||||
_commandDeserializers[commandKind];
|
_commandDeserializers[commandKind];
|
||||||
@ -91,11 +95,13 @@ class FlutterDriverExtension {
|
|||||||
return new ServiceExtensionResponse.result(JSON.encode(result.toJson()));
|
return new ServiceExtensionResponse.result(JSON.encode(result.toJson()));
|
||||||
}, onError: (e, s) {
|
}, onError: (e, s) {
|
||||||
_log.warning('$e:\n$s');
|
_log.warning('$e:\n$s');
|
||||||
return new ServiceExtensionResponse.error(
|
return new ServiceExtensionResponse.error(ServiceExtensionResponse.kExtensionError, '$e');
|
||||||
ServiceExtensionResponse.kExtensionError, '$e');
|
|
||||||
});
|
});
|
||||||
} catch(error, stackTrace) {
|
} catch(error, stackTrace) {
|
||||||
_log.warning('Uncaught extension error: $error\n$stackTrace');
|
String message = 'Uncaught extension error: $error\n$stackTrace';
|
||||||
|
_log.error(message);
|
||||||
|
return new ServiceExtensionResponse.error(
|
||||||
|
ServiceExtensionResponse.kExtensionError, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +174,29 @@ class FlutterDriverExtension {
|
|||||||
return new TapResult();
|
return new TapResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ScrollResult> scroll(Scroll command) async {
|
||||||
|
Element target = await _dereferenceOrDie(command.targetRef);
|
||||||
|
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;
|
||||||
|
Point startLocation = prober.getCenter(target);
|
||||||
|
Point currentLocation = startLocation;
|
||||||
|
TestPointer pointer = new TestPointer(1);
|
||||||
|
HitTestResult hitTest = new HitTestResult();
|
||||||
|
|
||||||
|
prober.binding.hitTest(hitTest, startLocation);
|
||||||
|
prober.dispatchEvent(pointer.down(startLocation), hitTest);
|
||||||
|
await new Future<Null>.value(); // so that down and move don't happen in the same microtask
|
||||||
|
for (int moves = 0; moves < totalMoves; moves++) {
|
||||||
|
currentLocation = currentLocation + delta;
|
||||||
|
prober.dispatchEvent(pointer.move(currentLocation), hitTest);
|
||||||
|
await new Future<Null>.delayed(pause);
|
||||||
|
}
|
||||||
|
prober.dispatchEvent(pointer.up(), hitTest);
|
||||||
|
|
||||||
|
return new ScrollResult();
|
||||||
|
}
|
||||||
|
|
||||||
Future<GetTextResult> getText(GetText command) async {
|
Future<GetTextResult> getText(GetText command) async {
|
||||||
Element target = await _dereferenceOrDie(command.targetRef);
|
Element target = await _dereferenceOrDie(command.targetRef);
|
||||||
// TODO(yjbanov): support more ways to read text
|
// TODO(yjbanov): support more ways to read text
|
||||||
|
@ -23,3 +23,54 @@ class TapResult extends Result {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() => {};
|
Map<String, dynamic> toJson() => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Command the driver to perform a scrolling action.
|
||||||
|
class Scroll extends CommandWithTarget {
|
||||||
|
final String kind = 'scroll';
|
||||||
|
|
||||||
|
Scroll(
|
||||||
|
ObjectRef targetRef,
|
||||||
|
this.dx,
|
||||||
|
this.dy,
|
||||||
|
this.duration,
|
||||||
|
this.frequency
|
||||||
|
) : super(targetRef);
|
||||||
|
|
||||||
|
static Scroll fromJson(Map<String, dynamic> json) {
|
||||||
|
return new Scroll(
|
||||||
|
new ObjectRef(json['targetRef']),
|
||||||
|
double.parse(json['dx']),
|
||||||
|
double.parse(json['dy']),
|
||||||
|
new Duration(microseconds: int.parse(json['duration'])),
|
||||||
|
int.parse(json['frequency'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delta X offset per move event.
|
||||||
|
final double dx;
|
||||||
|
|
||||||
|
/// Delta Y offset per move event.
|
||||||
|
final double dy;
|
||||||
|
|
||||||
|
/// The duration of the scrolling action
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
/// The frequency in Hz of the generated move events.
|
||||||
|
final int frequency;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => super.toJson()..addAll({
|
||||||
|
'dx': dx,
|
||||||
|
'dy': dy,
|
||||||
|
'duration': duration.inMicroseconds,
|
||||||
|
'frequency': frequency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScrollResult extends Result {
|
||||||
|
static ScrollResult fromJson(Map<String, dynamic> json) {
|
||||||
|
return new ScrollResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {};
|
||||||
|
}
|
||||||
|
@ -122,7 +122,7 @@ main() {
|
|||||||
test('finds by ValueKey', () async {
|
test('finds by ValueKey', () async {
|
||||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||||
expect(i.positionalArguments[1], {
|
expect(i.positionalArguments[1], {
|
||||||
'kind': 'find',
|
'command': 'find',
|
||||||
'searchSpecType': 'ByValueKey',
|
'searchSpecType': 'ByValueKey',
|
||||||
'keyValueString': 'foo',
|
'keyValueString': 'foo',
|
||||||
'keyValueType': 'String'
|
'keyValueType': 'String'
|
||||||
@ -150,7 +150,7 @@ main() {
|
|||||||
test('sends the tap command', () async {
|
test('sends the tap command', () async {
|
||||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||||
expect(i.positionalArguments[1], {
|
expect(i.positionalArguments[1], {
|
||||||
'kind': 'tap',
|
'command': 'tap',
|
||||||
'targetRef': '123'
|
'targetRef': '123'
|
||||||
});
|
});
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
@ -172,7 +172,7 @@ main() {
|
|||||||
test('sends the getText command', () async {
|
test('sends the getText command', () async {
|
||||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||||
expect(i.positionalArguments[1], {
|
expect(i.positionalArguments[1], {
|
||||||
'kind': 'get_text',
|
'command': 'get_text',
|
||||||
'targetRef': '123'
|
'targetRef': '123'
|
||||||
});
|
});
|
||||||
return new Future.value({
|
return new Future.value({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user