[custom-devices] add screenshotting support (#80675)

This commit is contained in:
Hannes Winkler 2021-04-30 07:39:03 +02:00 committed by GitHub
parent 7bff366b96
commit 82830fa1a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 7 deletions

View File

@ -11,6 +11,7 @@ import 'package:process/process.dart';
import '../application_package.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart';
@ -594,6 +595,24 @@ class CustomDevice extends Device {
@override
Future<bool> get isLocalEmulator async => false;
@override
bool get supportsScreenshot => _config.supportsScreenshotting;
@override
Future<void> takeScreenshot(File outputFile) async {
if (supportsScreenshot == false) {
throw UnsupportedError('Screenshotting is not supported for this device.');
}
final List<String> interpolated = interpolateCommand(
_config.screenshotCommand,
<String, String>{},
);
final RunResult result = await _processUtils.run(interpolated, throwOnError: true);
await outputFile.writeAsBytes(base64Decode(result.stdout));
}
@override
bool isSupported() {
return true;

View File

@ -22,7 +22,8 @@ class CustomDeviceConfig {
required this.uninstallCommand,
required this.runDebugCommand,
this.forwardPortCommand,
this.forwardPortSuccessRegex
this.forwardPortSuccessRegex,
this.screenshotCommand
}) : assert(forwardPortCommand == null || forwardPortSuccessRegex != null);
factory CustomDeviceConfig.fromJson(dynamic json) {
@ -40,7 +41,8 @@ class CustomDeviceConfig {
uninstallCommand: _castStringList(typedMap[_kUninstallCommand]!),
runDebugCommand: _castStringList(typedMap[_kRunDebugCommand]!),
forwardPortCommand: _castStringListOrNull(typedMap[_kForwardPortCommand]),
forwardPortSuccessRegex: _convertToRegexOrNull(typedMap[_kForwardPortSuccessRegex])
forwardPortSuccessRegex: _convertToRegexOrNull(typedMap[_kForwardPortSuccessRegex]),
screenshotCommand: _castStringListOrNull(typedMap[_kScreenshotCommand])
);
}
@ -56,6 +58,7 @@ class CustomDeviceConfig {
static const String _kRunDebugCommand = 'runDebug';
static const String _kForwardPortCommand = 'forwardPort';
static const String _kForwardPortSuccessRegex = 'forwardPortSuccessRegex';
static const String _kScreenshotCommand = 'screenshot';
/// An example device config used for creating the default config file.
static final CustomDeviceConfig example = CustomDeviceConfig(
@ -70,7 +73,8 @@ class CustomDeviceConfig {
uninstallCommand: const <String>['ssh', 'pi@raspberrypi', r'rm -rf "/tmp/${appName}"'],
runDebugCommand: const <String>['ssh', 'pi@raspberrypi', r'flutter-pi "/tmp/${appName}"'],
forwardPortCommand: const <String>['ssh', '-o', 'ExitOnForwardFailure=yes', '-L', r'127.0.0.1:${hostPort}:127.0.0.1:${devicePort}', 'pi@raspberrypi'],
forwardPortSuccessRegex: RegExp('Linux')
forwardPortSuccessRegex: RegExp('Linux'),
screenshotCommand: const <String>['ssh', 'pi@raspberrypi', r"fbgrab /tmp/screenshot.png && cat /tmp/screenshot.png | base64 | tr -d ' \n\t'"]
);
final String id;
@ -85,9 +89,12 @@ class CustomDeviceConfig {
final List<String> runDebugCommand;
final List<String>? forwardPortCommand;
final RegExp? forwardPortSuccessRegex;
final List<String>? screenshotCommand;
bool get usesPortForwarding => forwardPortCommand != null;
bool get supportsScreenshotting => screenshotCommand != null;
static List<String> _castStringList(Object object) {
return (object as List<dynamic>).cast<String>();
}
@ -113,7 +120,8 @@ class CustomDeviceConfig {
_kUninstallCommand: uninstallCommand,
_kRunDebugCommand: runDebugCommand,
_kForwardPortCommand: forwardPortCommand,
_kForwardPortSuccessRegex: forwardPortSuccessRegex?.pattern
_kForwardPortSuccessRegex: forwardPortSuccessRegex?.pattern,
_kScreenshotCommand: screenshotCommand,
};
}
@ -133,7 +141,9 @@ class CustomDeviceConfig {
bool explicitForwardPortCommand = false,
List<String>? forwardPortCommand,
bool explicitForwardPortSuccessRegex = false,
RegExp? forwardPortSuccessRegex
RegExp? forwardPortSuccessRegex,
bool explicitScreenshotCommand = false,
List<String>? screenshotCommand
}) {
return CustomDeviceConfig(
id: id ?? this.id,
@ -147,7 +157,8 @@ class CustomDeviceConfig {
uninstallCommand: uninstallCommand ?? this.uninstallCommand,
runDebugCommand: runDebugCommand ?? this.runDebugCommand,
forwardPortCommand: explicitForwardPortCommand ? forwardPortCommand : (forwardPortCommand ?? this.forwardPortCommand),
forwardPortSuccessRegex: explicitForwardPortSuccessRegex ? forwardPortSuccessRegex : (forwardPortSuccessRegex ?? this.forwardPortSuccessRegex)
forwardPortSuccessRegex: explicitForwardPortSuccessRegex ? forwardPortSuccessRegex : (forwardPortSuccessRegex ?? this.forwardPortSuccessRegex),
screenshotCommand: explicitScreenshotCommand ? screenshotCommand : (screenshotCommand ?? this.screenshotCommand),
);
}
}

View File

@ -107,6 +107,18 @@
"format": "regex",
"default": "Linux",
"required": false
},
"screenshot": {
"description": "Take a screenshot of the app as a png image. This command should take the screenshot, convert it to png, then base64 encode it and echo to stdout. Any stderr output will be ignored. If this command is not given, screenshotting will be disabled for this device.",
"type": ["array", "null"],
"items": {
"type": "string"
},
"minItems": 1,
"default": [
"ssh", "pi@raspberrypi", "fbgrab /tmp/screenshot.png && cat /tmp/screenshot.png | base64 | tr -d ' \\n\\t'"
],
"required": false
}
}
}

