Skip to content

Commit def8afe

Browse files
improve espresso support
1 parent b734913 commit def8afe

File tree

5 files changed

+1906
-56
lines changed

5 files changed

+1906
-56
lines changed

src/cli.ts

Lines changed: 147 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Command } from 'commander';
22
import logger from './logger';
33
import Auth from './auth';
44
import 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';
610
import XCUITestOptions from './models/xcuitest_options';
711
import XCUITest from './providers/xcuitest';
812
import 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

Comments
 (0)