Skip to content

Commit 90c13ea

Browse files
maestro improvements
1 parent 45af3a0 commit 90c13ea

File tree

7 files changed

+1142
-30
lines changed

7 files changed

+1142
-30
lines changed

package.json

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
{
22
"name": "testingbotctl",
33
"version": "1.0.0",
4-
"description": "",
4+
"description": "CLI tool to run Espresso, XCUITest, and Maestro tests on TestingBot's cloud infrastructure",
55
"main": "dist/index.js",
6+
"bin": {
7+
"testingbot": "dist/index.js"
8+
},
69
"scripts": {
710
"lint": "prettier --check '**/*.{js,ts}' && eslint --ext ts src/",
811
"build": "tsc",
@@ -16,9 +19,31 @@
1619
"release:minor": "npm run release -- minor",
1720
"release:major": "npm run release -- major"
1821
},
19-
"keywords": [],
20-
"author": "",
22+
"keywords": [
23+
"testingbot",
24+
"mobile-testing",
25+
"espresso",
26+
"xcuitest",
27+
"maestro",
28+
"android",
29+
"ios",
30+
"test-automation",
31+
"cloud-testing",
32+
"cli",
33+
"real-devices",
34+
"emulators",
35+
"simulators"
36+
],
37+
"author": "TestingBot",
2138
"license": "MIT",
39+
"repository": {
40+
"type": "git",
41+
"url": "https://github.com/testingbot/testingbotctl.git"
42+
},
43+
"homepage": "https://testingbot.com",
44+
"bugs": {
45+
"url": "https://github.com/testingbot/testingbotctl/issues"
46+
},
2247
"dependencies": {
2348
"@types/archiver": "^7.0.0",
2449
"@types/js-yaml": "^4.0.9",

src/cli.ts

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ import packageJson from '../package.json';
99
import MaestroOptions, {
1010
Orientation,
1111
ThrottleNetwork,
12+
ReportFormat,
1213
} from './models/maestro_options';
1314
import Maestro from './providers/maestro';
1415

1516
const program = new Command();
1617

1718
program
18-
.version(packageJson.version)
19+
.name('testingbot')
20+
.version(packageJson.version, '-v, --version', 'Show version number')
1921
.description(
20-
'TestingBotCTL is a CLI-tool to run Espresso, XCUITest and Maestro tests in the TestingBot cloud',
22+
'CLI tool to run Espresso, XCUITest and Maestro tests on TestingBot cloud',
2123
);
2224

2325
program
@@ -59,7 +61,7 @@ program
5961
})
6062
.showHelpAfterError(true);
6163

62-
program
64+
const maestroCommand = program
6365
.command('maestro')
6466
.description('Run Maestro flows on TestingBot.')
6567
.argument(
@@ -72,30 +74,30 @@ program
7274
)
7375
// App and flows options
7476
.option(
75-
'--app <string>',
77+
'--app <path>',
7678
'Path to application under test (.apk, .ipa, .app, or .zip).',
7779
)
7880
.option(
79-
'--flows <string>',
81+
'--flows <path>',
8082
'Path to flow file (.yaml/.yml), directory of flows, .zip file or glob pattern.',
8183
)
8284
// Device configuration
8385
.option(
8486
'--device <device>',
85-
'Device name to use for testing (e.g., "Pixel 8", "iPhone 15"). If not specified, uses "*" for any available device.',
87+
'Device name to use for testing (e.g., "Pixel 9", "iPhone 17").',
8688
)
8789
.option(
8890
'--platform <platform>',
8991
'Platform name: Android or iOS.',
9092
(val) => val as 'Android' | 'iOS',
9193
)
92-
.option('--version <version>', 'OS version (e.g., "14", "17.2").')
94+
.option('--deviceVersion <version>', 'OS version (e.g., "14", "17.2").')
9395
.option(
9496
'--orientation <orientation>',
9597
'Screen orientation: PORTRAIT or LANDSCAPE.',
9698
(val) => val.toUpperCase() as Orientation,
9799
)
98-
.option('--locale <locale>', 'Device locale (e.g., "en_US", "de_DE").')
100+
.option('--device-locale <locale>', 'Device locale (e.g., "en_US", "de_DE").')
99101
.option(
100102
'--timezone <timezone>',
101103
'Device timezone (e.g., "America/New_York", "Europe/London").',
@@ -134,6 +136,30 @@ program
134136
},
135137
[] as string[],
136138
)
139+
// Maestro configuration
140+
.option(
141+
'--maestro-version <version>',
142+
'Maestro version to use (e.g., "2.0.10").',
143+
)
144+
// Execution mode
145+
.option(
146+
'-q, --quiet',
147+
'Quieter console output without progress updates.',
148+
)
149+
.option(
150+
'--async',
151+
'Start tests and exit immediately without waiting for results.',
152+
)
153+
// Report options
154+
.option(
155+
'--report <format>',
156+
'Download test report after completion: html or junit.',
157+
(val) => val.toLowerCase() as ReportFormat,
158+
)
159+
.option(
160+
'--report-output-dir <path>',
161+
'Directory to save test reports (required when --report is used).',
162+
)
137163
// Authentication
138164
.option('--api-key <key>', 'TestingBot API key.')
139165
.option('--api-secret <secret>', 'TestingBot API secret.')
@@ -143,15 +169,9 @@ program
143169
const app = appFileArg || args.app;
144170
const flows = flowsArg || args.flows;
145171

