Commit a9b965cb authored by Hans Muller's avatar Hans Muller

Gallery demo start-time performance test (#3655)

*  Gallery demo start-time performance test
parent 76b04cdd
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_sprites/flutter_sprites.dart'; import 'package:flutter_sprites/flutter_sprites.dart';
...@@ -288,7 +287,7 @@ class _ProgressCircle extends NodeWithSize { ...@@ -288,7 +287,7 @@ class _ProgressCircle extends NodeWithSize {
Paint circlePaint = new Paint() Paint circlePaint = new Paint()
..color = Colors.white30 ..color = Colors.white30
..strokeWidth = 24.0 ..strokeWidth = 24.0
..style = ui.PaintingStyle.stroke; ..style = PaintingStyle.stroke;
canvas.drawCircle( canvas.drawCircle(
new Point(size.width / 2.0, size.height / 2.0), new Point(size.width / 2.0, size.height / 2.0),
...@@ -299,7 +298,7 @@ class _ProgressCircle extends NodeWithSize { ...@@ -299,7 +298,7 @@ class _ProgressCircle extends NodeWithSize {
Paint pathPaint = new Paint() Paint pathPaint = new Paint()
..color = Colors.purple[500] ..color = Colors.purple[500]
..strokeWidth = 25.0 ..strokeWidth = 25.0
..style = ui.PaintingStyle.stroke; ..style = PaintingStyle.stroke;
double angle = value.clamp(0.0, 1.0) * _kSweep; double angle = value.clamp(0.0, 1.0) * _kSweep;
Path path = new Path() Path path = new Path()
......
...@@ -44,6 +44,16 @@ final Map<String, WidgetBuilder> kRoutes = <String, WidgetBuilder>{ ...@@ -44,6 +44,16 @@ final Map<String, WidgetBuilder> kRoutes = <String, WidgetBuilder>{
TypographyDemo.routeName: (BuildContext context) => new TypographyDemo(), TypographyDemo.routeName: (BuildContext context) => new TypographyDemo(),
}; };
final ThemeData _kGalleryLightTheme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
);
final ThemeData _kGalleryDarkTheme = new ThemeData(
brightness: ThemeBrightness.dark,
primarySwatch: Colors.purple
);
class GalleryApp extends StatefulWidget { class GalleryApp extends StatefulWidget {
GalleryApp({ Key key }) : super(key: key); GalleryApp({ Key key }) : super(key: key);
...@@ -73,13 +83,3 @@ class GalleryAppState extends State<GalleryApp> { ...@@ -73,13 +83,3 @@ class GalleryAppState extends State<GalleryApp> {
); );
} }
} }
ThemeData _kGalleryLightTheme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
);
ThemeData _kGalleryDarkTheme = new ThemeData(
brightness: ThemeBrightness.dark,
primarySwatch: Colors.purple
);
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// 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 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef Widget GalleryDemoBuilder(); typedef Widget GalleryDemoBuilder();
...@@ -21,9 +23,14 @@ class GalleryItem extends StatelessWidget { ...@@ -21,9 +23,14 @@ class GalleryItem extends StatelessWidget {
leading: leading, leading: leading,
title: new Text(title), title: new Text(title),
onTap: () { onTap: () {
if (routeName != null) if (routeName != null) {
Timeline.instantSync('Start Transition', arguments: <String, String>{
'from': '/',
'to': routeName
});
Navigator.pushNamed(context, routeName); Navigator.pushNamed(context, routeName);
} }
}
); );
} }
} }
// 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:material_gallery/main.dart' as app;
void main() {
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';
// Warning: the following strings must be kept in sync with GalleryHome.
const List<String> demoCategories = const <String>['Demos', 'Components', 'Style'];
const List<String> demoNames = const <String>[
'Weather',
'Fitness',
'Fancy lines',
'Flexible space toolbar',
'Floating action button',
'Buttons',
'Cards',
'Chips',
'Date picker',
'Data tables',
'Dialog',
'Drop-down button',
'Expand/collapse list control',
'Grid',
'Icons',
'Leave-behind list items',
'List',
'Menus',
'Modal bottom sheet',
'Over-scroll',
'Page selector',
'Persistent bottom sheet',
'Progress indicators',
'Scrollable tabs',
'Selection controls',
'Sliders',
'Snackbar',
'Tabs',
'Text fields',
'Time picker',
'Tooltips',
'Colors',
'Typography'
];
void main() {
group('flutter gallery transitions', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null)
driver.close();
});
test('all demos', () async {
Timeline timeline = await driver.traceAction(() async {
// Expand the demo category submenus.
for (String category in demoCategories.reversed) {
await driver.tap(find.text(category));
await new Future<Null>.delayed(new Duration(milliseconds: 500));
}
// Scroll each demo menu item into view, launch the demo and
// return to the demo menu 2x.
for(String demoName in demoNames) {
SerializableFinder menuItem = find.text(demoName);
await driver.scrollIntoView(menuItem);
await new Future<Null>.delayed(new Duration(milliseconds: 500));
for(int i = 0; i < 2; i += 1) {
await driver.tap(menuItem); // Launch the demo
await new Future<Null>.delayed(new Duration(milliseconds: 500));
await driver.tap(find.byTooltip('Back'));
await new Future<Null>.delayed(new Duration(milliseconds: 1000));
}
}
},
categories: const <TracingCategory>[
TracingCategory.dart,
TracingCategory.gc,
TracingCategory.compiler
]);
new TimelineSummary.summarize(timeline)
..writeSummaryToFile('transitions_perf', pretty: true)
..writeTimelineToFile('transitions_perf', pretty: true);
}, timeout: new Timeout(new Duration(minutes: 15)));
});
}
...@@ -15,7 +15,8 @@ export 'src/driver.dart' show ...@@ -15,7 +15,8 @@ export 'src/driver.dart' show
find, find,
CommonFinders, CommonFinders,
EvaluatorFunction, EvaluatorFunction,
FlutterDriver; FlutterDriver,
TracingCategory;
export 'src/error.dart' show export 'src/error.dart' show
DriverError, DriverError,
......
...@@ -15,13 +15,39 @@ import 'health.dart'; ...@@ -15,13 +15,39 @@ import 'health.dart';
import 'message.dart'; import 'message.dart';
import 'timeline.dart'; import 'timeline.dart';
enum TracingCategory {
all, api, compiler, dart, debugger, embedder, gc, isolate, vm
}
const List<TracingCategory> _defaultCategories = const <TracingCategory>[TracingCategory.all];
// See https://github.com/dart-lang/sdk/blob/master/runtime/vm/timeline.cc#L32
String _tracingCategoriesToString(List<TracingCategory> categories) {
final String contents = categories.map((TracingCategory category) {
switch(category) {
case TracingCategory.all: return 'all';
case TracingCategory.api: return 'API';
case TracingCategory.compiler: return 'Compiler';
case TracingCategory.dart: return 'Dart';
case TracingCategory.debugger: return 'Debugger';
case TracingCategory.embedder: return 'Embedder';
case TracingCategory.gc: return 'GC';
case TracingCategory.isolate: return 'Isolate';
case TracingCategory.vm: return 'VM';
default:
throw 'Unknown tracing category $category';
}
}).join(', ');
return '[$contents]';
}
final Logger _log = new Logger('FlutterDriver'); final Logger _log = new Logger('FlutterDriver');
/// A convenient accessor to frequently used finders. /// A convenient accessor to frequently used finders.
/// ///
/// Examples: /// Examples:
/// ///
/// driver.tap(find.byText('Save')); /// driver.tap(find.text('Save'));
/// driver.scroll(find.byValueKey(42)); /// driver.scroll(find.byValueKey(42));
const CommonFinders find = const CommonFinders._(); const CommonFinders find = const CommonFinders._();
...@@ -211,15 +237,24 @@ class FlutterDriver { ...@@ -211,15 +237,24 @@ class FlutterDriver {
return await _sendCommand(new Scroll(finder, dx, dy, duration, frequency)).then((Map<String, dynamic> _) => null); return await _sendCommand(new Scroll(finder, dx, dy, duration, frequency)).then((Map<String, dynamic> _) => null);
} }
/// Scrolls the Scrollable ancestor of the widget located by [finder]
/// until the widget is completely visible.
Future<Null> scrollIntoView(SerializableFinder finder) async {
return await _sendCommand(new ScrollIntoView(finder)).then((Map<String, dynamic> _) => null);
}
/// Returns the text in the `Text` widget located by [finder]. /// Returns the text in the `Text` widget located by [finder].
Future<String> getText(SerializableFinder finder) async { Future<String> getText(SerializableFinder finder) async {
return GetTextResult.fromJson(await _sendCommand(new GetText(finder))).text; return GetTextResult.fromJson(await _sendCommand(new GetText(finder))).text;
} }
/// Starts recording performance traces. /// Starts recording performance traces.
Future<Null> startTracing() async { Future<Null> startTracing({List<TracingCategory> categories: _defaultCategories}) async {
assert(categories != null && categories.length > 0);
try { try {
await _peer.sendRequest(_kSetVMTimelineFlagsMethod, {'recordedStreams': '[all]'}); await _peer.sendRequest(_kSetVMTimelineFlagsMethod, {
'recordedStreams': _tracingCategoriesToString(categories)
});
return null; return null;
} catch(error, stackTrace) { } catch(error, stackTrace) {
throw new DriverError( throw new DriverError(
...@@ -251,8 +286,8 @@ class FlutterDriver { ...@@ -251,8 +286,8 @@ class FlutterDriver {
/// ///
/// This is merely a convenience wrapper on top of [startTracing] and /// This is merely a convenience wrapper on top of [startTracing] and
/// [stopTracingAndDownloadTimeline]. /// [stopTracingAndDownloadTimeline].
Future<Timeline> traceAction(Future<dynamic> action()) async { Future<Timeline> traceAction(Future<dynamic> action(), { List<TracingCategory> categories: _defaultCategories }) async {
await startTracing(); await startTracing(categories: categories);
await action(); await action();
return stopTracingAndDownloadTimeline(); return stopTracingAndDownloadTimeline();
} }
......
...@@ -63,6 +63,7 @@ class FlutterDriverExtension { ...@@ -63,6 +63,7 @@ class FlutterDriverExtension {
'tap': tap, 'tap': tap,
'get_text': getText, 'get_text': getText,
'scroll': scroll, 'scroll': scroll,
'scrollIntoView': scrollIntoView,
'waitFor': waitFor, 'waitFor': waitFor,
}); });
...@@ -71,6 +72,7 @@ class FlutterDriverExtension { ...@@ -71,6 +72,7 @@ class FlutterDriverExtension {
'tap': Tap.deserialize, 'tap': Tap.deserialize,
'get_text': GetText.deserialize, 'get_text': GetText.deserialize,
'scroll': Scroll.deserialize, 'scroll': Scroll.deserialize,
'scrollIntoView': ScrollIntoView.deserialize,
'waitFor': WaitFor.deserialize, 'waitFor': WaitFor.deserialize,
}); });
...@@ -207,6 +209,12 @@ class FlutterDriverExtension { ...@@ -207,6 +209,12 @@ class FlutterDriverExtension {
return new ScrollResult(); return new ScrollResult();
} }
Future<ScrollResult> scrollIntoView(ScrollIntoView command) async {
Finder target = await _waitForElement(_createFinder(command.finder));
await Scrollable.ensureVisible(target.evaluate().single);
return new ScrollResult();
}
Future<GetTextResult> getText(GetText command) async { Future<GetTextResult> getText(GetText command) async {
Finder target = await _waitForElement(_createFinder(command.finder)); Finder target = await _waitForElement(_createFinder(command.finder));
// TODO(yjbanov): support more ways to read text // TODO(yjbanov): support more ways to read text
......
...@@ -73,6 +73,19 @@ class Scroll extends CommandWithTarget { ...@@ -73,6 +73,19 @@ class Scroll extends CommandWithTarget {
}); });
} }
/// Command the driver to ensure that the element represented by [finder]
/// has been scrolled completely into view.
class ScrollIntoView extends CommandWithTarget {
@override
final String kind = 'scrollIntoView';
ScrollIntoView(SerializableFinder finder) : super(finder);
static ScrollIntoView deserialize(Map<String, dynamic> json) {
return new ScrollIntoView(SerializableFinder.deserialize(json));
}
}
class ScrollResult extends Result { class ScrollResult extends Result {
static ScrollResult fromJson(Map<String, dynamic> json) { static ScrollResult fromJson(Map<String, dynamic> json) {
return new ScrollResult(); return new ScrollResult();
......
...@@ -235,6 +235,50 @@ void main() { ...@@ -235,6 +235,50 @@ void main() {
expect(timeline.events.single.name, 'test event'); expect(timeline.events.single.name, 'test event');
}); });
}); });
group('traceAction categories', () {
test('specify non-default categories', () async {
bool actionCalled = false;
bool startTracingCalled = false;
bool stopTracingCalled = false;
when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals({'recordedStreams': '[Dart, GC, Compiler]'}))))
.thenAnswer((_) async {
startTracingCalled = true;
return null;
});
when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals({'recordedStreams': '[]'}))))
.thenAnswer((_) async {
stopTracingCalled = true;
return null;
});
when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((_) async {
return <String, dynamic> {
'traceEvents': [
{
'name': 'test event'
}
],
};
});
Timeline timeline = await driver.traceAction(() {
actionCalled = true;
},
categories: const <TracingCategory>[
TracingCategory.dart,
TracingCategory.gc,
TracingCategory.compiler
]);
expect(actionCalled, isTrue);
expect(startTracingCalled, isTrue);
expect(stopTracingCalled, isTrue);
expect(timeline.events.single.name, 'test event');
});
});
}); });
} }
......
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