From e9de4484209472079d3db1cc0dfd1fcf54570b65 Mon Sep 17 00:00:00 2001 From: Victor Eronmosele Date: Fri, 17 Nov 2023 02:08:13 +0100 Subject: [PATCH] Enable `flutter screenshot` outside Flutter project directory (#138160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR enables the `flutter screenshot` to work outside a Flutter project. This works by enabling `ScreenshotCommand` to find target devices not supported by the project. Before: ```bash $ cd $HOME # not a Flutter directory $ flutter screenshot No devices found yet. Checking for wireless devices... No supported devices connected. The following devices were found, but are not supported by this project: sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 13 (API 33) (emulator) macOS (desktop) • macos • darwin-arm64 • macOS 13.3.1 22E772610a darwin-arm64 Chrome (web) • chrome • web-javascript • Google Chrome 119.0.6045.105 If you would like your app to run on android or macos or web, consider running `flutter create .` to generate projects for these platforms. Must have a connected device for screenshot type device ``` After: ```bash $ cd $HOME # not a Flutter directory $ flutter_source screenshot Screenshot written to flutter_01.png (313kB). ``` Fixes #115790 --- .../lib/src/commands/screenshot.dart | 2 +- .../hermetic/screenshot_command_test.dart | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/commands/screenshot.dart b/packages/flutter_tools/lib/src/commands/screenshot.dart index db3686e49f..72182bd7f2 100644 --- a/packages/flutter_tools/lib/src/commands/screenshot.dart +++ b/packages/flutter_tools/lib/src/commands/screenshot.dart @@ -78,7 +78,7 @@ class ScreenshotCommand extends FlutterCommand { if (vmServiceUrl != null) { throwToolExit('VM Service URI cannot be provided for screenshot type $screenshotType'); } - device = await findTargetDevice(); + device = await findTargetDevice(includeDevicesUnsupportedByProject: true); if (device == null) { throwToolExit('Must have a connected device for screenshot type $screenshotType'); } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart index 01725cc991..c9731bb9cc 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart @@ -2,15 +2,23 @@ // 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:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/screenshot.dart'; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/vmservice.dart'; +import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fake_devices.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { @@ -116,4 +124,115 @@ void main() { message: 'It appears the output file contains an error message, not valid output.')); }); }); + + group('Screenshot for devices unsupported for project', () { + late _TestDeviceManager testDeviceManager; + + setUp(() { + testDeviceManager = _TestDeviceManager(logger: BufferLogger.test()); + }); + + testUsingContext('should not throw for a single device', () async { + final ScreenshotCommand command = ScreenshotCommand(fs: MemoryFileSystem.test()); + + final _ScreenshotDevice deviceUnsupportedForProject = _ScreenshotDevice( + id: '123', name: 'Device 1', isSupportedForProject: false); + + testDeviceManager.devices = [deviceUnsupportedForProject]; + + await createTestCommandRunner(command).run(['screenshot']); + }, overrides: { + DeviceManager: () => testDeviceManager, + }); + + testUsingContext('should tool exit for multiple devices', () async { + final ScreenshotCommand command = ScreenshotCommand(fs: MemoryFileSystem.test()); + + final List<_ScreenshotDevice> devicesUnsupportedForProject = <_ScreenshotDevice>[ + _ScreenshotDevice(id: '123', name: 'Device 1', isSupportedForProject: false), + _ScreenshotDevice(id: '456', name: 'Device 2', isSupportedForProject: false), + ]; + + testDeviceManager.devices = devicesUnsupportedForProject; + + await expectLater(() => createTestCommandRunner(command).run(['screenshot']), throwsToolExit( + message: 'Must have a connected device for screenshot type device', + )); + + expect(testLogger.statusText, contains(''' +More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices. + +Device 1 (mobile) • 123 • android • 1.2.3 +Device 2 (mobile) • 456 • android • 1.2.3 +''')); + }, overrides: { + DeviceManager: () => testDeviceManager, + }); + }); +} + +class _ScreenshotDevice extends Fake implements Device { + _ScreenshotDevice({ + required this.id, + required this.name, + required bool isSupportedForProject, + }) : _isSupportedForProject = isSupportedForProject; + + @override + final String name; + + @override + final String id; + + final bool _isSupportedForProject; + + @override + bool isSupportedForProject(FlutterProject flutterProject) => _isSupportedForProject; + + @override + bool supportsScreenshot = true; + + @override + bool get isConnected => true; + + @override + bool isSupported() => true; + + @override + bool ephemeral = true; + + @override + DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached; + + @override + Future takeScreenshot(File outputFile) async { + outputFile.writeAsBytesSync([1, 2, 3, 4]); + } + + @override + Future get targetPlatformDisplayName async => 'android'; + + @override + Future get sdkNameAndVersion async => '1.2.3'; + + @override + Future get targetPlatform => Future.value(TargetPlatform.android); + + @override + Future get isLocalEmulator async => false; + + @override + Category get category => Category.mobile; +} + +class _TestDeviceManager extends DeviceManager { + _TestDeviceManager({required super.logger}); + List devices = []; + + @override + List get deviceDiscoverers { + final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); + devices.forEach(discoverer.addDevice); + return [discoverer]; + } }