takeScreenshot function

Future<Screenshot> takeScreenshot({
  1. Element? element,
  2. WidgetSnapshot<Widget>? snapshot,
  3. WidgetSelector<Widget>? selector,
  4. 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);
}