@@ -2,7 +2,11 @@ import { Command } from 'commander';
22import logger from './logger' ;
33import Auth from './auth' ;
44import Espresso from './providers/espresso' ;
5- import EspressoOptions from './models/espresso_options' ;
5+ import EspressoOptions , {
6+ TestSize ,
7+ ReportFormat as EspressoReportFormat ,
8+ ThrottleNetwork as EspressoThrottleNetwork ,
9+ } from './models/espresso_options' ;
610import XCUITestOptions from './models/xcuitest_options' ;
711import XCUITest from './providers/xcuitest' ;
812import packageJson from '../package.json' ;
@@ -23,26 +27,148 @@ program
2327 'CLI tool to run Espresso, XCUITest and Maestro tests on TestingBot cloud' ,
2428 ) ;
2529
26- program
30+ const espressoCommand = program
2731 . command ( 'espresso' )
28- . description ( 'Bootstrap an Espresso project.' )
29- . requiredOption ( '--app <string>' , 'Path to application under test.' )
30- . requiredOption ( '--device <device>' , 'Real device to use for testing.' )
31- . requiredOption (
32- '--emulator <emulator>' ,
33- 'Android emulator/device to use for testing.' ,
32+ . description ( 'Run Espresso tests on TestingBot.' )
33+ . argument ( '[appFile]' , 'Path to application APK file' )
34+ . argument ( '[testAppFile]' , 'Path to test APK file containing Espresso tests' )
35+ // App and test options
36+ . option ( '--app <path>' , 'Path to application APK file.' )
37+ . option (
38+ '--test-app <path>' ,
39+ 'Path to test APK file containing Espresso tests.' ,
3440 )
35- . requiredOption ( '--test-app <string>' , 'Path to test application.' )
41+ // Device configuration
42+ . option (
43+ '--device <device>' ,
44+ 'Device name to use for testing (e.g., "Pixel 6", "Samsung.*").' ,
45+ )
46+ . option (
47+ '--platform-version <version>' ,
48+ 'Android OS version (e.g., "12", "13").' ,
49+ )
50+ . option ( '--real-device' , 'Use a real device instead of an emulator.' )
51+ . option ( '--tablet-only' , 'Only allocate tablet devices.' )
52+ . option ( '--phone-only' , 'Only allocate phone devices.' )
53+ . option ( '--locale <locale>' , 'Device locale (e.g., "en_US", "de_DE").' )
54+ . option (
55+ '--timezone <timezone>' ,
56+ 'Device timezone (e.g., "America/New_York", "Europe/London").' ,
57+ )
58+ // Test metadata
59+ . option ( '--name <name>' , 'Test name for identification in dashboard.' )
60+ . option ( '--build <build>' , 'Build identifier for grouping test runs.' )
61+ // Espresso-specific options
62+ . option (
63+ '--test-runner <runner>' ,
64+ 'Custom test instrumentation runner (e.g., "${packageName}/customTestRunner").' ,
65+ )
66+ . option (
67+ '--class <classes>' ,
68+ 'Run tests in specific classes (comma-separated fully qualified names).' ,
69+ ( val ) => val . split ( ',' ) . map ( ( c ) => c . trim ( ) ) ,
70+ )
71+ . option (
72+ '--not-class <classes>' ,
73+ 'Exclude tests in specific classes (comma-separated fully qualified names).' ,
74+ ( val ) => val . split ( ',' ) . map ( ( c ) => c . trim ( ) ) ,
75+ )
76+ . option (
77+ '--package <packages>' ,
78+ 'Run tests in specific packages (comma-separated).' ,
79+ ( val ) => val . split ( ',' ) . map ( ( p ) => p . trim ( ) ) ,
80+ )
81+ . option (
82+ '--not-package <packages>' ,
83+ 'Exclude tests in specific packages (comma-separated).' ,
84+ ( val ) => val . split ( ',' ) . map ( ( p ) => p . trim ( ) ) ,
85+ )
86+ . option (
87+ '--annotation <annotations>' ,
88+ 'Run tests with specific annotations (comma-separated).' ,
89+ ( val ) => val . split ( ',' ) . map ( ( a ) => a . trim ( ) ) ,
90+ )
91+ . option (
92+ '--not-annotation <annotations>' ,
93+ 'Exclude tests with specific annotations (comma-separated).' ,
94+ ( val ) => val . split ( ',' ) . map ( ( a ) => a . trim ( ) ) ,
95+ )
96+ . option (
97+ '--size <sizes>' ,
98+ 'Run tests by size: small, medium, large (comma-separated).' ,
99+ ( val ) => val . split ( ',' ) . map ( ( s ) => s . trim ( ) . toLowerCase ( ) as TestSize ) ,
100+ )
101+ // Localization
102+ . option (
103+ '--language <lang>' ,
104+ 'App language (ISO 639-1 code, e.g., "en", "fr", "de").' ,
105+ )
106+ // Geolocation
107+ . option (
108+ '--geo-location <code>' ,
109+ 'Geographic IP location (ISO country code, e.g., "US", "DE").' ,
110+ )
111+ // Network throttling
112+ . option (
113+ '--throttle-network <speed>' ,
114+ 'Network throttling: 4G, 3G, Edge, or airplane.' ,
115+ ( val ) => val as EspressoThrottleNetwork ,
116+ )
117+ // Execution mode
118+ . option ( '-q, --quiet' , 'Quieter console output without progress updates.' )
119+ . option (
120+ '--async' ,
121+ 'Start tests and exit immediately without waiting for results.' ,
122+ )
123+ // Report options
124+ . option (
125+ '--report <format>' ,
126+ 'Download test report after completion: html or junit.' ,
127+ ( val ) => val . toLowerCase ( ) as EspressoReportFormat ,
128+ )
129+ . option (
130+ '--report-output-dir <path>' ,
131+ 'Directory to save test reports (required when --report is used).' ,
132+ )
133+ // Authentication
36134 . option ( '--api-key <key>' , 'TestingBot API key.' )
37135 . option ( '--api-secret <secret>' , 'TestingBot API secret.' )
38- . action ( async ( args ) => {
136+ . action ( async ( appFileArg , testAppFileArg , args ) => {
39137 try {
40- const options = new EspressoOptions (
41- args . app ,
42- args . testApp ,
43- args . device ,
44- args . emulator ,
45- ) ;
138+ // Positional arguments take precedence, fall back to options
139+ const app = appFileArg || args . app ;
140+ const testApp = testAppFileArg || args . testApp ;
141+
142+ if ( ! app || ! testApp ) {
143+ espressoCommand . help ( ) ;
144+ return ;
145+ }
146+
147+ const options = new EspressoOptions ( app , testApp , args . device , {
148+ version : args . platformVersion ,
149+ realDevice : args . realDevice ,
150+ tabletOnly : args . tabletOnly ,
151+ phoneOnly : args . phoneOnly ,
152+ name : args . name ,
153+ build : args . build ,
154+ testRunner : args . testRunner ,
155+ class : args . class ,
156+ notClass : args . notClass ,
157+ package : args . package ,
158+ notPackage : args . notPackage ,
159+ annotation : args . annotation ,
160+ notAnnotation : args . notAnnotation ,
161+ size : args . size ,
162+ language : args . language ,
163+ locale : args . locale ,
164+ timeZone : args . timezone ,
165+ geoLocation : args . geoLocation ,
166+ throttleNetwork : args . throttleNetwork ,
167+ quiet : args . quiet ,
168+ async : args . async ,
169+ report : args . report ,
170+ reportOutputDir : args . reportOutputDir ,
171+ } ) ;
46172 const credentials = await Auth . getCredentials ( {
47173 apiKey : args . apiKey ,
48174 apiSecret : args . apiSecret ,
@@ -53,11 +179,15 @@ program
53179 ) ;
54180 }
55181 const espresso = new Espresso ( credentials , options ) ;
56- await espresso . run ( ) ;
182+ const result = await espresso . run ( ) ;
183+ if ( ! result . success ) {
184+ process . exitCode = 1 ;
185+ }
57186 } catch ( err ) {
58187 logger . error (
59188 `Espresso error: ${ err instanceof Error ? err . message : err } ` ,
60189 ) ;
190+ process . exitCode = 1 ;
61191 }
62192 } )
63193 . showHelpAfterError ( true ) ;
0 commit comments