delay taking screenshot to allow GPU thread to render the frame (#12896)
* delay taking screenshot to allow GPU thread to render the frame * address comments
This commit is contained in:
parent
289ff9d549
commit
91bd9bc4f8
@ -24,6 +24,7 @@ Future<TaskResult> runEndToEndTests() async {
|
|||||||
const List<String> entryPoints = const <String>[
|
const List<String> entryPoints = const <String>[
|
||||||
'lib/keyboard_resize.dart',
|
'lib/keyboard_resize.dart',
|
||||||
'lib/driver.dart',
|
'lib/driver.dart',
|
||||||
|
'lib/screenshot.dart',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (final String entryPoint in entryPoints) {
|
for (final String entryPoint in entryPoints) {
|
||||||
|
80
dev/integration_tests/ui/lib/screenshot.dart
Normal file
80
dev/integration_tests/ui/lib/screenshot.dart
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2017 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/material.dart';
|
||||||
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
|
||||||
|
/// This sample application creates a hard to render frame, causing the
|
||||||
|
/// driver script to race the GPU thread. If the driver script wins the
|
||||||
|
/// race, it will screenshot the previous frame. If the GPU thread wins
|
||||||
|
/// it, it will screenshot the latest frame.
|
||||||
|
void main() {
|
||||||
|
enableFlutterDriverExtension();
|
||||||
|
|
||||||
|
runApp(new Toggler());
|
||||||
|
}
|
||||||
|
|
||||||
|
class Toggler extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<Toggler> createState() => new TogglerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TogglerState extends State<Toggler> {
|
||||||
|
bool _visible = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new MaterialApp(
|
||||||
|
home: new Scaffold(
|
||||||
|
appBar: new AppBar(
|
||||||
|
title: const Text('FlutterDriver test'),
|
||||||
|
),
|
||||||
|
body: new Material(
|
||||||
|
child: new Column(
|
||||||
|
children: <Widget>[
|
||||||
|
new FlatButton(
|
||||||
|
key: const ValueKey<String>('toggle'),
|
||||||
|
child: const Text('Toggle visibility'),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_visible = !_visible;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new Expanded(
|
||||||
|
child: new ListView(
|
||||||
|
children: _buildRows(_visible ? 10 : 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildRows(int count) {
|
||||||
|
return new List<Widget>.generate(count, (int i) {
|
||||||
|
return new Row(
|
||||||
|
children: _buildCells(i / count),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds cells that are known to take time to render causing a delay on the
|
||||||
|
/// GPU thread.
|
||||||
|
List<Widget> _buildCells(double epsilon) {
|
||||||
|
return new List<Widget>.generate(15, (int i) {
|
||||||
|
return new Expanded(
|
||||||
|
child: new Material(
|
||||||
|
// A magic color that the test will be looking for on the screenshot.
|
||||||
|
color: const Color(0xffff0102),
|
||||||
|
borderRadius: new BorderRadius.all(new Radius.circular(i.toDouble() + epsilon)),
|
||||||
|
elevation: 5.0,
|
||||||
|
child: const SizedBox(height: 10.0, width: 10.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -2,6 +2,7 @@ name: integration_ui
|
|||||||
description: Flutter non-plugin UI integration tests.
|
description: Flutter non-plugin UI integration tests.
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
image: 1.1.29
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_driver:
|
flutter_driver:
|
||||||
|
46
dev/integration_tests/ui/test_driver/screenshot_test.dart
Normal file
46
dev/integration_tests/ui/test_driver/screenshot_test.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2017 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/flutter_driver.dart';
|
||||||
|
import 'package:image/image.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('FlutterDriver', () {
|
||||||
|
FlutterDriver driver;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
driver = await FlutterDriver.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await driver.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should take screenshot', () async {
|
||||||
|
final SerializableFinder toggleBtn = find.byValueKey('toggle');
|
||||||
|
// Cards use a magic background color that we look for in the screenshots.
|
||||||
|
final Matcher cardsAreVisible = contains(0xff0201ff);
|
||||||
|
await driver.waitFor(toggleBtn);
|
||||||
|
|
||||||
|
bool cardsShouldBeVisible = false;
|
||||||
|
Image imageBefore = decodePng(await driver.screenshot());
|
||||||
|
for (int i = 0; i < 10; i += 1) {
|
||||||
|
await driver.tap(toggleBtn);
|
||||||
|
cardsShouldBeVisible = !cardsShouldBeVisible;
|
||||||
|
final Image imageAfter = decodePng(await driver.screenshot());
|
||||||
|
|
||||||
|
if (cardsShouldBeVisible) {
|
||||||
|
expect(imageBefore.data, isNot(cardsAreVisible));
|
||||||
|
expect(imageAfter.data, cardsAreVisible);
|
||||||
|
} else {
|
||||||
|
expect(imageBefore.data, cardsAreVisible);
|
||||||
|
expect(imageAfter.data, isNot(cardsAreVisible));
|
||||||
|
}
|
||||||
|
|
||||||
|
imageBefore = imageAfter;
|
||||||
|
}
|
||||||
|
}, timeout: const Timeout(const Duration(minutes: 2)));
|
||||||
|
});
|
||||||
|
}
|
@ -435,6 +435,42 @@ class FlutterDriver {
|
|||||||
/// Take a screenshot. The image will be returned as a PNG.
|
/// Take a screenshot. The image will be returned as a PNG.
|
||||||
Future<List<int>> screenshot({ Duration timeout }) async {
|
Future<List<int>> screenshot({ Duration timeout }) async {
|
||||||
timeout ??= _kLongTimeout;
|
timeout ??= _kLongTimeout;
|
||||||
|
|
||||||
|
// HACK: this artificial delay here is to deal with a race between the
|
||||||
|
// driver script and the GPU thread. The issue is that driver API
|
||||||
|
// synchronizes with the framework based on transient callbacks, which
|
||||||
|
// are out of sync with the GPU thread. Here's the timeline of events
|
||||||
|
// in ASCII art:
|
||||||
|
//
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Before this change:
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// UI : <-- build -->
|
||||||
|
// GPU : <-- rasterize -->
|
||||||
|
// Gap : | random |
|
||||||
|
// Driver: <-- screenshot -->
|
||||||
|
//
|
||||||
|
// In the diagram above, the gap is the time between the last driver
|
||||||
|
// action taken, such as a `tap()`, and the subsequent call to
|
||||||
|
// `screenshot()`. The gap is random because it is determined by the
|
||||||
|
// unpredictable network communication between the driver process and
|
||||||
|
// the application. If this gap is too short, the screenshot is taken
|
||||||
|
// before the GPU thread is done rasterizing the frame, so the
|
||||||
|
// screenshot of the previous frame is taken, which is wrong.
|
||||||
|
//
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// After this change:
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// UI : <-- build -->
|
||||||
|
// GPU : <-- rasterize -->
|
||||||
|
// Gap : | 2 seconds or more |
|
||||||
|
// Driver: <-- screenshot -->
|
||||||
|
//
|
||||||
|
// The two-second gap should be long enough for the GPU thread to
|
||||||
|
// finish rasterizing the frame, but not longer than necessary to keep
|
||||||
|
// driver tests as fast a possible.
|
||||||
|
await new Future<Null>.delayed(const Duration(seconds: 2));
|
||||||
|
|
||||||
final Map<String, dynamic> result = await _peer.sendRequest('_flutter.screenshot').timeout(timeout);
|
final Map<String, dynamic> result = await _peer.sendRequest('_flutter.screenshot').timeout(timeout);
|
||||||
return BASE64.decode(result['screenshot']);
|
return BASE64.decode(result['screenshot']);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user