Add and use an integration test with native(ADB) screenshots (#152326)
Closes https://github.com/flutter/flutter/issues/152325. This PR is large due to generate `flutter create --platforms android`. A quick summary: - Moves the integration test from `packages/flutter_driver/test` to `dev/integration_tests` - Created a sample Flutter app that draws a blue rectangle - Forked a subset of `package:flutter_goldens` that will work on the standalone Dart VM - Forked a subset of `goldens.dart` (from `flutter_test`) to `src/native/goldens.dart` (i.e. `matchesGoldenFile`) This ... works locally, but as usual I have no idea if it will work on Skia Gold so let's roll some dice.
This commit is contained in:
@@ -3,15 +3,22 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import '../run_command.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
Future<void> runFlutterDriverAndroidTests() async {
|
||||
print('Running Flutter Driver Android tests...');
|
||||
|
||||
await runDartTest(
|
||||
path.join(flutterRoot, 'packages', 'flutter_driver'),
|
||||
testPaths: <String>[
|
||||
'test/src/native_tests/android',
|
||||
// TODO(matanlurey): Should we be using another instrumentation method?
|
||||
await runCommand(
|
||||
'flutter',
|
||||
<String>[
|
||||
'drive',
|
||||
],
|
||||
workingDirectory: path.join(
|
||||
'dev',
|
||||
'integration_tests',
|
||||
'android_driver_test',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
30
dev/integration_tests/android_driver_test/.metadata
Normal file
30
dev/integration_tests/android_driver_test/.metadata
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "6a346df51b97840ff2c06805e5482be28c6dd7c4"
|
||||
channel: "[user-branch]"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 6a346df51b97840ff2c06805e5482be28c6dd7c4
|
||||
base_revision: 6a346df51b97840ff2c06805e5482be28c6dd7c4
|
||||
- platform: android
|
||||
create_revision: 6a346df51b97840ff2c06805e5482be28c6dd7c4
|
||||
base_revision: 6a346df51b97840ff2c06805e5482be28c6dd7c4
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
6
dev/integration_tests/android_driver_test/README.md
Normal file
6
dev/integration_tests/android_driver_test/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Flutter Driver Android Integration Tests
|
||||
|
||||
This directory contains a sample app and tests that demonstrate how to use the
|
||||
(experimental) _native_ Flutter Driver API to drive Flutter apps that run on
|
||||
Android devices or emulators, interact with and capture screenshots of the app,
|
||||
and compare the screenshots against golden images.
|
||||
13
dev/integration_tests/android_driver_test/android/.gitignore
vendored
Normal file
13
dev/integration_tests/android_driver_test/android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.android_driver_test"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.android_driver_test"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="android_driver_test"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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.os.Bundle
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// https://developer.android.com/training/system-ui
|
||||
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
|
||||
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
actionBar?.hide()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,22 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- 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. -->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
dev/integration_tests/android_driver_test/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
dev/integration_tests/android_driver_test/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.1.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
29
dev/integration_tests/android_driver_test/lib/main.dart
Normal file
29
dev/integration_tests/android_driver_test/lib/main.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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) {
|
||||
// Draw a full-screen blue rectangle.
|
||||
return const DecoratedBox(decoration: BoxDecoration(color: Colors.blue));
|
||||
}
|
||||
}
|
||||
72
dev/integration_tests/android_driver_test/pubspec.yaml
Normal file
72
dev/integration_tests/android_driver_test/pubspec.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
name: android_driver_test
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0-77.0.dev
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
||||
async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
matcher: 0.12.16+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
meta: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
string_scanner: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
test_api: 0.7.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vm_service: 14.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
dev_dependencies:
|
||||
flutter_goldens:
|
||||
sdk: flutter
|
||||
platform: 3.1.5
|
||||
process: 5.0.2
|
||||
test: 1.25.8
|
||||
|
||||
_fe_analyzer_shared: 72.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
analyzer: 6.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
args: 2.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
coverage: 1.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
mime: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pub_semver: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_static: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
test_core: 0.6.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: f6a7
|
||||
4
dev/integration_tests/android_driver_test/test_driver/.gitignore
vendored
Normal file
4
dev/integration_tests/android_driver_test/test_driver/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# In flutter/flutter, all screenshot tests are run on Skia Gold.
|
||||
#
|
||||
# However, local development might require running screenshot tests locally.
|
||||
*.png
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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.
|
||||
|
||||
/// A fork of `package:flutter_goldens/flutter_goldens.dart` without the
|
||||
/// dependency on `package:flutter_test` or `package:flutter`; this allows
|
||||
/// the library to be used in a standalone Dart VM context.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file/local.dart';
|
||||
import 'package:flutter_driver/src/native_driver.dart';
|
||||
import 'package:flutter_goldens/skia_client.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
const LocalFileSystem _localFs = LocalFileSystem();
|
||||
|
||||
// TODO(matanlurey): Refactor flutter_goldens to just re-use that code instead.
|
||||
Future<void> testExecutable(
|
||||
FutureOr<void> Function() testMain, {
|
||||
String? namePrefix,
|
||||
}) async {
|
||||
assert(
|
||||
goldenFileComparator is NaiveLocalFileComparator,
|
||||
'The flutter_goldens_fork library should be used from a *_test.dart file '
|
||||
'where the "goldenFileComparator" has not yet been set. This is to ensure '
|
||||
'that the correct comparator is used for the current test environment.',
|
||||
);
|
||||
final io.Directory tmpDir = io.Directory.systemTemp.createTempSync('android_driver_test');
|
||||
goldenFileComparator = _GoldenFileComparator(
|
||||
SkiaGoldClient(
|
||||
_localFs.directory(tmpDir.path),
|
||||
fs: _localFs,
|
||||
process: const LocalProcessManager(),
|
||||
platform: const LocalPlatform(),
|
||||
httpClient: io.HttpClient(),
|
||||
log: io.stderr.writeln,
|
||||
),
|
||||
namePrefix: namePrefix,
|
||||
isPresubmit: false,
|
||||
);
|
||||
}
|
||||
|
||||
final class _GoldenFileComparator extends GoldenFileComparator {
|
||||
_GoldenFileComparator(
|
||||
this.skiaClient, {
|
||||
required this.isPresubmit,
|
||||
this.namePrefix,
|
||||
Uri? baseDir,
|
||||
}) : baseDir = baseDir ?? Uri.parse(path.dirname(io.Platform.script.path));
|
||||
|
||||
final Uri baseDir;
|
||||
final SkiaGoldClient skiaClient;
|
||||
final String? namePrefix;
|
||||
final bool isPresubmit;
|
||||
|
||||
@override
|
||||
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
|
||||
if (isPresubmit) {
|
||||
await skiaClient.tryjobInit();
|
||||
} else {
|
||||
await skiaClient.imgtestInit();
|
||||
}
|
||||
|
||||
golden = _addPrefix(golden);
|
||||
await update(golden, imageBytes);
|
||||
|
||||
final io.File goldenFile = _getGoldenFile(golden);
|
||||
if (isPresubmit) {
|
||||
await skiaClient.tryjobAdd(golden.path, _localFs.file(goldenFile.path));
|
||||
return true;
|
||||
} else {
|
||||
return skiaClient.imgtestAdd(golden.path, _localFs.file(goldenFile.path));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(Uri golden, Uint8List imageBytes) async {
|
||||
final io.File goldenFile = _getGoldenFile(golden);
|
||||
await goldenFile.parent.create(recursive: true);
|
||||
await goldenFile.writeAsBytes(imageBytes, flush: true);
|
||||
}
|
||||
|
||||
io.File _getGoldenFile(Uri uri) {
|
||||
return io.File.fromUri(baseDir.resolveUri(uri));
|
||||
}
|
||||
|
||||
Uri _addPrefix(Uri golden) {
|
||||
assert(
|
||||
golden.toString().split('.').last == 'png',
|
||||
'Golden files in the Flutter framework must end with the file extension '
|
||||
'.png.',
|
||||
);
|
||||
return Uri.parse(<String>[
|
||||
if (namePrefix != null) namePrefix!,
|
||||
baseDir.pathSegments[baseDir.pathSegments.length - 2],
|
||||
golden.toString(),
|
||||
].join('.'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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 full-screen blue rectangle', () async {
|
||||
await flutterDriver.waitFor(find.byType('DecoratedBox'));
|
||||
await expectLater(
|
||||
nativeDriver.screenshot(),
|
||||
matchesGoldenFile('android_driver_test.BlueRectangle.png'),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
// Examples can assume:
|
||||
// import 'package:flutter_driver/src/native/android.dart';
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -20,44 +21,57 @@ final class AndroidNativeDriver implements NativeDriver {
|
||||
/// The [tempDirectory] argument can be used to specify a custom directory
|
||||
/// where the driver will store temporary files. If not provided, a temporary
|
||||
/// directory will be created in the system's temporary directory.
|
||||
///
|
||||
/// @nodoc
|
||||
@visibleForTesting
|
||||
AndroidNativeDriver({
|
||||
required AndroidDeviceTarget target,
|
||||
String? adbPath,
|
||||
io.Directory? tempDirectory,
|
||||
}) : _adbPath = adbPath ?? 'adb',
|
||||
_target = target,
|
||||
_tmpDir = tempDirectory ?? io.Directory.systemTemp.createTempSync('flutter_driver.');
|
||||
}) : _adbPath = adbPath ?? 'adb',
|
||||
_target = target,
|
||||
_tmpDir = tempDirectory ??
|
||||
io.Directory.systemTemp.createTempSync('flutter_driver.');
|
||||
|
||||
/// Connects to a device or emulator identified by [target].
|
||||
static Future<AndroidNativeDriver> connect({
|
||||
AndroidDeviceTarget target = const AndroidDeviceTarget.onlyEmulatorOrDevice(),
|
||||
AndroidDeviceTarget? target,
|
||||
}) async {
|
||||
target ??= const AndroidDeviceTarget.onlyEmulatorOrDevice();
|
||||
final AndroidNativeDriver driver = AndroidNativeDriver(target: target);
|
||||
await driver._smokeTest();
|
||||
return driver;
|
||||
}
|
||||
|
||||
Future<void> _smokeTest() async {
|
||||
final io.ProcessResult version = await io.Process.run(
|
||||
Future<io.ProcessResult> _adb(
|
||||
List<String> args, {
|
||||
Encoding? stdoutEncoding = io.systemEncoding,
|
||||
}) {
|
||||
return io.Process.run(
|
||||
_adbPath,
|
||||
const <String>['version'],
|
||||
<String>[
|
||||
..._target._toAdbArgs(),
|
||||
...args,
|
||||
],
|
||||
stdoutEncoding: stdoutEncoding,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _smokeTest() async {
|
||||
final io.ProcessResult version = await _adb(<String>['version']);
|
||||
if (version.exitCode != 0) {
|
||||
throw StateError('Failed to run `$_adbPath version`: ${version.stderr}');
|
||||
}
|
||||
|
||||
final io.ProcessResult devices = await io.Process.run(
|
||||
_adbPath,
|
||||
final io.ProcessResult echo = await _adb(
|
||||
<String>[
|
||||
..._target._toAdbArgs(),
|
||||
'shell',
|
||||
'echo',
|
||||
'connected',
|
||||
],
|
||||
);
|
||||
if (devices.exitCode != 0) {
|
||||
throw StateError('Failed to connect to target: ${devices.stderr}');
|
||||
if (echo.exitCode != 0) {
|
||||
throw StateError('Failed to connect to target: ${echo.stderr}');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +84,38 @@ final class AndroidNativeDriver implements NativeDriver {
|
||||
await _tmpDir.delete(recursive: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> configureForScreenshotTesting() async {
|
||||
const Map<String, String> settings = <String, String>{
|
||||
'show_surface_updates': '1',
|
||||
'transition_animation_scale': '0',
|
||||
'window_animation_scale': '0',
|
||||
'animator_duration_scale': '0',
|
||||
};
|
||||
|
||||
for (final MapEntry<String, String> entry in settings.entries) {
|
||||
final io.ProcessResult result = await _adb(
|
||||
<String>[
|
||||
'shell',
|
||||
'settings',
|
||||
'put',
|
||||
'global',
|
||||
entry.key,
|
||||
entry.value,
|
||||
],
|
||||
);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
throw StateError('Failed to configure device: ${result.stderr}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<NativeScreenshot> screenshot() async {
|
||||
final io.ProcessResult result = await io.Process.run(
|
||||
_adbPath,
|
||||
// Similar pause to the one in `<FlutterDriver>.screenshot()`.
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
final io.ProcessResult result = await _adb(
|
||||
<String>[
|
||||
..._target._toAdbArgs(),
|
||||
'exec-out',
|
||||
@@ -134,7 +176,8 @@ sealed class AndroidDeviceTarget {
|
||||
/// ```dart
|
||||
/// const AndroidDeviceTarget target = AndroidDeviceTarget.bySerial('emulator-5554');
|
||||
/// ```
|
||||
const factory AndroidDeviceTarget.bySerial(String serialNumber) = _SerialDeviceTarget;
|
||||
const factory AndroidDeviceTarget.bySerial(String serialNumber) =
|
||||
_SerialDeviceTarget;
|
||||
|
||||
/// Represents the only running emulator _or_ connected device.
|
||||
///
|
||||
@@ -144,7 +187,8 @@ sealed class AndroidDeviceTarget {
|
||||
/// Represents the only running emulator on the host machine.
|
||||
///
|
||||
/// This is equivalent to using `adb -e`, a _single_ emulator must be running.
|
||||
const factory AndroidDeviceTarget.onlyRunningEmulator() = _SingleEmulatorTarget;
|
||||
const factory AndroidDeviceTarget.onlyRunningEmulator() =
|
||||
_SingleEmulatorTarget;
|
||||
|
||||
/// Represents the only connected device on the host machine.
|
||||
///
|
||||
|
||||
@@ -26,6 +26,16 @@ abstract interface class NativeDriver {
|
||||
/// After calling this method, the driver is no longer usable.
|
||||
Future<void> close();
|
||||
|
||||
/// Configures the device for screenshot testing.
|
||||
///
|
||||
/// Where possible, this method should suppress system UI elements that are
|
||||
/// not part of the application under test, such as the status bar or
|
||||
/// navigation bar, and disable animations that might interfere with
|
||||
/// screenshot comparison.
|
||||
///
|
||||
/// The exact details of what is configured are platform-specific.
|
||||
Future<void> configureForScreenshotTesting();
|
||||
|
||||
/// Take a screenshot using a platform-specific mechanism.
|
||||
///
|
||||
/// The image is returned as an opaque handle that can be used to retrieve
|
||||
|
||||
258
packages/flutter_driver/lib/src/native/goldens.dart
Normal file
258
packages/flutter_driver/lib/src/native/goldens.dart
Normal file
@@ -0,0 +1,258 @@
|
||||
// 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.
|
||||
|
||||
/// A partial copy of [flutter_test.matchesGoldenFile] and supporting code.
|
||||
///
|
||||
/// Flutter driver runs in the standalone Dart VM, which does not have access to
|
||||
/// the Flutter test library or `dart:ui`. This file provides a subset of the
|
||||
/// functionality of `flutter_test`'s `matchesGoldenFile` function, and we can
|
||||
/// consider refactoring this code to be shared between the two libraries
|
||||
/// (https://github.com/flutter/flutter/issues/152257).
|
||||
///
|
||||
/// ## TODOs
|
||||
///
|
||||
/// - [ ] Figure out a code-sharing strategy with `flutter_test`.
|
||||
///
|
||||
/// The basics, such as the matcher and APIs, could definitely be shared.
|
||||
///
|
||||
/// - [ ] Consider how local image diffing could be shared, if at all.
|
||||
///
|
||||
/// The `flutter_test` implementation uses `dart:ui`, which is not available
|
||||
/// today in a Flutter driver script. We could either (a) provide a different
|
||||
/// implementation for Flutter driver, or (b) provide a way, such as using
|
||||
/// `flutter_tester` to run the tests in a Flutter context, to use the same
|
||||
/// implementation.
|
||||
///
|
||||
/// - [ ] Teach `flutter drive` to use and provide `--update-goldens` flag.
|
||||
///
|
||||
/// @docImport 'package:flutter_test/flutter_test.dart' as flutter_test;
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:matcher/matcher.dart';
|
||||
|
||||
// Similar to `flutter_test`, we ignore the implementation import.
|
||||
// ignore: implementation_imports
|
||||
import 'package:matcher/src/expect/async_matcher.dart';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:test_api/test_api.dart';
|
||||
|
||||
import 'driver.dart';
|
||||
|
||||
/// Whether golden files should be automatically updated during tests rather
|
||||
/// than compared to the image bytes recorded by the tests.
|
||||
///
|
||||
/// When this is `true`, [matchesGoldenFile] will always report a successful
|
||||
/// match, because the bytes being tested implicitly become the new golden.
|
||||
bool autoUpdateGoldenFiles = false;
|
||||
|
||||
/// Compares pixels against those of a golden image file.
|
||||
///
|
||||
/// This comparator is used as the backend for [matchesGoldenFile].
|
||||
///
|
||||
/// By default, an exact pixel match to a local golden file is used.
|
||||
GoldenFileComparator goldenFileComparator = const NaiveLocalFileComparator._();
|
||||
|
||||
/// Compares image pixels against a golden image file.
|
||||
///
|
||||
/// Instances of this comparator will be used as the backend for
|
||||
/// [matchesGoldenFile].
|
||||
abstract mixin class GoldenFileComparator {
|
||||
/// Compares the pixels of decoded png [imageBytes] against the golden file
|
||||
/// identified by [golden].
|
||||
///
|
||||
/// The returned future completes with a boolean value that indicates whether
|
||||
/// the pixels decoded from [imageBytes] 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> compare(Uint8List imageBytes, Uri golden);
|
||||
|
||||
/// Updates the golden file identified by [golden] with [imageBytes].
|
||||
///
|
||||
/// This will be invoked in lieu of [compare] when [autoUpdateGoldenFiles]
|
||||
/// is `true` (which gets set automatically by the test framework when the
|
||||
/// user runs `flutter drive --update-goldens`).
|
||||
///
|
||||
/// The method by which [golden] is located and by which its bytes are written
|
||||
/// is left up to the implementation class.
|
||||
Future<void> update(Uri golden, Uint8List imageBytes);
|
||||
|
||||
/// Returns a new golden file [Uri] to incorporate any [version] number with
|
||||
/// the [key].
|
||||
///
|
||||
/// The [version] is an optional int that can be used to differentiate
|
||||
/// historical golden files.
|
||||
///
|
||||
/// Version numbers are used in golden file tests for package:flutter. You can
|
||||
/// learn more about these tests [here](https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md).
|
||||
Uri getTestUri(Uri key, int? version) {
|
||||
if (version == null) {
|
||||
return key;
|
||||
}
|
||||
final String keyString = key.toString();
|
||||
final String extension = path.extension(keyString);
|
||||
return Uri.parse('${keyString.split(extension).join()}.$version$extension');
|
||||
}
|
||||
}
|
||||
|
||||
/// The default [GoldenFileComparator] implementation for `flutter drive`.
|
||||
///
|
||||
/// This comparator performs a pixel-for-pixel comparison of the decoded PNGs,
|
||||
/// returning true only if there's an exact match. In cases where the captured
|
||||
/// test image does not match the golden file, this comparator will provide a
|
||||
/// fairly unhelpful error message, which could be improved in the future.
|
||||
final class NaiveLocalFileComparator with GoldenFileComparator {
|
||||
const NaiveLocalFileComparator._();
|
||||
|
||||
@override
|
||||
Future<bool> compare(Uint8List imageBytes, Uri golden) async {
|
||||
final io.File goldenFile = _getTestFilePath(golden);
|
||||
final Uint8List goldenBytes;
|
||||
try {
|
||||
goldenBytes = await goldenFile.readAsBytes();
|
||||
} on io.PathNotFoundException {
|
||||
throw TestFailure('Golden file not found: $golden');
|
||||
}
|
||||
|
||||
if (goldenBytes.length != imageBytes.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < goldenBytes.length; i++) {
|
||||
if (goldenBytes[i] != imageBytes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(Uri golden, Uint8List imageBytes) async {
|
||||
final io.File goldenFile = _getTestFilePath(golden);
|
||||
await goldenFile.parent.create(recursive: true);
|
||||
await goldenFile.writeAsBytes(imageBytes);
|
||||
}
|
||||
|
||||
/// Returns a path relative to the test script.
|
||||
///
|
||||
/// This is hacky and unreliable, but it's the best we can do until we have
|
||||
/// more integration with the `flutter` CLI (which does all the heavy lifting
|
||||
/// for us in `flutter_test`).
|
||||
io.File _getTestFilePath(Uri golden) {
|
||||
final String testScriptPath = io.Platform.script.toFilePath();
|
||||
final String testScriptDir = path.dirname(testScriptPath);
|
||||
return io.File(path.join(testScriptDir, golden.path));
|
||||
}
|
||||
}
|
||||
|
||||
// Examples can assume:
|
||||
// import 'package:flutter_driver/src/native/driver.dart';
|
||||
// import 'package:flutter_driver/src/native/goldens.dart';
|
||||
// import 'package:test/test.dart';
|
||||
// late NativeDriver nativeDriver;
|
||||
|
||||
/// Asserts that a [NativeScreenshot], [Future<NativeScreenshot>], or
|
||||
/// [List<int>] matches the golden image file indentified by [key], with an
|
||||
/// optional [version] number].
|
||||
///
|
||||
/// The [key] may be either a [Uri] or a [String] representation of a URL.
|
||||
///
|
||||
/// The [version] is a number that can be used to differentiate historical
|
||||
/// golden files. This parameter is optional.
|
||||
///
|
||||
/// This is an asynchronous matcher, meaning that callers should use
|
||||
/// [flutter_test.expectLater] when using this matcher and await the future
|
||||
/// returned by [flutter_test.expectLater].
|
||||
///
|
||||
/// ## Golden File Testing
|
||||
///
|
||||
/// The term __golden file__ refers to a master image that is considered the
|
||||
/// true renmdering of a given widget, state, application, or other visual
|
||||
/// representation you have chosen to capture.
|
||||
///
|
||||
/// The master golden image files are tested against can be created or updated
|
||||
/// by running `flutter drive --update-goldens` on the test.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
/// Sample invocations of [matchesGoldenFile].
|
||||
///
|
||||
/// ```dart
|
||||
/// await expectLater(
|
||||
/// nativeDriver.screenshot(),
|
||||
/// matchesGoldenFile('save.png'),
|
||||
/// );
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
AsyncMatcher matchesGoldenFile(Object key, {int? version}) {
|
||||
return switch (key) {
|
||||
Uri() => _MatchesGoldenFile(key, version),
|
||||
String() => _MatchesGoldenFile.forStringPath(key, version),
|
||||
_ => throw ArgumentError(
|
||||
'Unexpected type for golden file: ${key.runtimeType}'),
|
||||
};
|
||||
}
|
||||
|
||||
/// The matcher created by [matchesGoldenFile].
|
||||
final class _MatchesGoldenFile extends AsyncMatcher {
|
||||
/// Creates an instance of [MatchesGoldenFile].
|
||||
const _MatchesGoldenFile(this.key, this.version);
|
||||
|
||||
/// Creates an instance of [MatchesGoldenFile] from a [String] path.
|
||||
_MatchesGoldenFile.forStringPath(String path, this.version)
|
||||
: key = Uri.parse(path);
|
||||
|
||||
/// The [key] to the golden image.
|
||||
final Uri key;
|
||||
|
||||
/// The [version] of the golden image.
|
||||
final int? version;
|
||||
|
||||
@override
|
||||
Future<String?> matchAsync(Object? item) async {
|
||||
final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
|
||||
|
||||
final Uint8List buffer;
|
||||
if (item is FutureOr<List<int>>) {
|
||||
buffer = Uint8List.fromList(await item);
|
||||
} else if (item is FutureOr<NativeScreenshot>) {
|
||||
buffer = await (await item).readAsBytes();
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'Unexpected type for golden file: ${item.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
if (autoUpdateGoldenFiles) {
|
||||
await goldenFileComparator.update(testNameUri, buffer);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final bool success = await goldenFileComparator.compare(
|
||||
buffer,
|
||||
testNameUri,
|
||||
);
|
||||
return success ? null : 'does not match';
|
||||
} on TestFailure catch (e) {
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Description describe(Description description) {
|
||||
return description.add('app screenshot image matches golden file "$key"');
|
||||
}
|
||||
}
|
||||
12
packages/flutter_driver/lib/src/native_driver.dart
Normal file
12
packages/flutter_driver/lib/src/native_driver.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
@experimental
|
||||
library;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
export 'native/android.dart';
|
||||
export 'native/driver.dart';
|
||||
export 'native/goldens.dart';
|
||||
Reference in New Issue
Block a user