Skip to content

Commit 2a3338d

Browse files
send git metadata
1 parent 8491f3c commit 2a3338d

12 files changed

Lines changed: 384 additions & 0 deletions

File tree

src/cli.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ const espressoCommand = program
135135
'--report-output-dir <path>',
136136
'Directory to save test reports (required when --report is used).',
137137
)
138+
// CI/CD metadata
139+
.option('--commit-sha <sha>', 'The commit SHA of this upload.')
140+
.option('--pull-request-id <id>', 'The ID of the pull request this upload originated from.')
141+
.option('--repo-name <name>', 'Repository name (e.g., GitHub repo slug).')
142+
.option('--repo-owner <owner>', 'Repository owner (e.g., GitHub organization or user slug).')
138143
// Authentication
139144
.option('--api-key <key>', 'TestingBot API key.')
140145
.option('--api-secret <secret>', 'TestingBot API secret.')
@@ -149,6 +154,16 @@ const espressoCommand = program
149154
return;
150155
}
151156

157+
const metadata =
158+
args.commitSha || args.pullRequestId || args.repoName || args.repoOwner
159+
? {
160+
commitSha: args.commitSha,
161+
pullRequestId: args.pullRequestId,
162+
repoName: args.repoName,
163+
repoOwner: args.repoOwner,
164+
}
165+
: undefined;
166+
152167
const options = new EspressoOptions(app, testApp, args.device, {
153168
version: args.platformVersion,
154169
realDevice: args.realDevice,
@@ -173,6 +188,7 @@ const espressoCommand = program
173188
async: args.async,
174189
report: args.report,
175190
reportOutputDir: args.reportOutputDir,
191+
metadata,
176192
});
177193
const credentials = await Auth.getCredentials({
178194
apiKey: args.apiKey,
@@ -315,6 +331,11 @@ const maestroCommand = program
315331
'Number of chunks to split flows into (by default each flow runs on its own session).',
316332
(val) => parseInt(val, 10),
317333
)
334+
// CI/CD metadata
335+
.option('--commit-sha <sha>', 'The commit SHA of this upload.')
336+
.option('--pull-request-id <id>', 'The ID of the pull request this upload originated from.')
337+
.option('--repo-name <name>', 'Repository name (e.g., GitHub repo slug).')
338+
.option('--repo-owner <owner>', 'Repository owner (e.g., GitHub organization or user slug).')
318339
// Authentication
319340
.option('--api-key <key>', 'TestingBot API key.')
320341
.option('--api-secret <secret>', 'TestingBot API secret.')
@@ -351,6 +372,16 @@ const maestroCommand = program
351372
}
352373
}
353374

375+
const metadata =
376+
args.commitSha || args.pullRequestId || args.repoName || args.repoOwner
377+
? {
378+
commitSha: args.commitSha,
379+
pullRequestId: args.pullRequestId,
380+
repoName: args.repoName,
381+
repoOwner: args.repoOwner,
382+
}
383+
: undefined;
384+
354385
const options = new MaestroOptions(app, flows, args.device, {
355386
includeTags: args.includeTags,
356387
excludeTags: args.excludeTags,
@@ -376,6 +407,7 @@ const maestroCommand = program
376407
artifactsOutputDir: args.artifactsOutputDir,
377408
ignoreChecksumCheck: args.ignoreChecksumCheck,
378409
shardSplit: args.shardSplit,
410+
metadata,
379411
});
380412
const credentials = await Auth.getCredentials({
381413
apiKey: args.apiKey,
@@ -466,6 +498,11 @@ const xcuitestCommand = program
466498
'--report-output-dir <path>',
467499
'Directory to save test reports (required when --report is used).',
468500
)
501+
// CI/CD metadata
502+
.option('--commit-sha <sha>', 'The commit SHA of this upload.')
503+
.option('--pull-request-id <id>', 'The ID of the pull request this upload originated from.')
504+
.option('--repo-name <name>', 'Repository name (e.g., GitHub repo slug).')
505+
.option('--repo-owner <owner>', 'Repository owner (e.g., GitHub organization or user slug).')
469506
// Authentication
470507
.option('--api-key <key>', 'TestingBot API key.')
471508
.option('--api-secret <secret>', 'TestingBot API secret.')
@@ -480,6 +517,16 @@ const xcuitestCommand = program
480517
return;
481518
}
482519

