blob: f4c2d35319e576225045a36797060bf2dc29b3d8 [file] [log] [blame]
// 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() as TestWidgetsFlutterBinding;
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());
}