Add a test of an Android platform view that draws a gradient (#153878)

Closes https://github.com/flutter/flutter/issues/152376.

The gradient, for posterity, looks like this:

<img src="https://github.com/user-attachments/assets/bed9599a-4e16-499c-af79-b51980095e89" width="150">

Let's see if CI agrees!

/cc @johnmccutchan
This commit is contained in:
Matan Lurey 2024-08-23 11:25:40 -07:00 committed by GitHub
parent 327193af9a
commit 5e194383af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 189 additions and 17 deletions

View File

@ -44,6 +44,27 @@ Future<void> runFlutterDriverAndroidTests() async {
'flutter',
<String>[
'drive',
'lib/blue_rectangle_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
],
workingDirectory: path.join(
'dev',
'integration_tests',
'android_driver_test',
),
);
await runCommand(
'flutter',
<String>[
'drive',
'lib/blue_orange_gradient_platform_view_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.

View File

@ -0,0 +1,72 @@
// 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.
@file:Suppress("PackageName")
package com.example.android_driver_test
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Shader
import android.view.View
import android.view.ViewGroup
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class BlueOrangeGradientPlatformViewFactory : PlatformViewFactory(null) {
override fun create(
context: Context,
viewId: Int,
args: Any?
): PlatformView = GradientPlatformView(context)
}
private class GradientPlatformView(
context: Context
) : View(context),
PlatformView {
val paint = Paint()
init {
layoutParams =
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
override fun getView(): View = this
override fun dispose() {}
override fun onDraw(canvas: Canvas) {
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
super.onDraw(canvas)
}
override fun onSizeChanged(
w: Int,
h: Int,
oldw: Int,
oldh: Int
) {
paint.shader =
LinearGradient(
0f,
0f,
w.toFloat(),
h.toFloat(),
intArrayOf(
Color.rgb(0x41, 0x69, 0xE1),
Color.rgb(0xFF, 0xA5, 0x00)
),
null,
Shader.TileMode.CLAMP
)
super.onSizeChanged(w, h, oldw, oldh)
}
}

View File

@ -11,8 +11,18 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// Intentionally do not use GeneratedPluginRegistrant.
flutterEngine
.platformViewsController
.registry
.registerViewFactory("blue_orange_gradient_platform_view", BlueOrangeGradientPlatformViewFactory())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@ -0,0 +1,31 @@
// 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:io' as io;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
void main() {
enableFlutterDriverExtension();
if (kIsWeb || !io.Platform.isAndroid) {
throw UnsupportedError('This app should only run on Android devices.');
}
runApp(const MainApp());
}
final class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AndroidView(viewType: 'blue_orange_gradient_platform_view'),
);
}
}

View File

@ -0,0 +1,47 @@
// 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 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_driver/src/native_driver.dart';
import 'package:test/test.dart';
import '_flutter_goldens_fork.dart';
// TODO(matanlurey): This is done automatically by 'flutter test' but not by
// 'flutter drive'. If we get closer to shipping the native 'flutter drive'
// command, we should look into if 'flutter_test_config.dart', or a similar
// mechanism, can be used to configure this automatically.
void main() async {
await testExecutable(_main);
}
Future<void> _main() async {
// To generate golden files locally, uncomment the following line.
// autoUpdateGoldenFiles = true;
late FlutterDriver flutterDriver;
late NativeDriver nativeDriver;
setUpAll(() async {
flutterDriver = await FlutterDriver.connect();
nativeDriver = await AndroidNativeDriver.connect();
});
tearDownAll(() async {
await nativeDriver.close();
await flutterDriver.close();
});
test(
'should screenshot and match a blue (top left) -> orange (bottom right) gradient',
() async {
await flutterDriver.waitFor(find.byType('AndroidView'));
await expectLater(
nativeDriver.screenshot(),
matchesGoldenFile('android_driver_test.BlueOrangeGradient.png'),
);
},
timeout: Timeout.none,
);
}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' as io;
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_driver/src/native_driver.dart';
import 'package:test/test.dart';
@ -18,34 +16,27 @@ void main() async {
await testExecutable(_main);
}
final bool _isLuciCi = io.Platform.environment['LUCI_CI'] == 'True';
Future<void> _main() async {
// To generate golden files locally, uncomment the following line.
// autoUpdateGoldenFiles = true;
FlutterDriver? flutterDriver;
NativeDriver? nativeDriver;
late FlutterDriver flutterDriver;
late NativeDriver nativeDriver;
setUpAll(() async {
flutterDriver = await FlutterDriver.connect(
// TODO(matanlurey): Workaround log uploading in LUCI not being enabled.
// Default to true on CI because log uploading doesn't work.
// See <https://github.com/flutter/flutter/issues/152775>.
printCommunication: _isLuciCi,
);
flutterDriver = await FlutterDriver.connect();
nativeDriver = await AndroidNativeDriver.connect();
});
tearDownAll(() async {
await nativeDriver?.close();
await flutterDriver?.close();
await nativeDriver.close();
await flutterDriver.close();
});
test('should screenshot and match a full-screen blue rectangle', () async {
await flutterDriver?.waitFor(find.byType('DecoratedBox'));
await flutterDriver.waitFor(find.byType('DecoratedBox'));
await expectLater(
nativeDriver?.screenshot(),
nativeDriver.screenshot(),
matchesGoldenFile('android_driver_test.BlueRectangle.png'),
);
}, timeout: Timeout.none);

View File

@ -124,7 +124,7 @@ final class NaiveLocalFileComparator with GoldenFileComparator {
try {
goldenBytes = await goldenFile.readAsBytes();
} on io.PathNotFoundException {
throw TestFailure('Golden file not found: $golden');
throw TestFailure('Golden file not found: ${goldenFile.path}');
}
if (goldenBytes.length != imageBytes.length) {