520+
const metadata =
521+
args.commitSha || args.pullRequestId || args.repoName || args.repoOwner
522+
? {
523+
commitSha: args.commitSha,
524+
pullRequestId: args.pullRequestId,
525+
repoName: args.repoName,
526+
repoOwner: args.repoOwner,
527+
}
528+
: undefined;
529+
483530
const options = new XCUITestOptions(app, testApp, args.device, {
484531
version: args.platformVersion,
485532
realDevice: args.realDevice,
@@ -497,6 +544,7 @@ const xcuitestCommand = program
497544
async: args.async,
498545
report: args.report,
499546
reportOutputDir: args.reportOutputDir,
547+
metadata,
500548
});
501549
const credentials = await Auth.getCredentials({
502550
apiKey: args.apiKey,

src/models/espresso_options.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ export type TestSize = 'small' | 'medium' | 'large';
22
export type ReportFormat = 'html' | 'junit';
33
export type ThrottleNetwork = '4G' | '3G' | 'Edge' | 'airplane';
44

5+
export interface RunMetadata {
6+
commitSha?: string;
7+
pullRequestId?: string;
8+
repoName?: string;
9+
repoOwner?: string;
10+
}
11+
512
export interface CustomNetworkProfile {
613
uploadSpeed: number; // kbps
714
downloadSpeed: number; // kbps
@@ -71,6 +78,8 @@ export default class EspressoOptions {
7178
private _async: boolean;
7279
private _report?: ReportFormat;
7380
private _reportOutputDir?: string;
81+
// Metadata
82+
private _metadata?: RunMetadata;
7483

7584
public constructor(
7685
app: string,
@@ -100,6 +109,7 @@ export default class EspressoOptions {
100109
async?: boolean;
101110
report?: ReportFormat;
102111
reportOutputDir?: string;
112+
metadata?: RunMetadata;
103113
},
104114
) {
105115
this._app = app;
@@ -128,6 +138,7 @@ export default class EspressoOptions {
128138
this._async = options?.async ?? false;
129139
this._report = options?.report;
130140
this._reportOutputDir = options?.reportOutputDir;
141+
this._metadata = options?.metadata;
131142
}
132143

133144
public get app(): string {
@@ -237,6 +248,10 @@ export default class EspressoOptions {
237248
return this._reportOutputDir;
238249
}
239250

251+
public get metadata(): RunMetadata | undefined {
252+
return this._metadata;
253+
}
254+
240255
public getCapabilities(): EspressoCapabilities {
241256
const caps: EspressoCapabilities = {
242257
platformName: 'Android',

src/models/maestro_options.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ export type ThrottleNetwork = '4G' | '3G' | 'Edge' | 'airplane' | 'disable';
1010
export type ReportFormat = 'html' | 'junit';
1111
export type ArtifactDownloadMode = 'all' | 'failed';
1212

13+
export interface RunMetadata {
14+
commitSha?: string;
15+
pullRequestId?: string;
16+
repoName?: string;
17+
repoOwner?: string;
18+
}
19+
1320
export interface MaestroCapabilities {
1421
platformName?: 'Android' | 'iOS';
1522
version?: string;
@@ -55,6 +62,8 @@ export default class MaestroOptions {
5562
private _artifactsOutputDir?: string;
5663
private _ignoreChecksumCheck: boolean;
5764
private _shardSplit?: number;
65+
// Metadata
66+
private _metadata?: RunMetadata;
5867

5968
public constructor(
6069
app: string,
@@ -82,6 +91,7 @@ export default class MaestroOptions {
8291
artifactsOutputDir?: string;
8392
ignoreChecksumCheck?: boolean;
8493
shardSplit?: number;
94+
metadata?: RunMetadata;
8595
},
8696
) {
8797
this._app = app;
@@ -108,6 +118,7 @@ export default class MaestroOptions {
108118
this._artifactsOutputDir = options?.artifactsOutputDir;
109119
this._ignoreChecksumCheck = options?.ignoreChecksumCheck ?? false;
110120
this._shardSplit = options?.shardSplit;
121+
this._metadata = options?.metadata;
111122
}
112123

113124
public get app(): string {
@@ -206,6 +217,10 @@ export default class MaestroOptions {
206217
return this._shardSplit;
207218
}
208219

220+
public get metadata(): RunMetadata | undefined {
221+
return this._metadata;
222+
}
223+
209224
public getMaestroOptions(): MaestroRunOptions | undefined {
210225
const opts: MaestroRunOptions = {};
211226

src/models/xcuitest_options.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ export type Orientation = 'PORTRAIT' | 'LANDSCAPE';
22
export type ReportFormat = 'html' | 'junit';
33
export type ThrottleNetwork = '4G' | '3G' | 'Edge' | 'airplane';
44

5+
export interface RunMetadata {
6+
commitSha?: string;
7+
pullRequestId?: string;
8+
repoName?: string;
9+
repoOwner?: string;
10+
}
11+
512
export interface CustomNetworkProfile {
613
uploadSpeed: number; // kbps
714
downloadSpeed: number; // kbps
@@ -58,6 +65,8 @@ export default class XCUITestOptions {
5865
private _async: boolean;
5966
private _report?: ReportFormat;
6067
private _reportOutputDir?: string;
68+
// Metadata
69+
private _metadata?: RunMetadata;
6170

6271
public constructor(
6372
app: string,
@@ -80,6 +89,7 @@ export default class XCUITestOptions {
8089
async?: boolean;
8190
report?: ReportFormat;
8291
reportOutputDir?: string;
92+
metadata?: RunMetadata;
8393
},
8494
) {
8595
this._app = app;
@@ -101,6 +111,7 @@ export default class XCUITestOptions {
101111
this._async = options?.async ?? false;
102112
this._report = options?.report;
103113
this._reportOutputDir = options?.reportOutputDir;
114+
this._metadata = options?.metadata;
104115
}
105116

106117
public get app(): string {
@@ -182,6 +193,10 @@ export default class XCUITestOptions {
182193
return this._reportOutputDir;
183194
}
184195

196+
public get metadata(): RunMetadata | undefined {
197+
return this._metadata;
198+
}
199+
185200
public getCapabilities(): XCUITestCapabilities {
186201
const caps: XCUITestCapabilities = {
187202
platformName: 'iOS',

src/providers/espresso.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,14 @@ export default class Espresso {
218218
try {
219219
const capabilities = this.options.getCapabilities();
220220
const espressoOptions = this.options.getEspressoOptions();
221+
const metadata = this.options.metadata;
221222

222223
const response = await axios.post(
223224
`${this.URL}/${this.appId}/run`,
224225
{
225226
capabilities: [capabilities],
226227
...(espressoOptions && { espressoOptions }),
228+
...(metadata && { metadata }),
227229
},
228230
{
229231
headers: {

src/providers/maestro.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,12 +812,14 @@ export default class Maestro {
812812
try {
813813
const capabilities = this.options.getCapabilities(this.detectedPlatform);
814814
const maestroOptions = this.options.getMaestroOptions();
815+
const metadata = this.options.metadata;
815816
const response = await axios.post(
816817
`${this.URL}/${this.appId}/run`,
817818
{
818819
capabilities: [capabilities],
819820
...(maestroOptions && { maestroOptions }),
820821
...(this.options.shardSplit && { shardSplit: this.options.shardSplit }),
822+
...(metadata && { metadata }),
821823
},
822824
{
823825
headers: {

src/providers/xcuitest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,14 @@ export default class XCUITest {
218218
try {
219219
const capabilities = this.options.getCapabilities();
220220
const xcuitestOptions = this.options.getXCUITestOptions();
221+
const metadata = this.options.metadata;
221222

222223
const response = await axios.post(
223224
`${this.URL}/${this.appId}/run`,
224225
{
225226
capabilities: [capabilities],
226227
...(xcuitestOptions && { options: xcuitestOptions }),
228+
...(metadata && { metadata }),
227229
},
228230
{
229231
headers: {

tests/cli.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,83 @@ describe('TestingBotCTL CLI', () => {
269269
expect(mockMaestroRun).toHaveBeenCalledTimes(1);
270270
});
271271

272+
test('espresso command should accept metadata options', async () => {
273+
mockGetCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
274+
mockEspressoRun.mockResolvedValue({ success: true, runs: [] });
275+
276+
await program.parseAsync([
277+
'node',
278+
'cli',
279+
'espresso',
280+
'--app',
281+
'app.apk',
282+
'--test-app',
283+
'test-app.apk',
284+
'--device',
285+
'Pixel 6',
286+
'--commit-sha',
287+
'abc123def456',
288+
'--pull-request-id',
289+
'42',
290+
'--repo-name',
291+
'my-app',
292+
'--repo-owner',
293+
'my-org',
294+
]);
295+
296+
expect(mockEspressoRun).toHaveBeenCalledTimes(1);
297+
});
298+
299+
test('maestro command should accept metadata options', async () => {
300+
mockGetCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
301+
302+
await program.parseAsync([
303+
'node',
304+
'cli',
305+
'maestro',
306+
'app.apk',
307+
'./flows',
308+
'--device',
309+
'Pixel 6',
310+
'--commit-sha',
311+
'abc123def456',
312+
'--pull-request-id',
313+
'42',
314+
'--repo-name',
315+
'my-app',
316+
'--repo-owner',
317+
'my-org',
318+
]);
319+
320+
expect(mockMaestroRun).toHaveBeenCalledTimes(1);
321+
});
322+
323+
test('xcuitest command should accept metadata options', async () => {
324+
mockGetCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
325+
326+
await program.parseAsync([
327+
'node',
328+
'cli',
329+
'xcuitest',
330+
'--app',
331+
'app.ipa',
332+
'--test-app',
333+
'test-app.zip',
334+
'--device',
335+
'iPhone 15',
336+
'--commit-sha',
337+
'abc123def456',
338+
'--pull-request-id',
339+
'42',
340+
'--repo-name',
341+
'my-ios-app',
342+
'--repo-owner',
343+
'my-org',
344+
]);
345+
346+
expect(mockXCUITestRun).toHaveBeenCalledTimes(1);
347+
});
348+
272349
test('xcuitest command should call xcuitest.run() with valid options', async () => {
273350
mockGetCredentials.mockResolvedValue({ apiKey: 'test-api-key' });
274351

0 commit comments

Comments
 (0)