@@ -4,110 +4,14 @@ import process from "node:process";
44import { spawnSync } from "node:child_process" ;
55import { fileURLToPath } from "node:url" ;
66
7+ import {
8+ captureNativeQaUsage ,
9+ defaultAndroidPackage ,
10+ parseCaptureNativeQaArgs
11+ } from "./capture-native-qa-options.mjs" ;
712import { listNativeQaRows } from "./record-native-qa-evidence.mjs" ;
813
914const defaultRepoRoot = process . cwd ( ) ;
10- const defaultAndroidPackage = "io.chartkit.showcase" ;
11- const validPlatforms = new Set ( [ "android" , "ios" ] ) ;
12-
13- const usage = `Usage:
14- node scripts/capture-native-qa-screenshot.mjs --matrix <runtime|accessibility|performance> --row <row-id> --platform <ios|android> [options]
15-
16- Options:
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.
19- --device <id> iOS simulator UDID or Android adb serial. Defaults to booted/default device.
20- --dry-run Print launch and screenshot commands without executing them.
21- --no-launch Capture current screen without opening the row deep link first.
22- --output <path> Repo-relative screenshot path. Defaults to docs/release/artifacts/<row-id>-screenshot.png.
23- --package <id> Android package id. Defaults to io.chartkit.showcase.
24- --wait-ms <number> Delay between launch and capture. Defaults to 1500.
25- --help Show this help.
26- ` ;
27-
28- const parseArgs = ( argv ) => {
29- const options = {
30- androidPackage : defaultAndroidPackage ,
31- androidLogLines : 400 ,
32- launch : true ,
33- waitMs : 1500
34- } ;
35-
36- for ( let index = 0 ; index < argv . length ; index += 1 ) {
37- const arg = argv [ index ] ;
38- const readValue = ( ) => {
39- const value = argv [ index + 1 ] ;
40-
41- if ( ! value || value . startsWith ( "--" ) ) {
42- throw new Error ( `${ arg } requires a value` ) ;
43- }
44-
45- index += 1 ;
46- return value ;
47- } ;
48-
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" ) {
54- options . device = readValue ( ) ;
55- } else if ( arg === "--dry-run" ) {
56- options . dryRun = true ;
57- } else if ( arg === "--help" || arg === "-h" ) {
58- options . help = true ;
59- } else if ( arg === "--matrix" ) {
60- options . matrixName = readValue ( ) ;
61- } else if ( arg === "--no-launch" ) {
62- options . launch = false ;
63- } else if ( arg === "--output" ) {
64- options . output = readValue ( ) ;
65- } else if ( arg === "--package" ) {
66- options . androidPackage = readValue ( ) ;
67- } else if ( arg === "--platform" ) {
68- options . platform = readValue ( ) ;
69- } else if ( arg === "--row" ) {
70- options . rowId = readValue ( ) ;
71- } else if ( arg === "--wait-ms" ) {
72- options . waitMs = Number ( readValue ( ) ) ;
73- } else {
74- throw new Error ( `Unknown argument: ${ arg } ` ) ;
75- }
76- }
77-
78- if ( options . help ) {
79- return options ;
80- }
81-
82- if ( ! options . matrixName ) {
83- throw new Error ( "--matrix is required" ) ;
84- }
85-
86- if ( ! options . rowId ) {
87- throw new Error ( "--row is required" ) ;
88- }
89-
90- if ( ! validPlatforms . has ( options . platform ) ) {
91- throw new Error ( "--platform must be ios or android" ) ;
92- }
93-
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-
105- if ( ! Number . isFinite ( options . waitMs ) || options . waitMs < 0 ) {
106- throw new Error ( "--wait-ms must be a non-negative number" ) ;
107- }
108-
109- return options ;
110- } ;
11115
11216const shellQuote = ( value ) => {
11317 const text = String ( value ) ;
@@ -139,15 +43,34 @@ const shouldWaitAfterCommand = ({ args, command }) =>
13943const validateCaptureOptions = ( {
14044 androidLogLines,
14145 androidLogOutput,
46+ iosLogLast,
47+ iosLogOutput,
48+ iosLogPredicate,
14249 platform
14350} ) => {
51+ if ( platform !== "android" && platform !== "ios" ) {
52+ throw new Error ( "--platform must be ios or android" ) ;
53+ }
54+
14455 if ( androidLogOutput && platform !== "android" ) {
14556 throw new Error ( "--android-log-output can only be used with Android" ) ;
14657 }
14758
59+ if ( iosLogOutput && platform !== "ios" ) {
60+ throw new Error ( "--ios-log-output can only be used with iOS" ) ;
61+ }
62+
14863 if ( ! Number . isInteger ( androidLogLines ) || androidLogLines <= 0 ) {
14964 throw new Error ( "--android-log-lines must be a positive integer" ) ;
15065 }
66+
67+ if ( iosLogOutput && ! iosLogLast ) {
68+ throw new Error ( "--ios-log-last must be a non-empty duration" ) ;
69+ }
70+
71+ if ( iosLogOutput && ! iosLogPredicate ) {
72+ throw new Error ( "--ios-log-predicate must be non-empty" ) ;
73+ }
15174} ;
15275
15376const runCommand = ( { args, command, encoding = "utf8" } ) => {
@@ -181,7 +104,15 @@ const runCommand = ({ args, command, encoding = "utf8" }) => {
181104
182105const getIosDeviceTarget = ( device ) => device ?? "booted" ;
183106
184- const buildIosCommands = ( { device, launch, launchUrl, outputPath } ) => {
107+ const buildIosCommands = ( {
108+ device,
109+ iosLogLast,
110+ iosLogOutputPath,
111+ iosLogPredicate,
112+ launch,
113+ launchUrl,
114+ outputPath
115+ } ) => {
185116 const target = getIosDeviceTarget ( device ) ;
186117 const commands = [ ] ;
187118
@@ -197,6 +128,28 @@ const buildIosCommands = ({ device, launch, launchUrl, outputPath }) => {
197128 command : "xcrun"
198129 } ) ;
199130
131+ if ( iosLogOutputPath ) {
132+ commands . push ( {
133+ args : [
134+ "simctl" ,
135+ "spawn" ,
136+ target ,
137+ "log" ,
138+ "show" ,
139+ "--style" ,
140+ "compact" ,
141+ "--last" ,
142+ iosLogLast ,
143+ "--predicate" ,
144+ iosLogPredicate
145+ ] ,
146+ command : "xcrun" ,
147+ encoding : "utf8" ,
148+ outputPath : iosLogOutputPath ,
149+ writesStdoutToFile : true
150+ } ) ;
151+ }
152+
200153 return commands ;
201154} ;
202155
@@ -288,6 +241,9 @@ export const createNativeQaScreenshotPlan = async ({
288241 androidLogOutput,
289242 androidPackage = defaultAndroidPackage ,
290243 device,
244+ iosLogLast = "2m" ,
245+ iosLogOutput,
246+ iosLogPredicate = 'process == "ChartKitShowcase"' ,
291247 launch = true ,
292248 matrixName,
293249 output,
@@ -296,19 +252,32 @@ export const createNativeQaScreenshotPlan = async ({
296252 rowId,
297253 waitMs = 1500
298254} ) => {
299- validateCaptureOptions ( { androidLogLines, androidLogOutput, platform } ) ;
255+ validateCaptureOptions ( {
256+ androidLogLines,
257+ androidLogOutput,
258+ iosLogLast,
259+ iosLogOutput,
260+ iosLogPredicate,
261+ platform
262+ } ) ;
300263
301264 const row = await findRow ( { matrixName, repoRoot, rowId } ) ;
302265 const outputPath = output ?? defaultOutputForRow ( rowId ) ;
303266 const absoluteOutputPath = path . resolve ( repoRoot , outputPath ) ;
304267 const absoluteAndroidLogOutputPath = androidLogOutput
305268 ? path . resolve ( repoRoot , androidLogOutput )
306269 : undefined ;
270+ const absoluteIosLogOutputPath = iosLogOutput
271+ ? path . resolve ( repoRoot , iosLogOutput )
272+ : undefined ;
307273 const commandOptions = {
308274 androidLogLines,
309275 androidLogOutputPath : absoluteAndroidLogOutputPath ,
310276 androidPackage,
311277 device,
278+ iosLogLast,
279+ iosLogOutputPath : absoluteIosLogOutputPath ,
280+ iosLogPredicate,
312281 launch,
313282 launchUrl : row . launchUrl ,
314283 outputPath : absoluteOutputPath
@@ -320,16 +289,19 @@ export const createNativeQaScreenshotPlan = async ({
320289
321290 return {
322291 absoluteAndroidLogOutputPath,
292+ absoluteIosLogOutputPath,
323293 absoluteOutputPath,
324294 androidLogOutput,
325295 commands,
296+ iosLogOutput,
326297 launchUrl : row . launchUrl ,
327298 outputPath,
328299 recordCommand : [
329300 `npm run release:qa:record -- --matrix ${ matrixName } --row ${ rowId } ` ,
330301 "--status partial" ,
331302 `--evidence ${ outputPath } ` ,
332- androidLogOutput ? `--evidence ${ androidLogOutput } ` : ""
303+ androidLogOutput ? `--evidence ${ androidLogOutput } ` : "" ,
304+ iosLogOutput ? `--evidence ${ iosLogOutput } ` : ""
333305 ]
334306 . filter ( Boolean )
335307 . join ( " " ) ,
@@ -355,6 +327,11 @@ export const captureNativeQaScreenshot = async ({
355327 recursive : true
356328 } ) ;
357329 }
330+ if ( plan . absoluteIosLogOutputPath ) {
331+ await mkdir ( path . dirname ( plan . absoluteIosLogOutputPath ) , {
332+ recursive : true
333+ } ) ;
334+ }
358335
359336 for ( const command of plan . commands ) {
360337 if ( command . writesStdoutToFile ) {
@@ -380,10 +357,10 @@ export const captureNativeQaScreenshot = async ({
380357} ;
381358
382359const main = async ( ) => {
383- const options = parseArgs ( process . argv . slice ( 2 ) ) ;
360+ const options = parseCaptureNativeQaArgs ( process . argv . slice ( 2 ) ) ;
384361
385362 if ( options . help ) {
386- console . log ( usage . trim ( ) ) ;
363+ console . log ( captureNativeQaUsage . trim ( ) ) ;
387364 return ;
388365 }
389366
0 commit comments