Last active
January 20, 2025 21:02
-
-
Save stevenosse/b191d56cb4b75ed8012c3d04c1d80448 to your computer and use it in GitHub Desktop.
testWidgets screenshot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Originally published on: https://gist.github.com/stevsct/fc84fee8bcc3271e2295d99d7c7ae49d | |
/// | |
/// Inspired by https://pub.dev/packages/spot | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
import 'dart:ui' as ui; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:flutter_test/flutter_test.dart'; | |
extension TestScreenshotUtil on WidgetTester { | |
Future<void> takeScreenshot({required String name}) async { | |
final liveElement = 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 directory = Directory('./screenshots'); | |
if (!directory.existsSync()) { | |
directory.createSync(); | |
} | |
final file = File('./screenshots/$name.png'); | |
file.writeAsBytesSync(bytes); | |
} | |
Future<ui.Image> _captureImage(Element element) async { | |
assert(element.renderObject != null); | |
RenderObject renderObject = element.renderObject!; | |
while (!renderObject.isRepaintBoundary) { | |
// ignore: unnecessary_cast | |
renderObject = renderObject.parent! as RenderObject; | |
} | |
assert(!renderObject.debugNeedsPaint); | |
final OffsetLayer layer = renderObject.debugLayer! as OffsetLayer; | |
final ui.Image image = await layer.toImage(renderObject.paintBounds); | |
if (element.renderObject is RenderBox) { | |
final expectedSize = (element.renderObject as RenderBox?)!.size; | |
if (expectedSize.width != image.width || expectedSize.height != image.height) { | |
// ignore: avoid_print | |
print( | |
'Warning: The screenshot captured of ${element.toStringShort()} is ' | |
'larger (${image.width}, ${image.height}) than ' | |
'${element.toStringShort()} (${expectedSize.width}, ${expectedSize.height}) itself.\n' | |
'Wrap the ${element.toStringShort()} in a RepaintBoundary to be able to capture only that layer. ', | |
); | |
} | |
} | |
return image; | |
} | |
} |
Where should I put this code to?
Hi
You could create an utils
folder under your test
directory. Then in your widget test, you'll be able to call the method like this:
await tester.takeScreenshot(name: 'my_test_name');
The file will be located in $PROJECT_DIR/screenshots
Got it, thanks Steven!
How can I get this to take a screenshot of a very tall widget? I find that it only captures the portion of the widget that fits inside some default viewport.
Hi @amake
I looked into your issue and came up with this:
Future<void> takeTallScreenshot({required Widget widget, required String name}) async {
final repaintBoundary = GlobalKey();
final testWidget = SingleChildScrollView(
child: RepaintBoundary(
key: repaintBoundary,
child: widget,
),
);
await pumpWidget(MaterialApp(home: testWidget));
late final Uint8List bytes;
await binding.runAsync(() async {
final image = await _captureImageFromRepaintBoundary(repaintBoundary);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) {
return 'Could not take screenshot';
}
bytes = byteData.buffer.asUint8List();
image.dispose();
});
final directory = Directory('./screenshots');
if (!directory.existsSync()) {
directory.createSync();
}
final file = File('./screenshots/$name.png');
file.writeAsBytesSync(bytes);
}
Future<ui.Image> _captureImageFromRepaintBoundary(GlobalKey repaintBoundaryKey) async {
final renderObject = repaintBoundaryKey.currentContext?.findRenderObject();
if (renderObject is! RenderRepaintBoundary) {
throw Exception('No RenderRepaintBoundary found!');
}
final context = repaintBoundaryKey.currentContext!;
return await renderObject.toImage(pixelRatio: View.of(context).devicePixelRatio);
}
Sample usage:
testWidgets('Capture tall widget screenshot', (WidgetTester tester) async {
final tallWidget = Column(
children: List.generate(
50,
(index) => Container(
height: 100,
color: index.isEven ? Colors.blue : Colors.green,
child: Center(child: Text('Item $index')),
),
),
);
await tester.takeTallScreenshot(
widget: tallWidget,
name: 'tall_widget_screenshot',
);
});
This solution is probably not complete but i think it can still be useful for your usecase
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Where should I put this code to?