Unverified Commit 34639464 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Take screenshot when drive fails (#78822)

parent 49adef67
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/host_agent.dart';
import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
...@@ -29,6 +30,8 @@ void main() { ...@@ -29,6 +30,8 @@ void main() {
p.join(complexLayoutPath, 'test_driver', 'semantics_perf.dart'), p.join(complexLayoutPath, 'test_driver', 'semantics_perf.dart'),
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
}); });
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/host_agent.dart';
import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/framework/utils.dart';
...@@ -15,6 +16,8 @@ Future<String> _runWithMode(String mode, String deviceId) async { ...@@ -15,6 +16,8 @@ Future<String> _runWithMode(String mode, String deviceId) async {
'test_driver/scroll_perf.dart', 'test_driver/scroll_perf.dart',
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
return stderr.toString(); return stderr.toString();
} }
......
...@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path; ...@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/host_agent.dart';
import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/framework/utils.dart';
...@@ -24,7 +25,16 @@ void main() { ...@@ -24,7 +25,16 @@ void main() {
await inDirectory(appDir, () async { await inDirectory(appDir, () async {
return flutter( return flutter(
'drive', 'drive',
options: <String>['--verbose', '-d', device.deviceId, '--route', '/smuggle-it', 'lib/route.dart'], options: <String>[
'--verbose',
'-d',
device.deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
'--route',
'/smuggle-it',
'lib/route.dart',
],
canFail: false, canFail: false,
); );
}); });
......
...@@ -6,9 +6,9 @@ import 'dart:convert'; ...@@ -6,9 +6,9 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import '../framework/adb.dart'; import '../framework/adb.dart';
import '../framework/framework.dart'; import '../framework/framework.dart';
import '../framework/host_agent.dart';
import '../framework/task_result.dart'; import '../framework/task_result.dart';
import '../framework/utils.dart'; import '../framework/utils.dart';
...@@ -101,6 +101,8 @@ class GalleryTransitionTest { ...@@ -101,6 +101,8 @@ class GalleryTransitionTest {
'test_driver/$testDriver.dart', 'test_driver/$testDriver.dart',
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
}); });
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import '../framework/adb.dart'; import '../framework/adb.dart';
import '../framework/framework.dart'; import '../framework/framework.dart';
import '../framework/host_agent.dart';
import '../framework/task_result.dart'; import '../framework/task_result.dart';
import '../framework/utils.dart'; import '../framework/utils.dart';
...@@ -151,6 +152,8 @@ class DriverTest { ...@@ -151,6 +152,8 @@ class DriverTest {
testTarget, testTarget,
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
...extraOptions, ...extraOptions,
]; ];
await flutter('drive', options: options, environment: Map<String, String>.from(environment)); await flutter('drive', options: options, environment: Map<String, String>.from(environment));
......
...@@ -12,6 +12,7 @@ import 'package:path/path.dart' as path; ...@@ -12,6 +12,7 @@ import 'package:path/path.dart' as path;
import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/host_agent.dart';
import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/framework/utils.dart';
...@@ -292,6 +293,8 @@ TaskFunction createStackSizeTest() { ...@@ -292,6 +293,8 @@ TaskFunction createStackSizeTest() {
'--driver', testDriver, '--driver', testDriver,
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
final Map<String, dynamic> data = json.decode( final Map<String, dynamic> data = json.decode(
file('$testDirectory/build/stack_size.json').readAsStringSync(), file('$testDirectory/build/stack_size.json').readAsStringSync(),
...@@ -387,6 +390,8 @@ TaskFunction createsScrollSmoothnessPerfTest() { ...@@ -387,6 +390,8 @@ TaskFunction createsScrollSmoothnessPerfTest() {
'-t', testTarget, '-t', testTarget,
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
final Map<String, dynamic> data = json.decode( final Map<String, dynamic> data = json.decode(
file('$testDirectory/build/scroll_smoothness_test.json').readAsStringSync(), file('$testDirectory/build/scroll_smoothness_test.json').readAsStringSync(),
...@@ -436,6 +441,8 @@ TaskFunction createFramePolicyIntegrationTest() { ...@@ -436,6 +441,8 @@ TaskFunction createFramePolicyIntegrationTest() {
'-t', testTarget, '-t', testTarget,
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
final Map<String, dynamic> data = json.decode( final Map<String, dynamic> data = json.decode(
file('$testDirectory/build/frame_policy_event_delay.json').readAsStringSync(), file('$testDirectory/build/frame_policy_event_delay.json').readAsStringSync(),
...@@ -689,6 +696,8 @@ class PerfTest { ...@@ -689,6 +696,8 @@ class PerfTest {
...<String>['--dart-define', dartDefine], ...<String>['--dart-define', dartDefine],
'-d', '-d',
deviceId, deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
]); ]);
final Map<String, dynamic> data = json.decode( final Map<String, dynamic> data = json.decode(
file('$testDirectory/build/$resultFilename.json').readAsStringSync(), file('$testDirectory/build/$resultFilename.json').readAsStringSync(),
...@@ -1334,6 +1343,8 @@ class DevToolsMemoryTest { ...@@ -1334,6 +1343,8 @@ class DevToolsMemoryTest {
options: <String>[ options: <String>[
'--use-existing-app', _observatoryUri, '--use-existing-app', _observatoryUri,
'-d', _device.deviceId, '-d', _device.deviceId,
'--screenshot',
hostAgent.dumpDirectory.path,
'--profile', '--profile',
driverTest, driverTest,
], ],
......
...@@ -157,6 +157,7 @@ List<FlutterCommand> generateCommands({ ...@@ -157,6 +157,7 @@ List<FlutterCommand> generateCommands({
DriveCommand(verboseHelp: verboseHelp, DriveCommand(verboseHelp: verboseHelp,
fileSystem: globals.fs, fileSystem: globals.fs,
logger: globals.logger, logger: globals.logger,
platform: globals.platform,
), ),
EmulatorsCommand(), EmulatorsCommand(),
FormatCommand(), FormatCommand(),
......
...@@ -15,6 +15,7 @@ import '../artifacts.dart'; ...@@ -15,6 +15,7 @@ import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../device.dart'; import '../device.dart';
...@@ -50,9 +51,11 @@ class DriveCommand extends RunCommandBase { ...@@ -50,9 +51,11 @@ class DriveCommand extends RunCommandBase {
@visibleForTesting FlutterDriverFactory flutterDriverFactory, @visibleForTesting FlutterDriverFactory flutterDriverFactory,
@required FileSystem fileSystem, @required FileSystem fileSystem,
@required Logger logger, @required Logger logger,
@required Platform platform,
}) : _flutterDriverFactory = flutterDriverFactory, }) : _flutterDriverFactory = flutterDriverFactory,
_fileSystem = fileSystem, _fileSystem = fileSystem,
_logger = logger, _logger = logger,
_fsUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform),
super(verboseHelp: verboseHelp) { super(verboseHelp: verboseHelp) {
requiresPubspecYaml(); requiresPubspecYaml();
addEnableExperimentation(hide: !verboseHelp); addEnableExperimentation(hide: !verboseHelp);
...@@ -90,6 +93,10 @@ class DriveCommand extends RunCommandBase { ...@@ -90,6 +93,10 @@ class DriveCommand extends RunCommandBase {
help: '(deprecated) Build the app before running. To use an existing app, pass the "--use-application-binary" ' help: '(deprecated) Build the app before running. To use an existing app, pass the "--use-application-binary" '
'flag with an existing APK.', 'flag with an existing APK.',
) )
..addOption('screenshot',
valueHelp: 'path/to/directory',
help: 'Directory location to write screenshots on test failure.',
)
..addOption('driver-port', ..addOption('driver-port',
defaultsTo: '4444', defaultsTo: '4444',
help: 'The port where Webdriver server is launched at.', help: 'The port where Webdriver server is launched at.',
...@@ -147,6 +154,7 @@ class DriveCommand extends RunCommandBase { ...@@ -147,6 +154,7 @@ class DriveCommand extends RunCommandBase {
FlutterDriverFactory _flutterDriverFactory; FlutterDriverFactory _flutterDriverFactory;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final Logger _logger; final Logger _logger;
final FileSystemUtils _fsUtils;
@override @override
final String name = 'drive'; final String name = 'drive';
...@@ -159,6 +167,8 @@ class DriveCommand extends RunCommandBase { ...@@ -159,6 +167,8 @@ class DriveCommand extends RunCommandBase {
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser); String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
String get screenshot => stringArg('screenshot');
@override @override
bool get startPausedDefault => true; bool get startPausedDefault => true;
...@@ -189,6 +199,9 @@ class DriveCommand extends RunCommandBase { ...@@ -189,6 +199,9 @@ class DriveCommand extends RunCommandBase {
if (device == null) { if (device == null) {
throwToolExit(null); throwToolExit(null);
} }
if (screenshot != null && !device.supportsScreenshot) {
throwToolExit('Screenshot not supported for ${device.name}.');
}
final bool web = device is WebServerDevice || device is ChromiumDevice; final bool web = device is WebServerDevice || device is ChromiumDevice;
_flutterDriverFactory ??= FlutterDriverFactory( _flutterDriverFactory ??= FlutterDriverFactory(
...@@ -198,7 +211,7 @@ class DriveCommand extends RunCommandBase { ...@@ -198,7 +211,7 @@ class DriveCommand extends RunCommandBase {
dartSdkPath: globals.artifacts.getArtifactPath(Artifact.engineDartBinary), dartSdkPath: globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
); );
final PackageConfig packageConfig = await loadPackageConfigWithLogging( final PackageConfig packageConfig = await loadPackageConfigWithLogging(
globals.fs.file('.packages'), _fileSystem.file('.packages'),
logger: _logger, logger: _logger,
throwOnError: false, throwOnError: false,
) ?? PackageConfig.empty; ) ?? PackageConfig.empty;
...@@ -253,6 +266,9 @@ class DriveCommand extends RunCommandBase { ...@@ -253,6 +266,9 @@ class DriveCommand extends RunCommandBase {
: null, : null,
androidEmulator: boolArg('android-emulator'), androidEmulator: boolArg('android-emulator'),
); );
if (testResult != 0 && screenshot != null) {
await takeScreenshot(device, screenshot, _fileSystem, _logger, _fsUtils);
}
if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) { if (boolArg('keep-app-running') ?? (argResults['use-existing-app'] != null)) {
_logger.printStatus('Leaving the application running.'); _logger.printStatus('Leaving the application running.');
...@@ -311,3 +327,26 @@ class DriveCommand extends RunCommandBase { ...@@ -311,3 +327,26 @@ class DriveCommand extends RunCommandBase {
return '${pathWithNoExtension}_test${_fileSystem.path.extension(appFile)}'; return '${pathWithNoExtension}_test${_fileSystem.path.extension(appFile)}';
} }
} }
@visibleForTesting
Future<void> takeScreenshot(
Device device,
String screenshotPath,
FileSystem fileSystem,
Logger logger,
FileSystemUtils fileSystemUtils,
) async {
try {
final Directory outputDirectory = fileSystem.directory(screenshotPath);
outputDirectory.createSync(recursive: true);
final File outputFile = fileSystemUtils.getUniqueFile(
outputDirectory,
'drive',
'png',
);
await device.takeScreenshot(outputFile);
logger.printStatus('Screenshot written to ${outputFile.path}');
} on Exception catch (error) {
logger.printError('Error taking screenshot: $error');
}
}
// 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.
// @dart = 2.8
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/commands/drive.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
void main() {
FileSystem fileSystem;
BufferLogger logger;
Platform platform;
setUp(() {
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
platform = FakePlatform(operatingSystem: 'linux');
});
testWithoutContext('drive --screenshot writes to expected output', () async {
final Device screenshotDevice = ScreenshotDevice();
await takeScreenshot(
screenshotDevice,
'drive_screenshots',
fileSystem,
logger,
FileSystemUtils(
fileSystem: fileSystem,
platform: platform,
),
);
expect(logger.statusText, contains('Screenshot written to drive_screenshots/drive_01.png'));
});
testWithoutContext('drive --screenshot errors but does not fail if screenshot fails', () async {
final Device screenshotDevice = ScreenshotDevice();
fileSystem.file('drive_screenshots').createSync();
await takeScreenshot(
screenshotDevice,
'drive_screenshots',
fileSystem,
logger,
FileSystemUtils(
fileSystem: fileSystem,
platform: platform,
),
);
expect(logger.statusText, isEmpty);
expect(logger.errorText, contains('Error taking screenshot: FileSystemException: Not a directory'));
});
}
class ScreenshotDevice extends Fake implements Device {
@override
Future<void> takeScreenshot(File outputFile) async {}
}
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