146-
if (!app) {
147-
throw new Error(
148-
'App file is required. Provide it as first argument or use --app option.',
149-
);
150-
}
151-
if (!flows) {
152-
throw new Error(
153-
'Flows path is required. Provide it as second argument or use --flows option.',
154-
);
172+
if (!app || !flows) {
173+
maestroCommand.help();
174+
return;
155175
}
156176

157177
// Parse environment variables from -e KEY=VALUE format
@@ -169,15 +189,20 @@ program
169189
includeTags: args.includeTags,
170190
excludeTags: args.excludeTags,
171191
platformName: args.platform,
172-
version: args.version,
192+
version: args.deviceVersion,
173193
name: args.name,
174194
build: args.build,
175195
orientation: args.orientation,
176-
locale: args.locale,
196+
locale: args.deviceLocale,
177197
timeZone: args.timezone,
178198
throttleNetwork: args.throttleNetwork,
179199
geoCountryCode: args.geoCountryCode,
180200
env: Object.keys(env).length > 0 ? env : undefined,
201+
maestroVersion: args.maestroVersion,
202+
quiet: args.quiet,
203+
async: args.async,
204+
report: args.report,
205+
reportOutputDir: args.reportOutputDir,
181206
});
182207
const credentials = await Auth.getCredentials({
183208
apiKey: args.apiKey,
@@ -189,11 +214,15 @@ program
189214
);
190215
}
191216
const maestro = new Maestro(credentials, options);
192-
await maestro.run();
217+
const result = await maestro.run();
218+
if (!result.success) {
219+
process.exitCode = 1;
220+
}
193221
} catch (err) {
194222
logger.error(
195223
`Maestro error: ${err instanceof Error ? err.message : err}`,
196224
);
225+
process.exitCode = 1;
197226
}
198227
})
199228
.showHelpAfterError(true);

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
#!/usr/bin/env node
12
import program from './cli';
23
program.parse(process.argv);

src/models/maestro_options.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface MaestroConfig {
77

88
export type Orientation = 'PORTRAIT' | 'LANDSCAPE';
99
export type ThrottleNetwork = '4G' | '3G' | 'Edge' | 'airplane' | 'disable';
10+
export type ReportFormat = 'html' | 'junit';
1011

1112
export interface MaestroCapabilities {
1213
platformName?: 'Android' | 'iOS';
@@ -25,6 +26,7 @@ export interface MaestroRunOptions {
2526
includeTags?: string[];
2627
excludeTags?: string[];
2728
env?: Record<string, string>;
29+
version?: string;
2830
}
2931

3032
export default class MaestroOptions {
@@ -43,6 +45,11 @@ export default class MaestroOptions {
4345
private _throttleNetwork?: ThrottleNetwork;
4446
private _geoCountryCode?: string;
4547
private _env?: Record<string, string>;
48+
private _maestroVersion?: string;
49+
private _quiet: boolean;
50+
private _async: boolean;
51+
private _report?: ReportFormat;
52+
private _reportOutputDir?: string;
4653

4754
public constructor(
4855
app: string,
@@ -61,6 +68,11 @@ export default class MaestroOptions {
6168
throttleNetwork?: ThrottleNetwork;
6269
geoCountryCode?: string;
6370
env?: Record<string, string>;
71+
maestroVersion?: string;
72+
quiet?: boolean;
73+
async?: boolean;
74+
report?: ReportFormat;
75+
reportOutputDir?: string;
6476
},
6577
) {
6678
this._app = app;
@@ -78,6 +90,11 @@ export default class MaestroOptions {
7890
this._throttleNetwork = options?.throttleNetwork;
7991
this._geoCountryCode = options?.geoCountryCode;
8092
this._env = options?.env;
93+
this._maestroVersion = options?.maestroVersion;
94+
this._quiet = options?.quiet ?? false;
95+
this._async = options?.async ?? false;
96+
this._report = options?.report;
97+
this._reportOutputDir = options?.reportOutputDir;
8198
}
8299

83100
public get app(): string {
@@ -140,6 +157,26 @@ export default class MaestroOptions {
140157
return this._env;
141158
}
142159

160+
public get maestroVersion(): string | undefined {
161+
return this._maestroVersion;
162+
}
163+
164+
public get quiet(): boolean {
165+
return this._quiet;
166+
}
167+
168+
public get async(): boolean {
169+
return this._async;
170+
}
171+
172+
public get report(): ReportFormat | undefined {
173+
return this._report;
174+
}
175+
176+
public get reportOutputDir(): string | undefined {
177+
return this._reportOutputDir;
178+
}
179+
143180
public getMaestroOptions(): MaestroRunOptions | undefined {
144181
const opts: MaestroRunOptions = {};
145182

@@ -152,6 +189,9 @@ export default class MaestroOptions {
152189
if (this._env && Object.keys(this._env).length > 0) {
153190
opts.env = this._env;
154191
}
192+
if (this._maestroVersion) {
193+
opts.version = this._maestroVersion;
194+
}
155195

156196
return Object.keys(opts).length > 0 ? opts : undefined;
157197
}

0 commit comments

Comments
 (0)