diff --git a/dev/bots/suite_runners/run_flutter_driver_android_tests.dart b/dev/bots/suite_runners/run_flutter_driver_android_tests.dart index b37d36d80f..c6c7e7b0dc 100644 --- a/dev/bots/suite_runners/run_flutter_driver_android_tests.dart +++ b/dev/bots/suite_runners/run_flutter_driver_android_tests.dart @@ -44,6 +44,27 @@ Future runFlutterDriverAndroidTests() async { 'flutter', [ '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', + [ + '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. diff --git a/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/BlueOrangeGradientPlatformViewFactory.kt b/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/BlueOrangeGradientPlatformViewFactory.kt new file mode 100644 index 0000000000..1e81046d55 --- /dev/null +++ b/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/BlueOrangeGradientPlatformViewFactory.kt @@ -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) + } +} diff --git a/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/MainActivity.kt b/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/MainActivity.kt index 6c2d815128..3fd1a0cb98 100644 --- a/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/MainActivity.kt +++ b/dev/integration_tests/android_driver_test/android/app/src/main/kotlin/com/example/android_driver_test/MainActivity.kt @@ -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) diff --git a/dev/integration_tests/android_driver_test/lib/blue_orange_gradient_platform_view_main.dart b/dev/integration_tests/android_driver_test/lib/blue_orange_gradient_platform_view_main.dart new file mode 100644 index 0000000000..75829c782d --- /dev/null +++ b/dev/integration_tests/android_driver_test/lib/blue_orange_gradient_platform_view_main.dart @@ -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'), + ); + } +} diff --git a/dev/integration_tests/android_driver_test/lib/main.dart b/dev/integration_tests/android_driver_test/lib/blue_rectangle_main.dart similarity index 100% rename from dev/integration_tests/android_driver_test/lib/main.dart rename to dev/integration_tests/android_driver_test/lib/blue_rectangle_main.dart diff --git a/dev/integration_tests/android_driver_test/test_driver/blue_orange_gradient_platform_view_main_test.dart b/dev/integration_tests/android_driver_test/test_driver/blue_orange_gradient_platform_view_main_test.dart new file mode 100644 index 0000000000..3284547a54 --- /dev/null +++ b/dev/integration_tests/android_driver_test/test_driver/blue_orange_gradient_platform_view_main_test.dart @@ -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 _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, + ); +} diff --git a/dev/integration_tests/android_driver_test/test_driver/main_test.dart b/dev/integration_tests/android_driver_test/test_driver/blue_rectangle_main_test.dart similarity index 64% rename from dev/integration_tests/android_driver_test/test_driver/main_test.dart rename to dev/integration_tests/android_driver_test/test_driver/blue_rectangle_main_test.dart index d2e70a69c9..49e681368e 100644 --- a/dev/integration_tests/android_driver_test/test_driver/main_test.dart +++ b/dev/integration_tests/android_driver_test/test_driver/blue_rectangle_main_test.dart @@ -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 _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 . - 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); diff --git a/packages/flutter_driver/lib/src/native/goldens.dart b/packages/flutter_driver/lib/src/native/goldens.dart index 01c39d1904..1c4e72bd9a 100644 --- a/packages/flutter_driver/lib/src/native/goldens.dart +++ b/packages/flutter_driver/lib/src/native/goldens.dart @@ -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) {