diff --git a/testing/scenario_app/bin/run_android_tests.dart b/testing/scenario_app/bin/run_android_tests.dart index 71ff683d40b10..a4782f31cf9b1 100644 --- a/testing/scenario_app/bin/run_android_tests.dart +++ b/testing/scenario_app/bin/run_android_tests.dart @@ -91,6 +91,7 @@ void main(List args) async { ndkStack: options.ndkStack, forceSurfaceProducerSurfaceTexture: options.forceSurfaceProducerSurfaceTexture, prefixLogsPerRun: options.prefixLogsPerRun, + recordScreen: options.recordScreen, ); onSigint.cancel(); exit(0); @@ -135,6 +136,7 @@ Future _run({ required String ndkStack, required bool forceSurfaceProducerSurfaceTexture, required bool prefixLogsPerRun, + required bool recordScreen, }) async { const ProcessManager pm = LocalProcessManager(); final String scenarioAppPath = join(outDir.path, 'scenario_app'); @@ -230,6 +232,7 @@ Future _run({ late Process logcatProcess; late Future logcatProcessExitCode; _ImpellerBackend? actualImpellerBackend; + Process? screenRecordProcess; final IOSink logcat = File(logcatPath).openWrite(); try { @@ -361,6 +364,36 @@ Future _run({ } }); + if (recordScreen) { + await step('Recording screen...', () async { + // Create a /tmp directory on the device to store the screen recording. + final int exitCode = await pm.runAndForward([ + adb.path, + 'shell', + 'mkdir', + '-p', + join(_emulatorStoragePath, 'tmp'), + ]); + if (exitCode != 0) { + panic(['could not create /tmp directory on device']); + } + final String screenRecordingPath = join( + _emulatorStoragePath, + 'tmp', + 'screen.mp4', + ); + screenRecordProcess = await pm.start([ + adb.path, + 'shell', + 'screenrecord', + '--time-limit=0', + '--bugreport', + screenRecordingPath, + ]); + log('writing screen recording to $screenRecordingPath'); + }); + } + await step('Running instrumented tests...', () async { final (int exitCode, StringBuffer out) = await pm.runAndCapture([ adb.path, @@ -368,7 +401,7 @@ Future _run({ 'am', 'instrument', '-w', - '--no-window-animation', + '--no-window-animation', if (smokeTestFullPath != null) '-e class $smokeTestFullPath', if (enableImpeller) @@ -447,6 +480,47 @@ Future _run({ } }); + if (screenRecordProcess != null) { + await step('Killing screen recording process...', () async { + // Kill the screen recording process. + screenRecordProcess!.kill(ProcessSignal.sigkill); + await screenRecordProcess!.exitCode; + + // Pull the screen recording from the device. + final String screenRecordingPath = join( + _emulatorStoragePath, + 'tmp', + 'screen.mp4', + ); + final String screenRecordingLocalPath = join( + logsDir.path, + 'screen.mp4', + ); + final int exitCode = await pm.runAndForward([ + adb.path, + 'pull', + screenRecordingPath, + screenRecordingLocalPath, + ]); + if (exitCode != 0) { + logError('could not pull screen recording from device'); + } + + log('wrote screen recording to $screenRecordingLocalPath'); + + // Remove the screen recording from the device. + final int removeExitCode = await pm.runAndForward([ + adb.path, + 'shell', + 'rm', + screenRecordingPath, + ]); + if (removeExitCode != 0) { + logError('could not remove screen recording from device'); + } + }); + } + await step('Killing logcat process...', () async { final bool delivered = logcatProcess.kill(ProcessSignal.sigkill); assert(delivered); @@ -536,6 +610,8 @@ Future _run({ } } +const String _emulatorStoragePath = '/storage/emulated/0/Download'; + void _withTemporaryCwd(String path, void Function() callback) { final String originalCwd = Directory.current.path; Directory.current = Directory(path).path; diff --git a/testing/scenario_app/bin/utils/options.dart b/testing/scenario_app/bin/utils/options.dart index 36364cf83ba39..e1c785a42f767 100644 --- a/testing/scenario_app/bin/utils/options.dart +++ b/testing/scenario_app/bin/utils/options.dart @@ -165,6 +165,11 @@ extension type const Options._(ArgResults _args) { defaultsTo: environment.isCi, hide: hideUnusualOptions, ) + ..addFlag( + 'record-screen', + help: 'Whether to record the screen during the test run.', + defaultsTo: environment.isCi, + ) ..addOption( 'impeller-backend', help: 'The graphics backend to use when --enable-impeller is true. ' @@ -286,6 +291,9 @@ extension type const Options._(ArgResults _args) { /// Whether to enable Impeller as the graphics backend. bool get enableImpeller => _args['enable-impeller'] as bool; + /// Whether to record the screen during the test run. + bool get recordScreen => _args['record-screen'] as bool; + /// The graphics backend to use when --enable-impeller is true. String get impellerBackend => _args['impeller-backend'] as String;