Skip to content

Commit e9de448

Browse files
Enable flutter screenshot outside Flutter project directory (#138160)
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
1 parent 2e5990c commit e9de448

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

packages/flutter_tools/lib/src/commands/screenshot.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class ScreenshotCommand extends FlutterCommand {
7878
if (vmServiceUrl != null) {
7979
throwToolExit('VM Service URI cannot be provided for screenshot type $screenshotType');
8080
}
81-
device = await findTargetDevice();
81+
device = await findTargetDevice(includeDevicesUnsupportedByProject: true);
8282
if (device == null) {
8383
throwToolExit('Must have a connected device for screenshot type $screenshotType');
8484
}

packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
6+
57
import 'package:file/memory.dart';
8+
import 'package:flutter_tools/src/base/file_system.dart';
69
import 'package:flutter_tools/src/base/io.dart';
710
import 'package:flutter_tools/src/base/logger.dart';
11+
import 'package:flutter_tools/src/build_info.dart';
812
import 'package:flutter_tools/src/cache.dart';
913
import 'package:flutter_tools/src/commands/screenshot.dart';
14+
import 'package:flutter_tools/src/device.dart';
15+
import 'package:flutter_tools/src/project.dart';
1016
import 'package:flutter_tools/src/vmservice.dart';
17+
import 'package:test/fake.dart';
1118

1219
import '../../src/common.dart';
1320
import '../../src/context.dart';
21+
import '../../src/fake_devices.dart';
1422
import '../../src/test_flutter_command_runner.dart';
1523

1624
void main() {
@@ -116,4 +124,115 @@ void main() {
116124
message: 'It appears the output file contains an error message, not valid output.'));
117125
});
118126
});
127+
128+
group('Screenshot for devices unsupported for project', () {
129+
late _TestDeviceManager testDeviceManager;
130+
131+
setUp(() {
132+
testDeviceManager = _TestDeviceManager(logger: BufferLogger.test());
133+
});
134+
135+
testUsingContext('should not throw for a single device', () async {
136+
final ScreenshotCommand command = ScreenshotCommand(fs: MemoryFileSystem.test());
137+
138+
final _ScreenshotDevice deviceUnsupportedForProject = _ScreenshotDevice(
139+
id: '123', name: 'Device 1', isSupportedForProject: false);
140+
141+
testDeviceManager.devices = <Device>[deviceUnsupportedForProject];
142+
143+
await createTestCommandRunner(command).run(<String>['screenshot']);
144+
}, overrides: <Type, Generator>{
145+
DeviceManager: () => testDeviceManager,
146+
});
147+
148+
testUsingContext('should tool exit for multiple devices', () async {
149+
final ScreenshotCommand command = ScreenshotCommand(fs: MemoryFileSystem.test());
150+
151+
final List<_ScreenshotDevice> devicesUnsupportedForProject = <_ScreenshotDevice>[
152+
_ScreenshotDevice(id: '123', name: 'Device 1', isSupportedForProject: false),
153+
_ScreenshotDevice(id: '456', name: 'Device 2', isSupportedForProject: false),
154+
];
155+
156+
testDeviceManager.devices = devicesUnsupportedForProject;
157+
158+
await expectLater(() => createTestCommandRunner(command).run(<String>['screenshot']), throwsToolExit(
159+
message: 'Must have a connected device for screenshot type device',
160+
));
161+
162+
expect(testLogger.statusText, contains('''
163+
More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.
164+
165+
Device 1 (mobile) • 123 • android • 1.2.3
166+
Device 2 (mobile) • 456 • android • 1.2.3
167+
'''));
168+
}, overrides: <Type, Generator>{
169+
DeviceManager: () => testDeviceManager,
170+
});
171+
});
172+
}
173+
174+
class _ScreenshotDevice extends Fake implements Device {
175+
_ScreenshotDevice({
176+
required this.id,
177+
required this.name,
178+
required bool isSupportedForProject,
179+
}) : _isSupportedForProject = isSupportedForProject;
180+
181+
@override
182+
final String name;
183+
184+
@override
185+
final String id;
186+
187+
final bool _isSupportedForProject;
188+
189+
@override
190+
bool isSupportedForProject(FlutterProject flutterProject) => _isSupportedForProject;
191+
192+
@override
193+
bool supportsScreenshot = true;
194+
195+
@override
196+
bool get isConnected => true;
197+
198+
@override
199+
bool isSupported() => true;
200+
201+
@override
202+
bool ephemeral = true;
203+
204+
@override
205+
DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
206+
207+
@override
208+
Future<void> takeScreenshot(File outputFile) async {
209+
outputFile.writeAsBytesSync(<int>[1, 2, 3, 4]);
210+
}
211+
212+
@override
213+
Future<String> get targetPlatformDisplayName async => 'android';
214+
215+
@override
216+
Future<String> get sdkNameAndVersion async => '1.2.3';
217+
218+
@override
219+
Future<TargetPlatform> get targetPlatform => Future<TargetPlatform>.value(TargetPlatform.android);
220+
221+
@override
222+
Future<bool> get isLocalEmulator async => false;
223+
224+
@override
225+
Category get category => Category.mobile;
226+
}
227+
228+
class _TestDeviceManager extends DeviceManager {
229+
_TestDeviceManager({required super.logger});
230+
List<Device> devices = <Device>[];
231+
232+
@override
233+
List<DeviceDiscovery> get deviceDiscoverers {
234+
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
235+
devices.forEach(discoverer.addDevice);
236+
return <DeviceDiscovery>[discoverer];
237+
}
119238
}

0 commit comments

Comments
 (0)