@@ -7,7 +7,11 @@ import EspressoOptions, {
77 ReportFormat as EspressoReportFormat ,
88 ThrottleNetwork as EspressoThrottleNetwork ,
99} from './models/espresso_options' ;
10- import XCUITestOptions from './models/xcuitest_options' ;
10+ import XCUITestOptions , {
11+ Orientation as XCUITestOrientation ,
12+ ThrottleNetwork as XCUITestThrottleNetwork ,
13+ ReportFormat as XCUITestReportFormat ,
14+ } from './models/xcuitest_options' ;
1115import XCUITest from './providers/xcuitest' ;
1216import packageJson from '../package.json' ;
1317import MaestroOptions , {
@@ -360,17 +364,100 @@ const maestroCommand = program
360364 } )
361365 . showHelpAfterError ( true ) ;
362366
363- program
367+ const xcuitestCommand = program
364368 . command ( 'xcuitest' )
365- . description ( 'Bootstrap an XCUITest project.' )
366- . requiredOption ( '--app <string>' , 'Path to application under test.' )
367- . requiredOption ( '--device <device>' , 'Real device to use for testing.' )
368- . requiredOption ( '--test-app <string>' , 'Path to test application.' )
369+ . description ( 'Run XCUITest tests on TestingBot.' )
370+ . argument ( '[appFile]' , 'Path to application IPA file' )
371+ . argument ( '[testAppFile]' , 'Path to test ZIP file containing XCUITests' )
372+ // App and test options
373+ . option ( '--app <path>' , 'Path to application IPA file.' )
374+ . option ( '--test-app <path>' , 'Path to test ZIP file containing XCUITests.' )
375+ // Device configuration
376+ . option (
377+ '--device <device>' ,
378+ 'Device name to use for testing (e.g., "iPhone 15", "iPad.*").' ,
379+ )
380+ . option ( '--platform-version <version>' , 'iOS version (e.g., "17.0", "18.2").' )
381+ . option ( '--real-device' , 'Use a real device instead of a simulator.' )
382+ . option ( '--tablet-only' , 'Only allocate tablet devices.' )
383+ . option ( '--phone-only' , 'Only allocate phone devices.' )
384+ . option (
385+ '--orientation <orientation>' ,
386+ 'Screen orientation: PORTRAIT or LANDSCAPE.' ,
387+ ( val ) => val . toUpperCase ( ) as XCUITestOrientation ,
388+ )
389+ . option ( '--locale <locale>' , 'Device locale (e.g., "DE", "US").' )
390+ . option (
391+ '--timezone <timezone>' ,
392+ 'Device timezone (e.g., "New_York", "Europe/London").' ,
393+ )
394+ // Test metadata
395+ . option ( '--name <name>' , 'Test name for identification in dashboard.' )
396+ . option ( '--build <build>' , 'Build identifier for grouping test runs.' )
397+ // Localization
398+ . option (
399+ '--language <lang>' ,
400+ 'App language (ISO 639-1 code, e.g., "en", "fr", "de").' ,
401+ )
402+ // Geolocation
403+ . option (
404+ '--geo-location <code>' ,
405+ 'Geographic IP location (ISO country code, e.g., "US", "DE").' ,
406+ )
407+ // Network throttling
408+ . option (
409+ '--throttle-network <speed>' ,
410+ 'Network throttling: 4G, 3G, Edge, or airplane.' ,
411+ ( val ) => val as XCUITestThrottleNetwork ,
412+ )
413+ // Execution mode
414+ . option ( '-q, --quiet' , 'Quieter console output without progress updates.' )
415+ . option (
416+ '--async' ,
417+ 'Start tests and exit immediately without waiting for results.' ,
418+ )
419+ // Report options
420+ . option (
421+ '--report <format>' ,
422+ 'Download test report after completion: html or junit.' ,
423+ ( val ) => val . toLowerCase ( ) as XCUITestReportFormat ,
424+ )
425+ . option (
426+ '--report-output-dir <path>' ,
427+ 'Directory to save test reports (required when --report is used).' ,
428+ )
429+ // Authentication
369430 . option ( '--api-key <key>' , 'TestingBot API key.' )
370431 . option ( '--api-secret <secret>' , 'TestingBot API secret.' )
371- . action ( async ( args ) => {
432+ . action ( async ( appFileArg , testAppFileArg , args ) => {
372433 try {
373- const options = new XCUITestOptions ( args . app , args . testApp , args . device ) ;
434+ // Positional arguments take precedence, fall back to options
435+ const app = appFileArg || args . app ;
436+ const testApp = testAppFileArg || args . testApp ;
437+
438+ if ( ! app || ! testApp ) {
439+ xcuitestCommand . help ( ) ;
440+ return ;
441+ }
442+
443+ const options = new XCUITestOptions ( app , testApp , args . device , {
444+ version : args . platformVersion ,
445+ realDevice : args . realDevice ,
446+ tabletOnly : args . tabletOnly ,
447+ phoneOnly : args . phoneOnly ,
448+ name : args . name ,
449+ build : args . build ,
450+ orientation : args . orientation ,
451+ language : args . language ,
452+ locale : args . locale ,
453+ timeZone : args . timezone ,
454+ geoLocation : args . geoLocation ,
455+ throttleNetwork : args . throttleNetwork ,
456+ quiet : args . quiet ,
457+ async : args . async ,
458+ report : args . report ,
459+ reportOutputDir : args . reportOutputDir ,
460+ } ) ;
374461 const credentials = await Auth . getCredentials ( {
375462 apiKey : args . apiKey ,
376463 apiSecret : args . apiSecret ,
@@ -381,13 +468,18 @@ program
381468 ) ;
382469 }
383470 const xcuitest = new XCUITest ( credentials , options ) ;
384- await xcuitest . run ( ) ;
471+ const result = await xcuitest . run ( ) ;
472+ if ( ! result . success ) {
473+ process . exitCode = 1 ;
474+ }
385475 } catch ( err ) {
386476 logger . error (
387477 `XCUITest error: ${ err instanceof Error ? err . message : err } ` ,
388478 ) ;
479+ process . exitCode = 1 ;
389480 }
390- } ) ;
481+ } )
482+ . showHelpAfterError ( true ) ;
391483
392484program
393485 . command ( 'login' )
0 commit comments