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) {
|
||||
return new ScrollableList(
|
||||
key: const ValueKey('stock-list'),
|
||||
itemExtent: StockRow.kHeight,
|
||||
children: stocks.map((Stock stock) {
|
||||
return new StockRow(
|
||||
|
@ -7,3 +7,5 @@ dependencies:
|
||||
dev_dependencies:
|
||||
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;
|
||||
|
||||
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());
|
||||
return _appIsolate.invokeExtension(_kFlutterExtensionMethod, json)
|
||||
.then((Map<String, dynamic> result) => result, onError: (error, stackTrace) {
|
||||
@ -184,6 +184,23 @@ class FlutterDriver {
|
||||
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 {
|
||||
GetTextResult result = GetTextResult.fromJson(await _sendCommand(new GetText(ref)));
|
||||
return result.text;
|
||||
|
@ -7,7 +7,9 @@ import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_test/src/instrumentation.dart';
|
||||
import 'package:flutter_test/src/test_pointer.dart';
|
||||
|
||||
import 'error.dart';
|
||||
import 'find.dart';
|
||||
@ -54,6 +56,7 @@ class FlutterDriverExtension {
|
||||
'find': find,
|
||||
'tap': tap,
|
||||
'get_text': getText,
|
||||
'scroll': scroll,
|
||||
};
|
||||
|
||||
_commandDeserializers = {
|
||||
@ -61,6 +64,7 @@ class FlutterDriverExtension {
|
||||
'find': Find.fromJson,
|
||||
'tap': Tap.fromJson,
|
||||
'get_text': GetText.fromJson,
|
||||
'scroll': Scroll.fromJson,
|
||||
};
|
||||
}
|
||||
|
||||
@ -74,7 +78,7 @@ class FlutterDriverExtension {
|
||||
|
||||
Future<ServiceExtensionResponse> call(Map<String, String> params) async {
|
||||
try {
|
||||
String commandKind = params['kind'];
|
||||
String commandKind = params['command'];
|
||||
CommandHandlerCallback commandHandler = _commandHandlers[commandKind];
|
||||
CommandDeserializerCallback commandDeserializer =
|
||||
_commandDeserializers[commandKind];
|
||||
@ -91,11 +95,13 @@ class FlutterDriverExtension {
|
||||
return new ServiceExtensionResponse.result(JSON.encode(result.toJson()));
|
||||
}, onError: (e, s) {
|
||||
_log.warning('$e:\n$s');
|
||||
return new ServiceExtensionResponse.error(
|
||||
ServiceExtensionResponse.kExtensionError, '$e');
|
||||
return new ServiceExtensionResponse.error(ServiceExtensionResponse.kExtensionError, '$e');
|
||||
});
|
||||
} 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();
|
||||
}
|
||||
|
||||
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 {
|
||||
Element target = await _dereferenceOrDie(command.targetRef);
|
||||
// TODO(yjbanov): support more ways to read text
|
||||
|
@ -23,3 +23,54 @@ class TapResult extends Result {
|
||||
|
||||
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 {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], {
|
||||
'kind': 'find',
|
||||
'command': 'find',
|
||||
'searchSpecType': 'ByValueKey',
|
||||
'keyValueString': 'foo',
|
||||
'keyValueType': 'String'
|
||||
@ -150,7 +150,7 @@ main() {
|
||||
test('sends the tap command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], {
|
||||
'kind': 'tap',
|
||||
'command': 'tap',
|
||||
'targetRef': '123'
|
||||
});
|
||||
return new Future.value();
|
||||
@ -172,7 +172,7 @@ main() {
|
||||
test('sends the getText command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], {
|
||||
'kind': 'get_text',
|
||||
'command': 'get_text',
|
||||
'targetRef': '123'
|
||||
});
|
||||
return new Future.value({
|
||||
|
Loading…
x
Reference in New Issue
Block a user