View File

@ -10,6 +10,7 @@ import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file/src/interface/directory.dart';
import 'package:file/src/interface/file.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
@ -108,7 +109,8 @@ void main() {
uninstallCommand: const <String>['testuninstall'],
runDebugCommand: const <String>['testrundebug'],
forwardPortCommand: const <String>['testforwardport'],
forwardPortSuccessRegex: RegExp('testforwardportsuccess')
forwardPortSuccessRegex: RegExp('testforwardportsuccess'),
screenshotCommand: const <String>['testscreenshot']
);
const String testConfigPingSuccessOutput = 'testpingsuccess\n';
@ -520,6 +522,63 @@ void main() {
ProcessManager: () => FakeProcessManager.any()
}
);
testWithoutContext('CustomDevice screenshotting', () async {
bool screenshotCommandWasExecuted = false;
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: testConfig.screenshotCommand,
onRun: () => screenshotCommandWasExecuted = true,
)
]);
final MemoryFileSystem fs = MemoryFileSystem.test();
final File screenshotFile = fs.file('screenshot.png');
final CustomDevice device = CustomDevice(
config: testConfig,
logger: BufferLogger.test(),
processManager: processManager
);
expect(device.supportsScreenshot, true);
await device.takeScreenshot(screenshotFile);
expect(screenshotCommandWasExecuted, true);
expect(screenshotFile, exists);
});
testWithoutContext('CustomDevice without screenshotting support', () async {
bool screenshotCommandWasExecuted = false;
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: testConfig.screenshotCommand,
onRun: () => screenshotCommandWasExecuted = true,
)
]);
final MemoryFileSystem fs = MemoryFileSystem.test();
final File screenshotFile = fs.file('screenshot.png');
final CustomDevice device = CustomDevice(
config: testConfig.copyWith(
explicitScreenshotCommand: true,
screenshotCommand: null
),
logger: BufferLogger.test(),
processManager: processManager
);
expect(device.supportsScreenshot, false);
expect(
() => device.takeScreenshot(screenshotFile),
throwsA(const TypeMatcher<UnsupportedError>()),
);
expect(screenshotCommandWasExecuted, false);
expect(screenshotFile.existsSync(), false);
});
}
class FakeBundleBuilder extends Fake implements BundleBuilder {