takeScreenshot function
Future<Screenshot>
takeScreenshot({
- Element? element,
- WidgetSnapshot<
Widget> ? snapshot, - WidgetSelector<
Widget> ? selector, - String? name,
Takes a screenshot of the entire screen or a single widget.
Provide a selector
, snapshot
or element
to take a screenshot of.
When the screenshot is taken from a larger than just your widget, wrap your
widget with a RepaintBoundary to indicate where the screenshot should be
taken.
Use name
to make it easier to identify the screenshot in the file system.
By default, a random name is generated prefixed with the test file name and
line number.
Implementation
Future<Screenshot> takeScreenshot({
Element? element,
WidgetSnapshot? snapshot,
WidgetSelector? selector,
String? name,
}) async {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
final Frame? frame = _caller();
// Element that is currently active in the widget tree, to take a screenshot of
final Element liveElement = () {
if (selector != null) {
// taking a fresh snapshot guarantees an element that is currently in the
// tree and can be screenshotted
final snapshot = selector.snapshot().existsOnce();
return snapshot.element;
}
if (snapshot != null) {
final elements = snapshot.discovered;
if (elements.length > 1) {
throw StateError(
'Screenshots can only be taken of a single elements. '
'The snapshot of ${snapshot.selector} contains ${elements.length} elements. '
'Use a more specific selector to narrow down the scope of the screenshot.',
);
}
final element = elements.first.element;
if (!element.mounted) {
throw StateError(
'Can not take a screenshot of snapshot $snapshot, because it is not mounted anymore. '
'Only Elements that are currently mounted can be screenshotted.',
);
}
if (snapshot.discoveredWidget != element.widget) {
throw StateError(
'Can not take a screenshot of snapshot $snapshot, because the Element has been updated since the snapshot was taken. '
'This happens when the widget tree is rebuilt.',
);
}
return element;
}
if (element != null) {
if (!element.mounted) {
throw StateError(
'Can not take a screenshot of Element $element, because it is not mounted anymore. '
'Only Elements that are currently mounted can be screenshotted.',
);
}
return element;
}
// fallback to screenshotting the entire app
// Deprecated, but as of today there is no multi window support for widget tests
// ignore: deprecated_member_use
return binding.renderViewElement!;
}();
late final Uint8List bytes;
await binding.runAsync(() async {
final image = await _captureImage(liveElement);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) {
return 'Could not take screenshot';
}
bytes = byteData.buffer.asUint8List();
image.dispose();
});
final spotTempDir = Directory.systemTemp.directory('spot');
if (!spotTempDir.existsSync()) {
spotTempDir.createSync();
}
String callerFileName() {
final file = frame?.uri.pathSegments.last.replaceFirst('.dart', '');
final line = frame?.line;
if (file != null && line != null) {
return '$file:$line';
}
if (file != null) {
return file;
}
return 'unknown';
}
final String screenshotFileName = () {
final String n;
if (name != null) {
// escape /
n = Uri.encodeQueryComponent(name);
} else {
n = callerFileName();
}
// always append a unique id to avoid name collisions
final uniqueId = nanoid(length: 5);
return '$n-$uniqueId.png';
}();
final file = spotTempDir.file(screenshotFileName);
file.writeAsBytesSync(bytes);
// ignore: avoid_print
core.print(
'Screenshot file://${file.path}\n'
' taken at ${frame?.member} ${frame?.uri}:${frame?.line}:${frame?.column}',
);
return Screenshot(file: file, initiator: frame);
}