Gallery demo start-time performance test (#3655)
* Gallery demo start-time performance test
This commit is contained in:
parent
76b04cddde
commit
a9b965cb9b
@ -4,7 +4,6 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_sprites/flutter_sprites.dart';
|
||||
@ -288,7 +287,7 @@ class _ProgressCircle extends NodeWithSize {
|
||||
Paint circlePaint = new Paint()
|
||||
..color = Colors.white30
|
||||
..strokeWidth = 24.0
|
||||
..style = ui.PaintingStyle.stroke;
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
canvas.drawCircle(
|
||||
new Point(size.width / 2.0, size.height / 2.0),
|
||||
@ -299,7 +298,7 @@ class _ProgressCircle extends NodeWithSize {
|
||||
Paint pathPaint = new Paint()
|
||||
..color = Colors.purple[500]
|
||||
..strokeWidth = 25.0
|
||||
..style = ui.PaintingStyle.stroke;
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
double angle = value.clamp(0.0, 1.0) * _kSweep;
|
||||
Path path = new Path()
|
||||
|
@ -44,6 +44,16 @@ final Map<String, WidgetBuilder> kRoutes = <String, WidgetBuilder>{
|
||||
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 {
|
||||
GalleryApp({ Key key }) : super(key: key);
|
||||
|
||||
@ -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 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef Widget GalleryDemoBuilder();
|
||||
@ -21,9 +23,14 @@ class GalleryItem extends StatelessWidget {
|
||||
leading: leading,
|
||||
title: new Text(title),
|
||||
onTap: () {
|
||||
if (routeName != null)
|
||||
if (routeName != null) {
|
||||
Timeline.instantSync('Start Transition', arguments: <String, String>{
|
||||
'from': '/',
|
||||
'to': routeName
|
||||
});
|
||||
Navigator.pushNamed(context, routeName);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
11
examples/material_gallery/test_driver/transitions_perf.dart
Normal file
11
examples/material_gallery/test_driver/transitions_perf.dart
Normal file
@ -0,0 +1,11 @@
|
||||
// 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();
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
// 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
|
||||
find,
|
||||
CommonFinders,
|
||||
EvaluatorFunction,
|
||||
FlutterDriver;
|
||||
FlutterDriver,
|
||||
TracingCategory;
|
||||
|
||||
export 'src/error.dart' show
|
||||
DriverError,
|
||||
|
@ -15,13 +15,39 @@ import 'health.dart';
|
||||
import 'message.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');
|
||||
|
||||
/// A convenient accessor to frequently used finders.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// driver.tap(find.byText('Save'));
|
||||
/// driver.tap(find.text('Save'));
|
||||
/// driver.scroll(find.byValueKey(42));
|
||||
const CommonFinders find = const CommonFinders._();
|
||||
|
||||
@ -211,15 +237,24 @@ class FlutterDriver {
|
||||
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].
|
||||
Future<String> getText(SerializableFinder finder) async {
|
||||
return GetTextResult.fromJson(await _sendCommand(new GetText(finder))).text;
|
||||
}
|
||||
|
||||
/// Starts recording performance traces.
|
||||
Future<Null> startTracing() async {
|
||||
Future<Null> startTracing({List<TracingCategory> categories: _defaultCategories}) async {
|
||||
assert(categories != null && categories.length > 0);
|
||||
try {
|
||||
await _peer.sendRequest(_kSetVMTimelineFlagsMethod, {'recordedStreams': '[all]'});
|
||||
await _peer.sendRequest(_kSetVMTimelineFlagsMethod, {
|
||||
'recordedStreams': _tracingCategoriesToString(categories)
|
||||
});
|
||||
return null;
|
||||
} catch(error, stackTrace) {
|
||||
throw new DriverError(
|
||||
@ -251,8 +286,8 @@ class FlutterDriver {
|
||||
///
|
||||
/// This is merely a convenience wrapper on top of [startTracing] and
|
||||
/// [stopTracingAndDownloadTimeline].
|
||||
Future<Timeline> traceAction(Future<dynamic> action()) async {
|
||||
await startTracing();
|
||||
Future<Timeline> traceAction(Future<dynamic> action(), { List<TracingCategory> categories: _defaultCategories }) async {
|
||||
await startTracing(categories: categories);
|
||||
await action();
|
||||
return stopTracingAndDownloadTimeline();
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ class FlutterDriverExtension {
|
||||
'tap': tap,
|
||||
'get_text': getText,
|
||||
'scroll': scroll,
|
||||
'scrollIntoView': scrollIntoView,
|
||||
'waitFor': waitFor,
|
||||
});
|
||||
|
||||
@ -71,6 +72,7 @@ class FlutterDriverExtension {
|
||||
'tap': Tap.deserialize,
|
||||
'get_text': GetText.deserialize,
|
||||
'scroll': Scroll.deserialize,
|
||||
'scrollIntoView': ScrollIntoView.deserialize,
|
||||
'waitFor': WaitFor.deserialize,
|
||||
});
|
||||
|
||||
@ -207,6 +209,12 @@ class FlutterDriverExtension {
|
||||
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 {
|
||||
Finder target = await _waitForElement(_createFinder(command.finder));
|
||||
// TODO(yjbanov): support more ways to read text
|
||||
|
@ -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 {
|
||||
static ScrollResult fromJson(Map<String, dynamic> json) {
|
||||
return new ScrollResult();
|
||||
|
@ -235,6 +235,50 @@ void main() {
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user