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
This commit is contained in:
parent
9beb98aabf
commit
464e751a78
@ -299,6 +299,16 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
|
||||
Future<void> update(double width, double height, Uri golden) {
|
||||
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.
|
||||
|
@ -80,4 +80,30 @@ class DefaultWebGoldenComparator extends WebGoldenComparator {
|
||||
// Update is handled on the server side, just use the same logic here
|
||||
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 @@
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:matcher/expect.dart';
|
||||
@ -61,25 +62,58 @@ class MatchesGoldenFile extends AsyncMatcher {
|
||||
final ui.FlutterView view = binding.platformDispatcher.implicitView!;
|
||||
final RenderView renderView = binding.renderViews.firstWhere((RenderView r) => r.flutterView == view);
|
||||
|
||||
// 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(view, 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;
|
||||
}
|
||||
});
|
||||
_renderElement(view, renderView);
|
||||
return result;
|
||||
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
|
||||
// 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(view, 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;
|
||||
}
|
||||
});
|
||||
_renderElement(view, renderView);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -185,6 +185,34 @@ abstract class WebGoldenComparator {
|
||||
/// is left up to the implementation class.
|
||||
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
|
||||
/// the [key].
|
||||
///
|
||||
@ -298,12 +326,7 @@ class _TrivialWebGoldenComparator implements WebGoldenComparator {
|
||||
|
||||
@override
|
||||
Future<bool> compare(double width, double height, 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);
|
||||
return _warnAboutSkipping(golden);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -315,6 +338,25 @@ class _TrivialWebGoldenComparator implements WebGoldenComparator {
|
||||
Uri getTestUri(Uri key, int? version) {
|
||||
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.
|
||||
|
@ -336,38 +336,44 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
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 testUri = Uri.parse(body['testUri']! as String);
|
||||
final num width = body['width']! as num;
|
||||
final num height = body['height']! as num;
|
||||
final num? width = body['width'] as num?;
|
||||
final num? height = body['height'] as num?;
|
||||
Uint8List bytes;
|
||||
|
||||
try {
|
||||
final ChromeTab chromeTab = (await _browserManager!._browser.chromeConnection.getTab((ChromeTab tab) {
|
||||
return tab.url.contains(_browserManager!._browser.url!);
|
||||
}))!;
|
||||
final WipConnection connection = await chromeTab.connect();
|
||||
final WipResponse response = await connection.sendCommand('Page.captureScreenshot', <String, Object>{
|
||||
// Clip the screenshot to include only the element.
|
||||
// Prior to taking a screenshot, we are calling `window.render()` in
|
||||
// `_matchers_web.dart` to only render the element on screen. That
|
||||
// will make sure that the element will always be displayed on the
|
||||
// origin of the screen.
|
||||
'clip': <String, Object>{
|
||||
'x': 0.0,
|
||||
'y': 0.0,
|
||||
'width': width.toDouble(),
|
||||
'height': height.toDouble(),
|
||||
'scale': 1.0,
|
||||
},
|
||||
});
|
||||
bytes = base64.decode(response.result!['data'] as String);
|
||||
} on WipError catch (ex) {
|
||||
_logger.printError('Caught WIPError: $ex');
|
||||
return shelf.Response.ok('WIP error: $ex');
|
||||
} on FormatException catch (ex) {
|
||||
_logger.printError('Caught FormatException: $ex');
|
||||
return shelf.Response.ok('Caught exception: $ex');
|
||||
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 {
|
||||
final ChromeTab chromeTab = (await _browserManager!._browser.chromeConnection.getTab((ChromeTab tab) {
|
||||
return tab.url.contains(_browserManager!._browser.url!);
|
||||
}))!;
|
||||
final WipConnection connection = await chromeTab.connect();
|
||||
final WipResponse response = await connection.sendCommand('Page.captureScreenshot', <String, Object>{
|
||||
// Clip the screenshot to include only the element.
|
||||
// Prior to taking a screenshot, we are calling `window.render()` in
|
||||
// `_matchers_web.dart` to only render the element on screen. That
|
||||
// will make sure that the element will always be displayed on the
|
||||
// origin of the screen.
|
||||
'clip': <String, Object>{
|
||||
'x': 0.0,
|
||||
'y': 0.0,
|
||||
'width': width!.toDouble(),
|
||||
'height': height!.toDouble(),
|
||||
'scale': 1.0,
|
||||
},
|
||||
});
|
||||
bytes = base64.decode(response.result!['data'] as String);
|
||||
} on WipError catch (ex) {
|
||||
_logger.printError('Caught WIPError: $ex');
|
||||
return shelf.Response.ok('WIP error: $ex');
|
||||
} on FormatException catch (ex) {
|
||||
_logger.printError('Caught FormatException: $ex');
|
||||
return shelf.Response.ok('Caught exception: $ex');
|
||||
}
|
||||
}
|
||||
|
||||
final String? errorMessage = await _testGoldenComparator.compareGoldens(testUri, bytes, goldenKey, updateGoldens);
|
||||
return shelf.Response.ok(errorMessage ?? 'true');
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user