@@ -14,6 +14,8 @@ const usage = `Usage:
1414 node scripts/capture-native-qa-screenshot.mjs --matrix <runtime|accessibility|performance> --row <row-id> --platform <ios|android> [options]
1515
1616Options:
17+ --android-log-output <path> Also clear/capture Android logcat to this repo-relative path.
18+ --android-log-lines <number> Number of trailing logcat lines to capture. Defaults to 400.
1719 --device <id> iOS simulator UDID or Android adb serial. Defaults to booted/default device.
1820 --dry-run Print launch and screenshot commands without executing them.
1921 --no-launch Capture current screen without opening the row deep link first.
@@ -26,6 +28,7 @@ Options:
2628const parseArgs = ( argv ) => {
2729 const options = {
2830 androidPackage : defaultAndroidPackage ,
31+ androidLogLines : 400 ,
2932 launch : true ,
3033 waitMs : 1500
3134 } ;
@@ -43,7 +46,11 @@ const parseArgs = (argv) => {
4346 return value ;
4447 } ;
4548
46- if ( arg === "--device" ) {
49+ if ( arg === "--android-log-lines" ) {
50+ options . androidLogLines = Number ( readValue ( ) ) ;
51+ } else if ( arg === "--android-log-output" ) {
52+ options . androidLogOutput = readValue ( ) ;
53+ } else if ( arg === "--device" ) {
4754 options . device = readValue ( ) ;
4855 } else if ( arg === "--dry-run" ) {
4956 options . dryRun = true ;
@@ -84,6 +91,17 @@ const parseArgs = (argv) => {
8491 throw new Error ( "--platform must be ios or android" ) ;
8592 }
8693
94+ if ( options . androidLogOutput && options . platform !== "android" ) {
95+ throw new Error ( "--android-log-output can only be used with Android" ) ;
96+ }
97+
98+ if (
99+ ! Number . isInteger ( options . androidLogLines ) ||
100+ options . androidLogLines <= 0
101+ ) {
102+ throw new Error ( "--android-log-lines must be a positive integer" ) ;
103+ }
104+
87105 if ( ! Number . isFinite ( options . waitMs ) || options . waitMs < 0 ) {
88106 throw new Error ( "--wait-ms must be a non-negative number" ) ;
89107 }
@@ -114,6 +132,24 @@ const defaultOutputForRow = (rowId) =>
114132
115133const sleep = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
116134
135+ const shouldWaitAfterCommand = ( { args, command } ) =>
136+ ( command === "xcrun" && args [ 1 ] === "openurl" ) ||
137+ ( command === "adb" && args . includes ( "start" ) ) ;
138+
139+ const validateCaptureOptions = ( {
140+ androidLogLines,
141+ androidLogOutput,
142+ platform
143+ } ) => {
144+ if ( androidLogOutput && platform !== "android" ) {
145+ throw new Error ( "--android-log-output can only be used with Android" ) ;
146+ }
147+
148+ if ( ! Number . isInteger ( androidLogLines ) || androidLogLines <= 0 ) {
149+ throw new Error ( "--android-log-lines must be a positive integer" ) ;
150+ }
151+ } ;
152+
117153const runCommand = ( { args, command, encoding = "utf8" } ) => {
118154 const result = spawnSync ( command , args , {
119155 encoding,
@@ -127,13 +163,9 @@ const runCommand = ({ args, command, encoding = "utf8" }) => {
127163
128164 if ( result . status !== 0 ) {
129165 const stderr =
130- encoding === "buffer"
131- ? result . stderr ?. toString ( "utf8" )
132- : result . stderr ;
166+ encoding === "buffer" ? result . stderr ?. toString ( "utf8" ) : result . stderr ;
133167 const stdout =
134- encoding === "buffer"
135- ? result . stdout ?. toString ( "utf8" )
136- : result . stdout ;
168+ encoding === "buffer" ? result . stdout ?. toString ( "utf8" ) : result . stdout ;
137169 const detail = ( stderr || stdout || "" ) . trim ( ) ;
138170 const suffix = detail ? `\n\n${ detail } ` : "" ;
139171
@@ -182,13 +214,22 @@ const buildAndroidLaunchArgs = ({ androidPackage, device, launchUrl }) => [
182214] ;
183215
184216const buildAndroidCommands = ( {
217+ androidLogLines,
218+ androidLogOutputPath,
185219 androidPackage,
186220 device,
187221 launch,
188222 launchUrl
189223} ) => {
190224 const commands = [ ] ;
191225
226+ if ( androidLogOutputPath ) {
227+ commands . push ( {
228+ args : [ ...( device ? [ "-s" , device ] : [ ] ) , "shell" , "logcat" , "-c" ] ,
229+ command : "adb"
230+ } ) ;
231+ }
232+
192233 if ( launch ) {
193234 commands . push ( {
194235 args : buildAndroidLaunchArgs ( { androidPackage, device, launchUrl } ) ,
@@ -202,6 +243,22 @@ const buildAndroidCommands = ({
202243 writesStdoutToFile : true
203244 } ) ;
204245
246+ if ( androidLogOutputPath ) {
247+ commands . push ( {
248+ args : [
249+ ...( device ? [ "-s" , device ] : [ ] ) ,
250+ "logcat" ,
251+ "-d" ,
252+ "-t" ,
253+ String ( androidLogLines )
254+ ] ,
255+ command : "adb" ,
256+ encoding : "utf8" ,
257+ outputPath : androidLogOutputPath ,
258+ writesStdoutToFile : true
259+ } ) ;
260+ }
261+
205262 return commands ;
206263} ;
207264
@@ -227,6 +284,8 @@ const findRow = async ({ matrixName, repoRoot, rowId }) => {
227284} ;
228285
229286export const createNativeQaScreenshotPlan = async ( {
287+ androidLogLines = 400 ,
288+ androidLogOutput,
230289 androidPackage = defaultAndroidPackage ,
231290 device,
232291 launch = true ,
@@ -237,10 +296,17 @@ export const createNativeQaScreenshotPlan = async ({
237296 rowId,
238297 waitMs = 1500
239298} ) => {
299+ validateCaptureOptions ( { androidLogLines, androidLogOutput, platform } ) ;
300+
240301 const row = await findRow ( { matrixName, repoRoot, rowId } ) ;
241302 const outputPath = output ?? defaultOutputForRow ( rowId ) ;
242303 const absoluteOutputPath = path . resolve ( repoRoot , outputPath ) ;
304+ const absoluteAndroidLogOutputPath = androidLogOutput
305+ ? path . resolve ( repoRoot , androidLogOutput )
306+ : undefined ;
243307 const commandOptions = {
308+ androidLogLines,
309+ androidLogOutputPath : absoluteAndroidLogOutputPath ,
244310 androidPackage,
245311 device,
246312 launch,
@@ -253,11 +319,20 @@ export const createNativeQaScreenshotPlan = async ({
253319 : buildAndroidCommands ( commandOptions ) ;
254320
255321 return {
322+ absoluteAndroidLogOutputPath,
256323 absoluteOutputPath,
324+ androidLogOutput,
257325 commands,
258326 launchUrl : row . launchUrl ,
259327 outputPath,
260- recordCommand : `npm run release:qa:record -- --matrix ${ matrixName } --row ${ rowId } --status partial --evidence ${ outputPath } ` ,
328+ recordCommand : [
329+ `npm run release:qa:record -- --matrix ${ matrixName } --row ${ rowId } ` ,
330+ "--status partial" ,
331+ `--evidence ${ outputPath } ` ,
332+ androidLogOutput ? `--evidence ${ androidLogOutput } ` : ""
333+ ]
334+ . filter ( Boolean )
335+ . join ( " " ) ,
261336 row,
262337 waitMs
263338 } ;
@@ -275,22 +350,28 @@ export const captureNativeQaScreenshot = async ({
275350 }
276351
277352 await mkdir ( path . dirname ( plan . absoluteOutputPath ) , { recursive : true } ) ;
353+ if ( plan . absoluteAndroidLogOutputPath ) {
354+ await mkdir ( path . dirname ( plan . absoluteAndroidLogOutputPath ) , {
355+ recursive : true
356+ } ) ;
357+ }
278358
279- for ( const [ index , command ] of plan . commands . entries ( ) ) {
280- const isLastCommand = index === plan . commands . length - 1 ;
281-
282- if ( isLastCommand && command . writesStdoutToFile ) {
283- const screenshot = runner ( {
359+ for ( const command of plan . commands ) {
360+ if ( command . writesStdoutToFile ) {
361+ const commandOutput = runner ( {
284362 args : command . args ,
285363 command : command . command ,
286- encoding : "buffer"
364+ encoding : command . encoding ?? "buffer"
287365 } ) ;
288- await writeFile ( plan . absoluteOutputPath , screenshot ) ;
366+ await writeFile (
367+ command . outputPath ?? plan . absoluteOutputPath ,
368+ commandOutput
369+ ) ;
289370 } else {
290371 runner ( command ) ;
291372 }
292373
293- if ( ! isLastCommand && plan . waitMs > 0 ) {
374+ if ( shouldWaitAfterCommand ( command ) && plan . waitMs > 0 ) {
294375 await sleep ( plan . waitMs ) ;
295376 }
296377 }
@@ -310,7 +391,11 @@ const main = async () => {
310391
311392 for ( const command of result . commands ) {
312393 const suffix = command . writesStdoutToFile
313- ? ` > ${ shellQuote ( result . outputPath ) } `
394+ ? ` > ${ shellQuote (
395+ command . outputPath
396+ ? path . relative ( defaultRepoRoot , command . outputPath )
397+ : result . outputPath
398+ ) } `
314399 : "" ;
315400 console . log ( `$ ${ commandText ( command . command , command . args ) } ${ suffix } ` ) ;
316401 }
0 commit comments