Unverified Commit 82830fa1 authored by Hannes Winkler's avatar Hannes Winkler Committed by GitHub

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

parent 7bff366b
......@@ -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;
......
......@@ -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),
);
}
}
......@@ -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
}
}
}
......
......@@ -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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment