Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 725c141

Browse files
Fix screenshot testing for flutter web integration_test (#117114)
* Fix screenshot testing for flutter web integration_test * update packages * fix method signature and todo * Run tests on CI * fix type * remove silences * Add docs * fix comment * fix whitespace * review comments
1 parent bd482eb commit 725c141

File tree

9 files changed

+94
-22
lines changed

9 files changed

+94
-22
lines changed

dev/bots/test.dart

+18-1
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ Future<void> _runWebUnitTests(String webRenderer) async {
11381138
/// Coarse-grained integration tests running on the Web.
11391139
Future<void> _runWebLongRunningTests() async {
11401140
final List<ShardRunner> tests = <ShardRunner>[
1141-
for (String buildMode in _kAllBuildModes)
1141+
for (String buildMode in _kAllBuildModes) ...<ShardRunner>[
11421142
() => _runFlutterDriverWebTest(
11431143
testAppDirectory: path.join('packages', 'integration_test', 'example'),
11441144
target: path.join('test_driver', 'failure.dart'),
@@ -1148,6 +1148,21 @@ Future<void> _runWebLongRunningTests() async {
11481148
// logs. To avoid confusion, silence browser output.
11491149
silenceBrowserOutput: true,
11501150
),
1151+
() => _runFlutterDriverWebTest(
1152+
testAppDirectory: path.join('packages', 'integration_test', 'example'),
1153+
target: path.join('integration_test', 'example_test.dart'),
1154+
driver: path.join('test_driver', 'integration_test.dart'),
1155+
buildMode: buildMode,
1156+
renderer: 'canvaskit',
1157+
),
1158+
() => _runFlutterDriverWebTest(
1159+
testAppDirectory: path.join('packages', 'integration_test', 'example'),
1160+
target: path.join('integration_test', 'extended_test.dart'),
1161+
driver: path.join('test_driver', 'extended_integration_test.dart'),
1162+
buildMode: buildMode,
1163+
renderer: 'canvaskit',
1164+
),
1165+
],
11511166

11521167
// This test specifically tests how images are loaded in HTML mode, so we don't run it in CanvasKit mode.
11531168
() => _runWebE2eTest('image_loading_integration', buildMode: 'debug', renderer: 'html'),
@@ -1281,6 +1296,7 @@ Future<void> _runFlutterDriverWebTest({
12811296
required String buildMode,
12821297
required String renderer,
12831298
required String testAppDirectory,
1299+
String? driver,
12841300
bool expectFailure = false,
12851301
bool silenceBrowserOutput = false,
12861302
}) async {
@@ -1295,6 +1311,7 @@ Future<void> _runFlutterDriverWebTest({
12951311
<String>[
12961312
...flutterTestArgs,
12971313
'drive',
1314+
if (driver != null) '--driver=$driver',
12981315
'--target=$target',
12991316
'--browser-name=chrome',
13001317
'--no-sound-null-safety',

dev/integration_tests/web_e2e_tests/lib/screenshot_support.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Future<void> runTestWithScreenshots({
2525

2626
test.integrationDriver(
2727
driver: driver,
28-
onScreenshot: (String screenshotName, List<int> screenshotBytes) async {
28+
onScreenshot: (String screenshotName, List<int> screenshotBytes, [Map<String, Object?>? args]) async {
2929
// TODO(yjbanov): implement, see https://github.com/flutter/flutter/issues/86120
3030
return true;
3131
},

packages/integration_test/example/integration_test/_extended_test_web.dart

+29-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import 'package:integration_test/integration_test.dart';
1717
import 'package:integration_test_example/main.dart' as app;
1818

1919
void main() {
20-
final IntegrationTestWidgetsFlutterBinding binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
20+
final IntegrationTestWidgetsFlutterBinding binding =
21+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
2122

2223
testWidgets('verify text', (WidgetTester tester) async {
2324
// Build our app and trigger a frame.
@@ -27,7 +28,18 @@ void main() {
2728
await tester.pumpAndSettle();
2829

2930
// Take a screenshot.
30-
await binding.takeScreenshot('platform_name');
31+
await binding.takeScreenshot(
32+
'platform_name',
33+
// The optional parameter 'args' can be used to pass values to the
34+
// [integrationDriver.onScreenshot] handler
35+
// (see test_driver/extended_integration_test.dart). For example, you
36+
// could look up environment variables in this test that were passed to
37+
// the run command via `--dart-define=`, and then pass the values to the
38+
// [integrationDriver.onScreenshot] handler through this 'args' map.
39+
<String, Object?>{
40+
'someArgumentKey': 'someArgumentValue',
41+
},
42+
);
3143

3244
// Verify that platform is retrieved.
3345
expect(
@@ -49,7 +61,20 @@ void main() {
4961
await tester.pumpAndSettle();
5062

5163
// Multiple methods can take screenshots. Screenshots are taken with the
52-
// same order the methods run.
53-
await binding.takeScreenshot('platform_name_2');
64+
// same order the methods run. We pass an argument that can be looked up
65+
// from the [onScreenshot] handler in
66+
// [test_driver/extended_integration_test.dart].
67+
await binding.takeScreenshot(
68+
'platform_name_2',
69+
// The optional parameter 'args' can be used to pass values to the
70+
// [integrationDriver.onScreenshot] handler
71+
// (see test_driver/extended_integration_test.dart). For example, you
72+
// could look up environment variables in this test that were passed to
73+
// the run command via `--dart-define=`, and then pass the values to the
74+
// [integrationDriver.onScreenshot] handler through this 'args' map.
75+
<String, Object?>{
76+
'someArgumentKey': 'someArgumentValue',
77+
},
78+
);
5479
});
5580
}

packages/integration_test/example/test_driver/extended_integration_test.dart

+13-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,20 @@ Future<void> main() async {
99
final FlutterDriver driver = await FlutterDriver.connect();
1010
await integrationDriver(
1111
driver: driver,
12-
onScreenshot: (String screenshotName, List<int> screenshotBytes) async {
12+
onScreenshot: (
13+
String screenshotName,
14+
List<int> screenshotBytes, [
15+
Map<String, Object?>? args,
16+
]) async {
1317
// Return false if the screenshot is invalid.
18+
// TODO(yjbanov): implement, see https://github.com/flutter/flutter/issues/86120
19+
20+
// Here is an example of using an argument that was passed in via the
21+
// optional 'args' Map.
22+
if (args != null) {
23+
final String? someArgumentValue = args['someArgumentKey'] as String?;
24+
return someArgumentValue != null;
25+
}
1426
return true;
1527
},
1628
);

packages/integration_test/lib/_callback_io.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class IOCallbackManager implements CallbackManager {
8686
}
8787

8888
@override
89-
Future<Map<String, dynamic>> takeScreenshot(String screenshot) async {
89+
Future<Map<String, dynamic>> takeScreenshot(String screenshot, [Map<String, Object?>? args]) async {
90+
assert(args == null, '[args] handling has not been implemented for this platform');
9091
if (Platform.isAndroid && !_isSurfaceRendered) {
9192
throw StateError('Call convertFlutterSurfaceToImage() before taking a screenshot');
9293
}

packages/integration_test/lib/_callback_web.dart

+7-4
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ class WebCallbackManager implements CallbackManager {
4444
///
4545
/// See: https://www.w3.org/TR/webdriver/#screen-capture.
4646
@override
47-
Future<Map<String, dynamic>> takeScreenshot(String screenshotName) async {
48-
await _sendWebDriverCommand(WebDriverCommand.screenshot(screenshotName));
49-
// Flutter Web doesn't provide the bytes.
50-
return const <String, dynamic>{'bytes': <int>[]};
47+
Future<Map<String, dynamic>> takeScreenshot(String screenshotName, [Map<String, Object?>? args]) async {
48+
await _sendWebDriverCommand(WebDriverCommand.screenshot(screenshotName, args));
49+
return <String, dynamic>{
50+
'screenshotName': screenshotName,
51+
// Flutter Web doesn't provide the bytes.
52+
'bytes': <int>[]
53+
};
5154
}
5255

5356
@override

packages/integration_test/lib/common.dart

+15-6
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@ import 'dart:convert';
77

88
/// A callback to use with [integrationDriver].
99
///
10-
/// The callback receives the name of screenshot passed to `binding.takeScreenshot(<name>)` and
11-
/// a PNG byte buffer.
10+
/// The callback receives the name of screenshot passed to `binding.takeScreenshot(<name>)`,
11+
/// a PNG byte buffer representing the screenshot, and an optional `Map` of arguments.
1212
///
1313
/// The callback returns `true` if the test passes or `false` otherwise.
1414
///
1515
/// You can use this callback to store the bytes locally in a file or upload them to a service
1616
/// that compares the image against a gold or baseline version.
1717
///
18+
/// The optional `Map` of arguments can be passed from the
19+
/// `binding.takeScreenshot(<name>, <args>)` callsite in the integration test,
20+
/// and then the arguments can be used in the `onScreenshot` handler that is defined by
21+
/// the Flutter driver. This `Map` should only contain values that can be serialized
22+
/// to JSON.
23+
///
1824
/// Since the function is executed on the host driving the test, you can access any environment
1925
/// variable from it.
20-
typedef ScreenshotCallback = Future<bool> Function(String name, List<int> image);
26+
typedef ScreenshotCallback = Future<bool> Function(String name, List<int> image, [Map<String, Object?>? args]);
2127

2228
/// Classes shared between `integration_test.dart` and `flutter drive` based
2329
/// adoptor (ex: `integration_test_driver.dart`).
@@ -247,9 +253,12 @@ class WebDriverCommand {
247253
values = <String, dynamic>{};
248254

249255
/// Constructor for [WebDriverCommandType.noop] screenshot.
250-
WebDriverCommand.screenshot(String screenshotName)
256+
WebDriverCommand.screenshot(String screenshotName, [Map<String, Object?>? args])
251257
: type = WebDriverCommandType.screenshot,
252-
values = <String, dynamic>{'screenshot_name': screenshotName};
258+
values = <String, dynamic>{
259+
'screenshot_name': screenshotName,
260+
if (args != null) 'args': args,
261+
};
253262

254263
/// Type of the [WebDriverCommand].
255264
///
@@ -286,7 +295,7 @@ abstract class CallbackManager {
286295

287296
/// Takes a screenshot of the application.
288297
/// Returns the data that is sent back to the host.
289-
Future<Map<String, dynamic>> takeScreenshot(String screenshot);
298+
Future<Map<String, dynamic>> takeScreenshot(String screenshot, [Map<String, Object?>? args]);
290299

291300
/// Android only. Converts the Flutter surface to an image view.
292301
Future<void> convertFlutterSurfaceToImage();

packages/integration_test/lib/integration_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,10 @@ https://flutter.dev/docs/testing/integration-tests#testing-on-firebase-test-lab
184184
///
185185
/// On Android, you need to call `convertFlutterSurfaceToImage()`, and
186186
/// pump a frame before taking a screenshot.
187-
Future<List<int>> takeScreenshot(String screenshotName) async {
187+
Future<List<int>> takeScreenshot(String screenshotName, [Map<String, Object?>? args]) async {
188188
reportData ??= <String, dynamic>{};
189189
reportData!['screenshots'] ??= <dynamic>[];
190-
final Map<String, dynamic> data = await callbackManager.takeScreenshot(screenshotName);
190+
final Map<String, dynamic> data = await callbackManager.takeScreenshot(screenshotName, args);
191191
assert(data.containsKey('bytes'));
192192

193193
(reportData!['screenshots']! as List<dynamic>).add(data);

packages/integration_test/lib/integration_test_driver_extended.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ Future<void> integrationDriver(
5050
// error if it's used as a message for requestData.
5151
String jsonResponse = await driver.requestData(DriverTestMessage.pending().toString());
5252

53+
final Map<String, bool> onScreenshotResults = <String, bool>{};
54+
5355
Response response = Response.fromJson(jsonResponse);
5456

5557
// Until `integration_test` returns a [WebDriverCommandType.noop], keep
@@ -63,8 +65,10 @@ Future<void> integrationDriver(
6365
// Use `driver.screenshot()` method to get a screenshot of the web page.
6466
final List<int> screenshotImage = await driver.screenshot();
6567
final String screenshotName = response.data!['screenshot_name']! as String;
68+
final Map<String, Object?>? args = (response.data!['args'] as Map<String, Object?>?)?.cast<String, Object?>();
6669

67-
final bool screenshotSuccess = await onScreenshot!(screenshotName, screenshotImage);
70+
final bool screenshotSuccess = await onScreenshot!(screenshotName, screenshotImage, args);
71+
onScreenshotResults[screenshotName] = screenshotSuccess;
6872
if (screenshotSuccess) {
6973
jsonResponse = await driver.requestData(DriverTestMessage.complete().toString());
7074
} else {
@@ -104,7 +108,8 @@ Future<void> integrationDriver(
104108

105109
bool ok = false;
106110
try {
107-
ok = await onScreenshot(screenshotName, screenshotBytes.cast<int>());
111+
ok = onScreenshotResults[screenshotName] ??
112+
await onScreenshot(screenshotName, screenshotBytes.cast<int>());
108113
} catch (exception) {
109114
throw StateError(
110115
'Screenshot failure:\n'

0 commit comments

Comments
 (0)