Unverified Commit 464e751a authored by Harry Terkelsen's avatar Harry Terkelsen Committed by GitHub

Reland "Use Layer.toImage for golden tests on CanvasKit" (#136918)

Relands https://github.com/flutter/flutter/pull/135249

A golden test was failing in post submit in the previous PR
parent 9beb98aa
...@@ -299,6 +299,16 @@ class DefaultWebGoldenComparator extends WebGoldenComparator { ...@@ -299,6 +299,16 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
Future<void> update(double width, double height, Uri golden) { Future<void> update(double width, double height, Uri golden) {
throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.'); throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.');
} }
@override
Future<bool> compareBytes(Uint8List bytes, Uri golden) {
throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.');
}
@override
Future<void> updateBytes(Uint8List bytes, Uri golden) {
throw UnsupportedError('DefaultWebGoldenComparator is only supported on the web.');
}
} }
/// Reads the red value out of a 32 bit rgba pixel. /// Reads the red value out of a 32 bit rgba pixel.
......
...@@ -80,4 +80,30 @@ class DefaultWebGoldenComparator extends WebGoldenComparator { ...@@ -80,4 +80,30 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
// Update is handled on the server side, just use the same logic here // Update is handled on the server side, just use the same logic here
await compare(width, height, golden); await compare(width, height, golden);
} }
@override
Future<bool> compareBytes(Uint8List bytes, Uri golden) async {
final String key = golden.toString();
final String bytesEncoded = base64.encode(bytes);
final html.HttpRequest request = await html.HttpRequest.request(
'flutter_goldens',
method: 'POST',
sendData: json.encode(<String, Object>{
'testUri': testUri.toString(),
'key': key,
'bytes': bytesEncoded,
}),
);
final String response = request.response as String;
if (response == 'true') {
return true;
}
fail(response);
}
@override
Future<void> updateBytes(Uint8List bytes, Uri golden) async {
// Update is handled on the server side, just use the same logic here
await compareBytes(bytes, golden);
}
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:matcher/expect.dart'; import 'package:matcher/expect.dart';
...@@ -61,7 +62,39 @@ class MatchesGoldenFile extends AsyncMatcher { ...@@ -61,7 +62,39 @@ class MatchesGoldenFile extends AsyncMatcher {
final ui.FlutterView view = binding.platformDispatcher.implicitView!; final ui.FlutterView view = binding.platformDispatcher.implicitView!;
final RenderView renderView = binding.renderViews.firstWhere((RenderView r) => r.flutterView == view); final RenderView renderView = binding.renderViews.firstWhere((RenderView r) => r.flutterView == view);
// Unlike `flutter_tester`, we don't have the ability to render an element if (isCanvasKit) {
// In CanvasKit, use Layer.toImage to generate the screenshot.
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
return binding.runAsync<String?>(() async {
assert(element.renderObject != null);
RenderObject renderObject = element.renderObject!;
while (!renderObject.isRepaintBoundary) {
renderObject = renderObject.parent!;
}
assert(!renderObject.debugNeedsPaint);
final OffsetLayer layer = renderObject.debugLayer! as OffsetLayer;
final ui.Image image = await layer.toImage(renderObject.paintBounds);
try {
final ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png);
if (bytes == null) {
return 'could not encode screenshot.';
}
if (autoUpdateGoldenFiles) {
await webGoldenComparator.updateBytes(bytes.buffer.asUint8List(), key);
return null;
}
try {
final bool success = await webGoldenComparator.compareBytes(bytes.buffer.asUint8List(), key);
return success ? null : 'does not match';
} on TestFailure catch (ex) {
return ex.message;
}
} finally {
image.dispose();
}
});
} else {
// In the HTML renderer, we don't have the ability to render an element
// to an image directly. Instead, we will use `window.render()` to render // 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 // 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. // requesting it to take a screenshot through the browser's debug interface.
...@@ -81,6 +114,7 @@ class MatchesGoldenFile extends AsyncMatcher { ...@@ -81,6 +114,7 @@ class MatchesGoldenFile extends AsyncMatcher {
_renderElement(view, renderView); _renderElement(view, renderView);
return result; return result;
} }
}
@override @override
Description describe(Description description) { Description describe(Description description) {
......
...@@ -185,6 +185,34 @@ abstract class WebGoldenComparator { ...@@ -185,6 +185,34 @@ abstract class WebGoldenComparator {
/// is left up to the implementation class. /// is left up to the implementation class.
Future<void> update(double width, double height, Uri golden); Future<void> update(double width, double height, Uri golden);
/// Compares the pixels of decoded png [bytes] against the golden file
/// identified by [golden].
///
/// The returned future completes with a boolean value that indicates whether
/// the pixels rendered on screen match the golden file's pixels.
///
/// In the case of comparison mismatch, the comparator may choose to throw a
/// [TestFailure] if it wants to control the failure message, often in the
/// form of a [ComparisonResult] that provides detailed information about the
/// mismatch.
///
/// The method by which [golden] is located and by which its bytes are loaded
/// is left up to the implementation class. For instance, some implementations
/// may load files from the local file system, whereas others may load files
/// over the network or from a remote repository.
Future<bool> compareBytes(Uint8List bytes, Uri golden);
/// Compares the pixels of decoded png [bytes] against the golden file
/// identified by [golden].
///
/// This will be invoked in lieu of [compareBytes] when [autoUpdateGoldenFiles]
/// is `true` (which gets set automatically by the test framework when the
/// user runs `flutter test --update-goldens --platform=chrome`).
///
/// The method by which [golden] is located and by which its bytes are written
/// is left up to the implementation class.
Future<void> updateBytes(Uint8List bytes, Uri golden);
/// Returns a new golden file [Uri] to incorporate any [version] number with /// Returns a new golden file [Uri] to incorporate any [version] number with
/// the [key]. /// the [key].
/// ///
...@@ -298,12 +326,7 @@ class _TrivialWebGoldenComparator implements WebGoldenComparator { ...@@ -298,12 +326,7 @@ class _TrivialWebGoldenComparator implements WebGoldenComparator {
@override @override
Future<bool> compare(double width, double height, Uri golden) { Future<bool> compare(double width, double height, Uri golden) {
// Ideally we would use markTestSkipped here but in some situations, return _warnAboutSkipping(golden);
// comparators are called outside of tests.
// See also: https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
print('Golden comparison requested for "$golden"; skipping...');
return Future<bool>.value(true);
} }
@override @override
...@@ -315,6 +338,25 @@ class _TrivialWebGoldenComparator implements WebGoldenComparator { ...@@ -315,6 +338,25 @@ class _TrivialWebGoldenComparator implements WebGoldenComparator {
Uri getTestUri(Uri key, int? version) { Uri getTestUri(Uri key, int? version) {
return key; return key;
} }
@override
Future<bool> compareBytes(Uint8List bytes, Uri golden) {
return _warnAboutSkipping(golden);
}
@override
Future<void> updateBytes(Uint8List bytes, Uri golden) {
throw StateError('webGoldenComparator has not been initialized');
}
Future<bool> _warnAboutSkipping(Uri golden) {
// Ideally we would use markTestSkipped here but in some situations,
// comparators are called outside of tests.
// See also: https://github.com/flutter/flutter/issues/91285
// ignore: avoid_print
print('Golden comparison requested for "$golden"; skipping...');
return Future<bool>.value(true);
}
} }
/// The result of a pixel comparison test. /// The result of a pixel comparison test.
......
...@@ -336,10 +336,16 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -336,10 +336,16 @@ class FlutterWebPlatform extends PlatformPlugin {
final Map<String, Object?> body = json.decode(await request.readAsString()) as Map<String, Object?>; final Map<String, Object?> body = json.decode(await request.readAsString()) as Map<String, Object?>;
final Uri goldenKey = Uri.parse(body['key']! as String); final Uri goldenKey = Uri.parse(body['key']! as String);
final Uri testUri = Uri.parse(body['testUri']! as String); final Uri testUri = Uri.parse(body['testUri']! as String);
final num width = body['width']! as num; final num? width = body['width'] as num?;
final num height = body['height']! as num; final num? height = body['height'] as num?;
Uint8List bytes; Uint8List bytes;
if (body.containsKey('bytes')) {
bytes = base64.decode(body['bytes']! as String);
} else {
// TODO(hterkelsen): Do not use browser screenshots for testing on the
// web once we transition off the HTML renderer. See:
// https://github.com/flutter/flutter/issues/135700
try { try {
final ChromeTab chromeTab = (await _browserManager!._browser.chromeConnection.getTab((ChromeTab tab) { final ChromeTab chromeTab = (await _browserManager!._browser.chromeConnection.getTab((ChromeTab tab) {
return tab.url.contains(_browserManager!._browser.url!); return tab.url.contains(_browserManager!._browser.url!);
...@@ -354,8 +360,8 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -354,8 +360,8 @@ class FlutterWebPlatform extends PlatformPlugin {
'clip': <String, Object>{ 'clip': <String, Object>{
'x': 0.0, 'x': 0.0,
'y': 0.0, 'y': 0.0,
'width': width.toDouble(), 'width': width!.toDouble(),
'height': height.toDouble(), 'height': height!.toDouble(),
'scale': 1.0, 'scale': 1.0,
}, },
}); });
...@@ -367,7 +373,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -367,7 +373,7 @@ class FlutterWebPlatform extends PlatformPlugin {
_logger.printError('Caught FormatException: $ex'); _logger.printError('Caught FormatException: $ex');
return shelf.Response.ok('Caught exception: $ex'); return shelf.Response.ok('Caught exception: $ex');
} }
}
final String? errorMessage = await _testGoldenComparator.compareGoldens(testUri, bytes, goldenKey, updateGoldens); final String? errorMessage = await _testGoldenComparator.compareGoldens(testUri, bytes, goldenKey, updateGoldens);
return shelf.Response.ok(errorMessage ?? 'true'); return shelf.Response.ok(errorMessage ?? 'true');
} else { } else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment