Implement takeScreenshot and add driver test for Fuchsia (#48611)
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart b/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart
new file mode 100644
index 0000000..8d97bb9
--- /dev/null
+++ b/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart
@@ -0,0 +1,14 @@
+// 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_devicelab/framework/adb.dart';
+import 'package:flutter_devicelab/framework/framework.dart';
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
+
+Future<void> main() async {
+ deviceOperatingSystem = DeviceOperatingSystem.fuchsia;
+ await task(createFlutterDriverScreenshotTest());
+}
diff --git a/dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx b/dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx
new file mode 100644
index 0000000..2938f45
--- /dev/null
+++ b/dev/integration_tests/flutter_driver_screenshot_test/fuchsia/meta/flutter_driver_screenshot_test.cmx
@@ -0,0 +1,22 @@
+{
+ "program": {
+ "data": "data/flutter_driver_screenshot_test"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.cobalt.LoggerFactory",
+ "fuchsia.fonts.Provider",
+ "fuchsia.logger.LogSink",
+ "fuchsia.modular.Clipboard",
+ "fuchsia.modular.ContextWriter",
+ "fuchsia.modular.DeviceMap",
+ "fuchsia.modular.ModuleContext",
+ "fuchsia.sys.Environment",
+ "fuchsia.sys.Launcher",
+ "fuchsia.testing.runner.TestRunner",
+ "fuchsia.ui.input.ImeService",
+ "fuchsia.ui.policy.Presenter",
+ "fuchsia.ui.scenic.Scenic"
+ ]
+ }
+}
diff --git a/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart b/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart
index 9147d16..1757908 100644
--- a/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart
+++ b/dev/integration_tests/flutter_driver_screenshot_test/lib/main.dart
@@ -71,28 +71,20 @@
Future<String> _handleDriverMessage(String message) async {
switch (message) {
case 'device_model':
- String target;
+ final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
- target = 'ios';
- break;
- case TargetPlatform.android:
- target = 'android';
- break;
- default:
- target = 'unsupported';
- break;
- }
- final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
- if (target == 'ios') {
- final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
- if (iosDeviceInfo.isPhysicalDevice) {
- return iosDeviceInfo.utsname.machine;
- } else {
+ final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
+ if (iosDeviceInfo.isPhysicalDevice) {
+ return iosDeviceInfo.utsname.machine;
+ }
return 'sim_' + iosDeviceInfo.name;
- }
- } else if (target == 'android') {
- return (await deviceInfo.androidInfo).model;
+ case TargetPlatform.android:
+ return (await deviceInfo.androidInfo).model;
+ case TargetPlatform.fuchsia:
+ return 'fuchsia';
+ default:
+ return 'unsupported';
}
break;
}
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index 7541049..644bbb0 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -139,7 +139,7 @@
FuchsiaDevices() : super('Fuchsia devices');
@override
- bool get supportsPlatform => globals.platform.isLinux || globals.platform.isMacOS;
+ bool get supportsPlatform => isFuchsiaSupportedPlatform();
@override
bool get canListAnything => fuchsiaWorkflow.canListDevices;
@@ -443,6 +443,39 @@
}
@override
+ bool get supportsScreenshot => isFuchsiaSupportedPlatform();
+
+ @override
+ Future<void> takeScreenshot(File outputFile) async {
+ if (outputFile.basename.split('.').last != 'ppm') {
+ throw '${outputFile.path} must be a .ppm file';
+ }
+ final RunResult screencapResult = await shell('screencap > /tmp/screenshot.ppm');
+ if (screencapResult.exitCode != 0) {
+ throw 'Could not take a screenshot on device $name:\n$screencapResult';
+ }
+ try {
+ final RunResult scpResult = await scp('/tmp/screenshot.ppm', outputFile.path);
+ if (scpResult.exitCode != 0) {
+ throw 'Failed to copy screenshot from device:\n$scpResult';
+ }
+ } finally {
+ try {
+ final RunResult deleteResult = await shell('rm /tmp/screenshot.ppm');
+ if (deleteResult.exitCode != 0) {
+ globals.printError(
+ 'Failed to delete screenshot.ppm from the device:\n$deleteResult'
+ );
+ }
+ } catch (_) {
+ globals.printError(
+ 'Failed to delete screenshot.ppm from the device'
+ );
+ }
+ }
+ }
+
+ @override
Future<TargetPlatform> get targetPlatform async => _targetPlatform ??= await _queryTargetPlatform();
@override
@@ -479,9 +512,6 @@
@override
void clearLogs() {}
- @override
- bool get supportsScreenshot => false;
-
bool get ipv6 {
try {
Uri.parseIPv6Address(id);
@@ -545,6 +575,21 @@
]);
}
+ /// Transfer the file [origin] from the device to [destination].
+ Future<RunResult> scp(String origin, String destination) async {
+ if (fuchsiaArtifacts.sshConfig == null) {
+ throwToolExit('Cannot interact with device. No ssh config.\n'
+ 'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
+ }
+ return await processUtils.run(<String>[
+ 'scp',
+ '-F',
+ fuchsiaArtifacts.sshConfig.absolute.path,
+ '$id:$origin',
+ destination,
+ ]);
+ }
+
/// Finds the first port running a VM matching `isolateName` from the
/// provided set of `ports`.
///
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
index b5effe8..628e624 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
@@ -20,6 +20,11 @@
/// The [FuchsiaArtifacts] instance.
FuchsiaArtifacts get fuchsiaArtifacts => context.get<FuchsiaArtifacts>();
+/// Returns [true] if the current platform supports Fuchsia targets.
+bool isFuchsiaSupportedPlatform() {
+ return globals.platform.isLinux || globals.platform.isMacOS;
+}
+
/// The Fuchsia SDK shell commands.
///
/// This workflow assumes development within the fuchsia source tree,
@@ -110,7 +115,7 @@
/// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to
/// a device.
factory FuchsiaArtifacts.find() {
- if (!globals.platform.isLinux && !globals.platform.isMacOS) {
+ if (!isFuchsiaSupportedPlatform()) {
// Don't try to find the artifacts on platforms that are not supported.
return FuchsiaArtifacts();
}
diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
index 6f6127e..2810fcf 100644
--- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
@@ -31,6 +31,7 @@
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
+import 'package:platform/platform.dart';
import '../../src/common.dart';
import '../../src/context.dart';
@@ -325,6 +326,230 @@
});
});
+ group('screenshot', () {
+ MockProcessManager mockProcessManager;
+
+ setUp(() {
+ mockProcessManager = MockProcessManager();
+ });
+
+ test('is supported on posix platforms', () {
+ final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
+ expect(device.supportsScreenshot, true);
+ }, testOn: 'posix');
+
+ testUsingContext('is not supported on Windows', () {
+ final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
+ expect(device.supportsScreenshot, false);
+ }, overrides: <Type, Generator>{
+ Platform: () => FakePlatform(
+ operatingSystem: 'windows',
+ ),
+ });
+
+ test('takeScreenshot throws if file isn\'t .ppm', () async {
+ final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
+ await expectLater(
+ () => device.takeScreenshot(globals.fs.file('file.invalid')),
+ throwsA(equals('file.invalid must be a .ppm file')),
+ );
+ }, testOn: 'posix');
+
+ testUsingContext('takeScreenshot throws if screencap failed', () async {
+ final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'screencap > /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 1, '', '<error-message>'));
+
+ await expectLater(
+ () => device.takeScreenshot(globals.fs.file('file.ppm')),
+ throwsA(equals('Could not take a screenshot on device tester:\n<error-message>')),
+ );
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Platform: () => FakePlatform(
+ environment: <String, String>{
+ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
+ },
+ operatingSystem: 'linux',
+ ),
+ }, testOn: 'posix');
+
+ testUsingContext('takeScreenshot throws if scp failed', () async {
+ final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'screencap > /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'scp',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0:/tmp/screenshot.ppm',
+ 'file.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 1, '', '<error-message>'));
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'rm /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ await expectLater(
+ () => device.takeScreenshot(globals.fs.file('file.ppm')),
+ throwsA(equals('Failed to copy screenshot from device:\n<error-message>')),
+ );
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Platform: () => FakePlatform(
+ environment: <String, String>{
+ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
+ },
+ operatingSystem: 'linux',
+ ),
+ }, testOn: 'posix');
+
+ testUsingContext('takeScreenshot prints error if can\'t delete file from device', () async {
+ final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'screencap > /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'scp',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0:/tmp/screenshot.ppm',
+ 'file.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'rm /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 1, '', '<error-message>'));
+
+ try {
+ await device.takeScreenshot(globals.fs.file('file.ppm'));
+ } catch (_) {
+ assert(false);
+ }
+ expect(
+ testLogger.errorText,
+ contains('Failed to delete screenshot.ppm from the device:\n<error-message>'),
+ );
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Platform: () => FakePlatform(
+ environment: <String, String>{
+ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
+ },
+ operatingSystem: 'linux',
+ ),
+ }, testOn: 'posix');
+
+ testUsingContext('takeScreenshot returns', () async {
+ final FuchsiaDevice device = FuchsiaDevice('0.0.0.0', name: 'tester');
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'screencap > /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'scp',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0:/tmp/screenshot.ppm',
+ 'file.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ when(mockProcessManager.run(
+ const <String>[
+ 'ssh',
+ '-F',
+ '/fuchsia/out/default/.ssh',
+ '0.0.0.0',
+ 'rm /tmp/screenshot.ppm',
+ ],
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment'),
+ )).thenAnswer((_) async => ProcessResult(0, 0, '', ''));
+
+ try {
+ await device.takeScreenshot(globals.fs.file('file.ppm'));
+ } catch (_) {
+ assert(false);
+ }
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Platform: () => FakePlatform(
+ environment: <String, String>{
+ 'FUCHSIA_SSH_CONFIG': '/fuchsia/out/default/.ssh',
+ },
+ operatingSystem: 'linux',
+ ),
+ }, testOn: 'posix');
+ });
+
group(FuchsiaIsolateDiscoveryProtocol, () {
MockPortForwarder portForwarder;
MockVMService vmService;