mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
Move ios screenshot test to flutter gold. (#52553)
This commit is contained in:
parent
bae2f2c108
commit
b05210c707
|
@ -10,5 +10,5 @@ import 'package:flutter_devicelab/tasks/integration_tests.dart';
|
|||
|
||||
Future<void> main() async {
|
||||
deviceOperatingSystem = DeviceOperatingSystem.fuchsia;
|
||||
await task(createFlutterDriverScreenshotTest(useFlutterGold: true));
|
||||
await task(createFlutterDriverScreenshotTest());
|
||||
}
|
||||
|
|
|
@ -108,17 +108,11 @@ TaskFunction createAndroidSplashScreenKitchenSinkTest() {
|
|||
}
|
||||
|
||||
/// Executes a driver test that takes a screenshot and compares it against a golden image.
|
||||
/// If [useFlutterGold] is true, the golden image is served by Flutter Gold
|
||||
/// (https://flutter-gold.skia.org/), otherwise the golden image is read from the disk.
|
||||
TaskFunction createFlutterDriverScreenshotTest({
|
||||
bool useFlutterGold = false,
|
||||
}) {
|
||||
/// The golden image is served by Flutter Gold (https://flutter-gold.skia.org/).
|
||||
TaskFunction createFlutterDriverScreenshotTest() {
|
||||
return DriverTest(
|
||||
'${flutterDirectory.path}/dev/integration_tests/flutter_driver_screenshot_test',
|
||||
'lib/main.dart',
|
||||
extraOptions: useFlutterGold ? const <String>[
|
||||
'--driver', 'test_driver/flutter_gold_main_test.dart'
|
||||
] : const <String>[]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@ Each sub page should displays some simple UIs to screenshot tested.
|
|||
|
||||
The flutter driver test runs the app and opens each page to take a screenshot.
|
||||
|
||||
Use `test_driver/flutter_gold_main_test.dart` to test against golden files stored on Flutter Gold.
|
||||
Otherwise, use `main_test.dart` to test against golden files stored on `test_driver/goldens/<some_test_page_name>/<device_model>.png`.
|
||||
Use `main_test.dart` to test against golden files stored on Flutter Gold.
|
||||
|
||||
Note that new binaries can't be checked in the Flutter repo, so use [Flutter Gold](https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package:flutter) instead.
|
||||
|
||||
|
@ -17,7 +16,6 @@ Note that new binaries can't be checked in the Flutter repo, so use [Flutter Gol
|
|||
2. The new class should set a static `title` and `key`
|
||||
3. Add an instance of the new class to the `_allPages` list in the `main.dart`
|
||||
4. Create a new test case similar to `"'A page with an image screenshot"` in `test_driver/main_test.dart` to run the screenshot test.
|
||||
5. Create directories for the test: `test_driver/goldens/<some_test_page_name>` should be created before running the test based on the target platform the test is designed to run.
|
||||
|
||||
An example of a `Page` subclass can be found in `lib/image_page.dart`
|
||||
|
||||
|
|
|
@ -15,55 +15,68 @@ def parse_KV_file(file, separator='=')
|
|||
if !File.exists? file_abs_path
|
||||
return [];
|
||||
end
|
||||
pods_ary = []
|
||||
generated_key_values = {}
|
||||
skip_line_start_symbols = ["#", "/"]
|
||||
File.foreach(file_abs_path) { |line|
|
||||
File.foreach(file_abs_path) do |line|
|
||||
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
|
||||
plugin = line.split(pattern=separator)
|
||||
if plugin.length == 2
|
||||
podname = plugin[0].strip()
|
||||
path = plugin[1].strip()
|
||||
podpath = File.expand_path("#{path}", file_abs_path)
|
||||
pods_ary.push({:name => podname, :path => podpath});
|
||||
generated_key_values[podname] = podpath
|
||||
else
|
||||
puts "Invalid plugin specification: #{line}"
|
||||
end
|
||||
}
|
||||
return pods_ary
|
||||
end
|
||||
generated_key_values
|
||||
end
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
# Flutter Pod
|
||||
|
||||
copied_flutter_dir = File.join(__dir__, 'Flutter')
|
||||
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
|
||||
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
|
||||
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
|
||||
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
|
||||
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
|
||||
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
|
||||
|
||||
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
|
||||
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
|
||||
|
||||
unless File.exist?(copied_framework_path)
|
||||
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
|
||||
end
|
||||
unless File.exist?(copied_podspec_path)
|
||||
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
|
||||
end
|
||||
end
|
||||
|
||||
# Keep pod path relative so it can be checked into Podfile.lock.
|
||||
pod 'Flutter', :path => 'Flutter'
|
||||
|
||||
# Plugin Pods
|
||||
|
||||
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
|
||||
# referring to absolute paths on developers' machines.
|
||||
system('rm -rf .symlinks')
|
||||
system('mkdir -p .symlinks/plugins')
|
||||
|
||||
# Flutter Pods
|
||||
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
|
||||
if generated_xcode_build_settings.empty?
|
||||
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first."
|
||||
end
|
||||
generated_xcode_build_settings.map { |p|
|
||||
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
|
||||
symlink = File.join('.symlinks', 'flutter')
|
||||
File.symlink(File.dirname(p[:path]), symlink)
|
||||
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
|
||||
end
|
||||
}
|
||||
|
||||
# Plugin Pods
|
||||
plugin_pods = parse_KV_file('../.flutter-plugins')
|
||||
plugin_pods.map { |p|
|
||||
symlink = File.join('.symlinks', 'plugins', p[:name])
|
||||
File.symlink(p[:path], symlink)
|
||||
pod p[:name], :path => File.join(symlink, 'ios')
|
||||
}
|
||||
plugin_pods.each do |name, path|
|
||||
symlink = File.join('.symlinks', 'plugins', name)
|
||||
File.symlink(path, symlink)
|
||||
pod name, :path => File.join(symlink, 'ios')
|
||||
end
|
||||
end
|
||||
|
||||
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
|
||||
install! 'cocoapods', :disable_input_output_paths => true
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
// 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:async';
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
const String _kPathParent = 'test_driver/goldens/';
|
||||
|
||||
/// The utility class that helps test cases to tests screenshots with a [FlutterDriver].
|
||||
@immutable
|
||||
class DriverScreenShotTester {
|
||||
/// Constructs a [DriverScreenShotTester].
|
||||
///
|
||||
/// All the parameters are required and must not be null.
|
||||
const DriverScreenShotTester({
|
||||
@required this.testName,
|
||||
@required this.driver,
|
||||
@required this.deviceModel,
|
||||
}) : assert(testName != null),
|
||||
assert(driver != null),
|
||||
assert(deviceModel != null);
|
||||
|
||||
/// The name of the test.
|
||||
///
|
||||
/// It needs to match the folder name which the goldens resides under `test_driver/goldens`.
|
||||
final String testName;
|
||||
|
||||
/// The `FlutterDriver` used to take the screenshots.
|
||||
final FlutterDriver driver;
|
||||
|
||||
/// The device model of the device that the test is running on.
|
||||
final String deviceModel;
|
||||
|
||||
/// Compares `screenshot` to the corresponding golden image. Returns true if they match.
|
||||
///
|
||||
/// The golden image should exists at `test_driver/goldens/<testName>/<deviceModel>.png`
|
||||
/// prior to this call.
|
||||
Future<bool> compareScreenshots(List<int> screenshot) async {
|
||||
if (screenshot == null) {
|
||||
return false;
|
||||
}
|
||||
final File file = File(_getImageFilePath());
|
||||
final List<int> matcher = await file.readAsBytes();
|
||||
|
||||
if (matcher == null) {
|
||||
return false;
|
||||
}
|
||||
return _bytesEqual(screenshot, matcher);
|
||||
}
|
||||
|
||||
bool _bytesEqual(List<int> a, List<int> b) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
for (int index = 0; index < a.length; index += 1) {
|
||||
if (a[index] != b[index]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns a bytes representation of a screenshot on the current screen.
|
||||
Future<List<int>> getScreenshotAsBytes() async {
|
||||
return await driver.screenshot();
|
||||
}
|
||||
|
||||
/// Save the `screenshot` as a golden image.
|
||||
///
|
||||
/// The path of the image is defined as:
|
||||
/// `test_driver/goldens/<testName>/<deviceModel>.png`
|
||||
///
|
||||
/// Can be used when recording the golden for the first time.
|
||||
Future<void> saveScreenshot(List<int> screenshot) async {
|
||||
final File file = File(_getImageFilePath());
|
||||
if (!file.existsSync()) {
|
||||
await file.writeAsBytes(screenshot);
|
||||
}
|
||||
}
|
||||
|
||||
String _getImageFilePath() {
|
||||
return path.joinAll(<String>[_kPathParent, testName, deviceModel + '.png']);
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// 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:async';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
import 'package:flutter_test/src/buffer_matcher.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
FlutterDriver driver;
|
||||
String deviceModel;
|
||||
|
||||
setUpAll(() async {
|
||||
driver = await FlutterDriver.connect();
|
||||
deviceModel = await driver.requestData('device_model');
|
||||
});
|
||||
|
||||
tearDownAll(() => driver.close());
|
||||
|
||||
test('A page with an image screenshot', () async {
|
||||
final SerializableFinder imagePageListTile =
|
||||
find.byValueKey('image_page');
|
||||
await driver.waitFor(imagePageListTile);
|
||||
await driver.tap(imagePageListTile);
|
||||
await driver.waitFor(find.byValueKey('red_square_image'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
// TODO(egarciad): This is currently a no-op on LUCI.
|
||||
// https://github.com/flutter/flutter/issues/49837
|
||||
await expectLater(
|
||||
driver.screenshot(),
|
||||
bufferMatchesGoldenFile('red_square_driver_screenshot__$deviceModel.png'),
|
||||
);
|
||||
|
||||
await driver.tap(find.byTooltip('Back'));
|
||||
});
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
|
@ -5,7 +5,7 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
import './driver_screenshot_tester.dart';
|
||||
import 'package:flutter_test/src/buffer_matcher.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
FlutterDriver driver;
|
||||
|
@ -19,7 +19,6 @@ Future<void> main() async {
|
|||
tearDownAll(() => driver.close());
|
||||
|
||||
test('A page with an image screenshot', () async {
|
||||
|
||||
final SerializableFinder imagePageListTile =
|
||||
find.byValueKey('image_page');
|
||||
await driver.waitFor(imagePageListTile);
|
||||
|
@ -27,10 +26,13 @@ Future<void> main() async {
|
|||
await driver.waitFor(find.byValueKey('red_square_image'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
final DriverScreenShotTester tester = DriverScreenShotTester(testName: 'red_square_image', deviceModel: deviceModel, driver: driver);
|
||||
final List<int> screenShot = await tester.getScreenshotAsBytes();
|
||||
final bool compareResult = await tester.compareScreenshots(screenShot);
|
||||
expect(compareResult, true);
|
||||
// TODO(egarciad): This is currently a no-op on LUCI.
|
||||
// https://github.com/flutter/flutter/issues/49837
|
||||
await expectLater(
|
||||
driver.screenshot(),
|
||||
bufferMatchesGoldenFile('red_square_driver_screenshot__$deviceModel.png'),
|
||||
);
|
||||
|
||||
await driver.tap(find.byTooltip('Back'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:image/image.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
|
Loading…
Reference in a new issue