// Copyright 2014 The Flutter 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:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:test_api/src/expect/async_matcher.dart'; // ignore: implementation_imports import 'package:test_api/test_api.dart'; // ignore: deprecated_member_use import 'binding.dart'; import 'finders.dart'; import 'goldens.dart'; /// An unsupported method that exists for API compatibility. Future<ui.Image> captureImage(Element element) { throw UnsupportedError('captureImage is not supported on the web.'); } /// The matcher created by [matchesGoldenFile]. This class is enabled when the /// test is running in a web browser using conditional import. class MatchesGoldenFile extends AsyncMatcher { /// Creates an instance of [MatchesGoldenFile]. Called by [matchesGoldenFile]. const MatchesGoldenFile(this.key, this.version); /// Creates an instance of [MatchesGoldenFile]. Called by [matchesGoldenFile]. MatchesGoldenFile.forStringPath(String path, this.version) : key = Uri.parse(path); /// The [key] to the golden image. final Uri key; /// The [version] of the golden image. final int? version; @override Future<String?> matchAsync(dynamic item) async { if (item is! Finder) { return 'web goldens only supports matching finders.'; } final Iterable<Element> elements = item.evaluate(); if (elements.isEmpty) { return 'could not be rendered because no widget was found'; } else if (elements.length > 1) { return 'matched too many widgets'; } final Element element = elements.single; final RenderObject renderObject = _findRepaintBoundary(element); final Size size = renderObject.paintBounds.size; final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final Element e = binding.renderViewElement!; // Unlike `flutter_tester`, we don't have the ability to render an element // to an image directly. Instead, we will use `window.render()` to render // only the element being requested, and send a request to the test server // requesting it to take a screenshot through the browser's debug interface. _renderElement(binding.window, renderObject); final String? result = await binding.runAsync<String?>(() async { if (autoUpdateGoldenFiles) { await webGoldenComparator.update(size.width, size.height, key); return null; } try { final bool success = await webGoldenComparator.compare(size.width, size.height, key); return success ? null : 'does not match'; } on TestFailure catch (ex) { return ex.message; } }, additionalTime: const Duration(seconds: 22)); _renderElement(binding.window, _findRepaintBoundary(e)); return result; } @override Description describe(Description description) { final Uri testNameUri = webGoldenComparator.getTestUri(key, version); return description.add('one widget whose rasterized image matches golden image "$testNameUri"'); } } RenderObject _findRepaintBoundary(Element element) { assert(element.renderObject != null); RenderObject renderObject = element.renderObject!; while (!renderObject.isRepaintBoundary) { renderObject = renderObject.parent! as RenderObject; } return renderObject; } void _renderElement(ui.FlutterView window, RenderObject renderObject) { assert(renderObject.debugLayer != null); final Layer layer = renderObject.debugLayer!; final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); if (layer is OffsetLayer) { sceneBuilder.pushOffset(-layer.offset.dx, -layer.offset.dy); } // ignore: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member layer.updateSubtreeNeedsAddToScene(); // ignore: invalid_use_of_protected_member layer.addToScene(sceneBuilder); sceneBuilder.pop(); window.render(sceneBuilder.build()); }