Commit ee184a78 authored by yjbanov's avatar yjbanov

driver.scroll action; scroll perf test for Stocks

parent cde2e101
......@@ -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
// 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();
}
// 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({
......
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