@devicecloud.dev/dcd 4.0.1 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,7 @@ export default class Cloud extends Command {
18
18
  'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
19
19
  'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
20
20
  'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ 'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
21
22
  async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
23
  config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
23
24
  debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -53,11 +54,4 @@ export default class Cloud extends Command {
53
54
  };
54
55
  private versionCheck;
55
56
  run(): Promise<any>;
56
- /**
57
- * Generate the JSON output file path based on upload ID or custom filename
58
- * @param uploadId - Upload ID to use if custom filename is not provided
59
- * @param jsonFileName - Optional custom filename (can include relative path)
60
- * @returns Path to the JSON output file
61
- */
62
- private getJsonOutputPath;
63
57
  }
@@ -64,7 +64,7 @@ class Cloud extends core_1.Command {
64
64
  let jsonFile = false;
65
65
  try {
66
66
  const { args, flags, raw } = await this.parse(Cloud);
67
- let { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, 'dry-run': dryRun, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'ignore-sha-check': ignoreShaCheck, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, json, 'json-file-name': jsonFileName, 'maestro-version': maestroVersion, metadata, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, name, orientation, quiet, report, retry, 'runner-type': runnerType, 'x86-arch': x86Arch, ...rest } = flags;
67
+ let { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, 'junit-path': junitPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, 'dry-run': dryRun, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'ignore-sha-check': ignoreShaCheck, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, json, 'json-file-name': jsonFileName, 'maestro-version': maestroVersion, metadata, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, name, orientation, quiet, report, retry, 'runner-type': runnerType, 'x86-arch': x86Arch, ...rest } = flags;
68
68
  // Resolve "latest" maestro version to actual version
69
69
  const resolvedMaestroVersion = (0, constants_1.resolveMaestroVersion)(maestroVersion);
70
70
  // Store debug flag for use in catch block
@@ -228,6 +228,9 @@ class Cloud extends core_1.Command {
228
228
  if (runnerType === 'm1') {
229
229
  this.log('Note: runnerType m1 is experimental and currently supports Android (Pixel 7, API Level 34) only.');
230
230
  }
231
+ if (runnerType === 'gpu1') {
232
+ this.log('Note: runnerType gpu1 is Android-only and requires contacting support to enable. Without support enablement, your runner type will revert to default.');
233
+ }
231
234
  const additionalAppBinaryIds = nonFlatAdditionalAppBinaryIds?.flat();
232
235
  const additionalAppFiles = nonFlatAdditionalAppFiles?.flat();
233
236
  const { firstFile, secondFile } = args;
@@ -589,7 +592,7 @@ class Cloud extends core_1.Command {
589
592
  uploadId: results[0].test_upload_id,
590
593
  };
591
594
  if (flags['json-file']) {
592
- const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
595
+ const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`;
593
596
  (0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
594
597
  }
595
598
  if (json) {
@@ -681,6 +684,34 @@ class Cloud extends core_1.Command {
681
684
  this.warn('Failed to download artifacts');
682
685
  }
683
686
  }
687
+ // Handle report download separately if --report junit is specified
688
+ if (report === 'junit') {
689
+ try {
690
+ if (debug) {
691
+ this.log('DEBUG: Downloading JUNIT report');
692
+ }
693
+ const reportPath = path.resolve(process.cwd(), junitPath || 'report.xml');
694
+ await api_gateway_1.ApiGateway.downloadReport(apiUrl, apiKey, results[0].test_upload_id, reportPath);
695
+ this.log('\n');
696
+ this.log(`JUNIT test report has been downloaded to ${reportPath}`);
697
+ }
698
+ catch (error) {
699
+ if (debug) {
700
+ this.log(`DEBUG: Error downloading JUNIT report: ${error}`);
701
+ }
702
+ const errorMessage = error instanceof Error ? error.message : String(error);
703
+ this.warn(`Failed to download JUNIT report: ${errorMessage}`);
704
+ if (errorMessage.includes('404')) {
705
+ this.warn('No JUNIT reports found for this upload. Make sure your tests were run with --report junit flag.');
706
+ }
707
+ else if (errorMessage.includes('EACCES') || errorMessage.includes('EPERM')) {
708
+ this.warn('Permission denied. Check write permissions for the current directory.');
709
+ }
710
+ else if (errorMessage.includes('ENOENT')) {
711
+ this.warn('Directory does not exist. Make sure you have write access to the current directory.');
712
+ }
713
+ }
714
+ }
684
715
  const resultsWithoutEarlierTries = updatedResults.filter((result) => {
685
716
  const originalTryId = result.retry_of || result.id;
686
717
  const tries = updatedResults.filter((r) => r.retry_of === originalTryId || r.id === originalTryId);
@@ -704,7 +735,7 @@ class Cloud extends core_1.Command {
704
735
  uploadId: results[0].test_upload_id,
705
736
  };
706
737
  if (flags['json-file']) {
707
- const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
738
+ const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`;
708
739
  (0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
709
740
  }
710
741
  if (json) {
@@ -730,7 +761,7 @@ class Cloud extends core_1.Command {
730
761
  uploadId: results[0].test_upload_id,
731
762
  };
732
763
  if (flags['json-file']) {
733
- const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
764
+ const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`;
734
765
  (0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
735
766
  }
736
767
  if (json) {
@@ -780,17 +811,5 @@ class Cloud extends core_1.Command {
780
811
  }
781
812
  }
782
813
  }
783
- /**
784
- * Generate the JSON output file path based on upload ID or custom filename
785
- * @param uploadId - Upload ID to use if custom filename is not provided
786
- * @param jsonFileName - Optional custom filename (can include relative path)
787
- * @returns Path to the JSON output file
788
- */
789
- getJsonOutputPath(uploadId, jsonFileName) {
790
- if (jsonFileName) {
791
- return jsonFileName;
792
- }
793
- return `${uploadId}_dcd.json`;
794
- }
795
814
  }
796
815
  exports.default = Cloud;
@@ -12,6 +12,7 @@ export declare const flags: {
12
12
  'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
13
13
  'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
14
14
  'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ 'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
15
16
  async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
17
  config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
17
18
  debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
package/dist/constants.js CHANGED
@@ -73,6 +73,10 @@ exports.flags = {
73
73
  dependsOn: ['download-artifacts'],
74
74
  description: 'Custom file path for downloaded artifacts (default: ./artifacts.zip)',
75
75
  }),
76
+ 'junit-path': core_1.Flags.string({
77
+ dependsOn: ['report'],
78
+ description: 'Custom file path for downloaded JUnit report (default: ./report.xml)',
79
+ }),
76
80
  async: core_1.Flags.boolean({
77
81
  description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)',
78
82
  }),
@@ -87,7 +91,7 @@ exports.flags = {
87
91
  description: 'Locale that will be set to a device, ISO-639-1 code and uppercase ISO-3166-1 code e.g. "de_DE" for Germany',
88
92
  }),
89
93
  'download-artifacts': core_1.Flags.string({
90
- description: 'Download a zip containing the logs, screenshots and videos for each result in this run. You will debited a $0.01 egress fee for each result. Use --download-artifacts=FAILED for failures only or --download-artifacts=ALL for every result.',
94
+ description: 'Download a zip containing the logs, screenshots and videos for each result in this run. You will be debited a $0.01 egress fee for each result. Options: ALL (everything), FAILED (failures only).',
91
95
  options: ['ALL', 'FAILED'],
92
96
  }),
93
97
  'dry-run': core_1.Flags.boolean({
@@ -198,8 +202,8 @@ exports.flags = {
198
202
  }),
199
203
  'runner-type': core_1.Flags.string({
200
204
  default: 'default',
201
- description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information',
202
- options: ['default', 'm4', 'm1'],
205
+ description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information. gpu1 is Android-only and requires contacting support to enable, otherwise reverts to default.',
206
+ options: ['default', 'm4', 'm1', 'gpu1'],
203
207
  }),
204
208
  'show-crosshairs': core_1.Flags.boolean({
205
209
  default: false,
@@ -4,17 +4,18 @@ export declare const ApiGateway: {
4
4
  appBinaryId: string;
5
5
  exists: boolean;
6
6
  }>;
7
+ downloadReport(baseUrl: string, apiKey: string, uploadId: string, reportPath?: string): Promise<void>;
7
8
  downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
8
9
  finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
9
10
  getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
10
- id: string;
11
11
  message: string;
12
12
  path: string;
13
13
  token: string;
14
+ id: string;
14
15
  }>;
15
16
  getResultsForUpload(baseUrl: string, apiKey: string, uploadId: string): Promise<{
16
- results?: import("../types/schema.types").components["schemas"]["TResultResponse"][];
17
17
  statusCode?: number;
18
+ results?: import("../types/schema.types").components["schemas"]["TResultResponse"][];
18
19
  }>;
19
20
  getUploadStatus(baseUrl: string, apiKey: string, options: {
20
21
  name?: string;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ApiGateway = void 0;
4
+ const fs = require("node:fs/promises");
5
+ const path = require("node:path");
4
6
  exports.ApiGateway = {
5
7
  async checkForExistingUpload(baseUrl, apiKey, sha) {
6
8
  const res = await fetch(`${baseUrl}/uploads/checkForExistingUpload`, {
@@ -13,6 +15,25 @@ exports.ApiGateway = {
13
15
  });
14
16
  return res.json();
15
17
  },
18
+ async downloadReport(baseUrl, apiKey, uploadId, reportPath) {
19
+ const finalReportPath = reportPath || path.resolve(process.cwd(), `report-${uploadId}.xml`);
20
+ const url = `${baseUrl}/results/${uploadId}/report`;
21
+ const res = await fetch(url, {
22
+ headers: {
23
+ 'x-app-api-key': apiKey,
24
+ },
25
+ method: 'GET',
26
+ });
27
+ if (!res.ok) {
28
+ const errorText = await res.text();
29
+ if (res.status === 404) {
30
+ throw new Error(`Upload ID '${uploadId}' not found or no results available for this upload`);
31
+ }
32
+ throw new Error(`Failed to download report: ${res.status} ${errorText}`);
33
+ }
34
+ const buffer = await res.arrayBuffer();
35
+ await fs.writeFile(finalReportPath, Buffer.from(buffer));
36
+ },
16
37
  async downloadArtifactsZip(baseUrl, apiKey, uploadId, results, artifactsPath = './artifacts.zip') {
17
38
  const res = await fetch(`${baseUrl}/results/${uploadId}/download`, {
18
39
  body: JSON.stringify({ results }),
@@ -6,41 +6,32 @@ export interface paths {
6
6
  "/admin/cleanupOldUploads": {
7
7
  post: operations["AdminController_cleanupOldUploads"];
8
8
  };
9
- "/frontend/check-domain-saml": {
10
- post: operations["FrontendController_checkDomainSaml"];
11
- };
12
- "/health": {
13
- get: operations["HealthController_health"];
14
- };
15
- "/moropo/run-scheduled-jobs": {
16
- get: operations["MoropoController_runScheduledJobs"];
17
- };
18
- "/org/accept-invite": {
19
- post: operations["OrgController_acceptInvite"];
9
+ "/uploads/binary": {
10
+ post: operations["UploadsController_createBinary"];
20
11
  };
21
- "/org/increase_credits": {
22
- post: operations["OrgController_paddleTransactionCompleted"];
12
+ "/uploads/getBinaryUploadUrl": {
13
+ post: operations["UploadsController_getBinaryUploadUrl"];
23
14
  };
24
- "/org/invite-team-member": {
25
- post: operations["OrgController_inviteTeamMember"];
15
+ "/uploads/checkForExistingUpload": {
16
+ post: operations["UploadsController_checkForExistingUpload"];
26
17
  };
27
- "/org/update-name": {
28
- post: operations["OrgController_updateOrgName"];
18
+ "/uploads/finaliseUpload": {
19
+ post: operations["UploadsController_finaliseUpload"];
29
20
  };
30
- "/public/stats": {
31
- get: operations["StatsController_getPublicStats"];
21
+ "/uploads/flow": {
22
+ post: operations["UploadsController_createTest"];
32
23
  };
33
- "/public/stats/report-slow-tests-dev": {
34
- post: operations["StatsController_reportSlowTestsDev"];
24
+ "/uploads/retryTest": {
25
+ post: operations["UploadsController_retryTest"];
35
26
  };
36
- "/public/stats/report-slow-tests-prod": {
37
- post: operations["StatsController_reportSlowTestsProd"];
27
+ "/uploads/cancelTest": {
28
+ post: operations["UploadsController_cancelTest"];
38
29
  };
39
- "/public/stats/send-daily-report": {
40
- post: operations["StatsController_sendDailyReport"];
30
+ "/uploads/status": {
31
+ get: operations["UploadsController_getUploadStatus"];
41
32
  };
42
- "/results": {
43
- post: operations["ResultsController_createResult"];
33
+ "/uploads/{uploadId}": {
34
+ delete: operations["UploadsController_deleteUpload"];
44
35
  };
45
36
  "/results/{uploadId}": {
46
37
  get: operations["ResultsController_getResults"];
@@ -48,102 +39,46 @@ export interface paths {
48
39
  "/results/{uploadId}/download": {
49
40
  post: operations["ResultsController_getTestRunArtifacts"];
50
41
  };
51
- "/results/compatibility/data": {
52
- get: operations["ResultsController_getCompatibilityData"];
42
+ "/results": {
43
+ post: operations["ResultsController_createResult"];
53
44
  };
54
45
  "/results/notify/{uploadId}": {
55
46
  post: operations["ResultsController_notifyTestRunComplete"];
56
47
  };
57
- "/uploads/{uploadId}": {
58
- delete: operations["UploadsController_deleteUpload"];
48
+ "/results/compatibility/data": {
49
+ get: operations["ResultsController_getCompatibilityData"];
59
50
  };
60
- "/uploads/binary": {
61
- post: operations["UploadsController_createBinary"];
51
+ "/org/increase_credits": {
52
+ post: operations["OrgController_paddleTransactionCompleted"];
62
53
  };
63
- "/uploads/cancelTest": {
64
- post: operations["UploadsController_cancelTest"];
54
+ "/org/update-name": {
55
+ post: operations["OrgController_updateOrgName"];
65
56
  };
66
- "/uploads/checkForExistingUpload": {
67
- post: operations["UploadsController_checkForExistingUpload"];
57
+ "/org/invite-team-member": {
58
+ post: operations["OrgController_inviteTeamMember"];
68
59
  };
69
- "/uploads/finaliseUpload": {
70
- post: operations["UploadsController_finaliseUpload"];
60
+ "/org/accept-invite": {
61
+ post: operations["OrgController_acceptInvite"];
71
62
  };
72
- "/uploads/flow": {
73
- post: operations["UploadsController_createTest"];
63
+ "/public/stats": {
64
+ get: operations["StatsController_getPublicStats"];
74
65
  };
75
- "/uploads/getBinaryUploadUrl": {
76
- post: operations["UploadsController_getBinaryUploadUrl"];
66
+ "/moropo/run-scheduled-jobs": {
67
+ get: operations["MoropoController_runScheduledJobs"];
77
68
  };
78
- "/uploads/retryTest": {
79
- post: operations["UploadsController_retryTest"];
69
+ "/frontend/check-domain-saml": {
70
+ post: operations["FrontendController_checkDomainSaml"];
80
71
  };
81
- "/uploads/status": {
82
- get: operations["UploadsController_getUploadStatus"];
72
+ "/health": {
73
+ get: operations["HealthController_health"];
83
74
  };
84
75
  }
85
76
  export type webhooks = Record<string, never>;
86
77
  export interface components {
87
- headers: never;
88
- parameters: never;
89
- pathItems: never;
90
- requestBodies: never;
91
- responses: never;
92
78
  schemas: {
93
- ICancelTestArgs: {
94
- resultId: number;
95
- };
96
- ICheckForExistingUploadArgs: {
97
- sha: string;
98
- };
99
- ICheckForExistingUploadResponse: {
100
- appBinaryId: string;
101
- exists: boolean;
102
- };
103
- ICreateBinaryResponse: {
104
- binaryId: string;
105
- message: string;
106
- };
107
- ICreateBinaryUploadArgs: {
108
- /**
109
- * Format: binary
110
- * @description This file must either be an apk or a zip file
111
- */
112
- file: string;
113
- };
114
- ICreateTestUploadArgs: {
115
- /** @enum {string} */
116
- androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35";
117
- /** @enum {string} */
118
- androidDevice?: "generic-tablet" | "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro";
119
- apiKey: string;
120
- apiUrl: string;
121
- appBinaryId: string;
122
- appFile: string;
123
- config: string;
124
- env: string;
125
- /**
126
- * Format: binary
127
- * @description This file must be a zip file
128
- */
129
- file: string;
130
- flowMetadata?: string;
131
- googlePlay: boolean;
132
- /** @enum {string} */
133
- iOSDevice?: "ipad-pro-6th-gen" | "iphone-14" | "iphone-14-pro" | "iphone-15" | "iphone-15-pro" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max";
134
- /** @enum {string} */
135
- iOSVersion?: "16" | "17" | "18";
136
- metadata?: string;
137
- name: string;
138
- platform?: string;
139
- runnerType?: Record<string, never>;
140
- sequentialFlows?: string;
141
- testFileNames?: string;
142
- workspaceConfig?: string;
143
- };
144
79
  IDBResult: {
145
80
  binary_upload_id: string;
146
- cost: null | number;
81
+ cost: number | null;
147
82
  created_at: string;
148
83
  env: Record<string, never>;
149
84
  id: number;
@@ -154,34 +89,95 @@ export interface components {
154
89
  test_file_name: string;
155
90
  test_upload_id: string;
156
91
  };
157
- IFinaliseUploadArgs: {
158
- id: string;
159
- metadata: Record<string, never>;
160
- path: string;
161
- sha: string;
92
+ ICreateBinaryUploadArgs: {
93
+ /**
94
+ * Format: binary
95
+ * @description This file must either be an apk or a zip file
96
+ */
97
+ file: string;
98
+ };
99
+ ICreateBinaryResponse: {
100
+ message: string;
101
+ binaryId: string;
162
102
  };
163
- IFinaliseUploadResponse: Record<string, never>;
164
103
  IGetBinaryUploadUrlArgs: {
165
104
  platform: Record<string, never>;
166
105
  };
167
106
  IGetBinaryUploadUrlResponse: {
168
- id: string;
169
107
  message: string;
170
108
  path: string;
171
109
  token: string;
110
+ id: string;
111
+ };
112
+ ICheckForExistingUploadArgs: {
113
+ sha: string;
114
+ };
115
+ ICheckForExistingUploadResponse: {
116
+ appBinaryId: string;
117
+ exists: boolean;
118
+ };
119
+ IFinaliseUploadArgs: {
120
+ id: string;
121
+ path: string;
122
+ metadata: Record<string, never>;
123
+ sha: string;
124
+ };
125
+ IFinaliseUploadResponse: Record<string, never>;
126
+ ICreateTestUploadArgs: {
127
+ /**
128
+ * Format: binary
129
+ * @description This file must be a zip file
130
+ */
131
+ file: string;
132
+ /** @enum {string} */
133
+ androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35";
134
+ /** @enum {string} */
135
+ androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet";
136
+ apiKey?: string;
137
+ apiUrl?: string;
138
+ appFile?: string;
139
+ /** @enum {string} */
140
+ iOSVersion?: "16" | "17" | "18";
141
+ /** @enum {string} */
142
+ iOSDevice?: "iphone-14" | "iphone-14-pro" | "iphone-15" | "iphone-15-pro" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen";
143
+ platform?: string;
144
+ /** @enum {string} */
145
+ runnerType?: "m4" | "m1" | "default" | "gpu1";
146
+ metadata?: string;
147
+ workspaceConfig?: string;
148
+ flowMetadata?: string;
149
+ testFileOverrides?: string;
150
+ testFileNames?: string;
151
+ sequentialFlows?: string;
152
+ appBinaryId: string;
153
+ env: string;
154
+ googlePlay: boolean;
155
+ config: string;
156
+ name: string;
172
157
  };
173
158
  IRetryTestArgs: {
174
159
  resultId: number;
175
160
  };
161
+ ICancelTestArgs: {
162
+ /** @description ID of a specific result to cancel. Either resultId or uploadId must be provided, but not both. */
163
+ resultId?: number;
164
+ /** @description ID of an upload to cancel all pending results for. Either resultId or uploadId must be provided, but not both. */
165
+ uploadId?: string;
166
+ };
176
167
  TResultResponse: {
177
- duration_seconds?: number;
178
- fail_reason?: string;
179
168
  id: number;
180
- retry_of?: number;
181
- status: string;
182
169
  test_file_name: string;
170
+ status: string;
171
+ retry_of?: number;
172
+ fail_reason?: string;
173
+ duration_seconds?: number;
183
174
  };
184
175
  };
176
+ responses: never;
177
+ parameters: never;
178
+ requestBodies: never;
179
+ headers: never;
180
+ pathItems: never;
185
181
  }
186
182
  export type $defs = Record<string, never>;
187
183
  export type external = Record<string, never>;
@@ -198,73 +194,87 @@ export interface operations {
198
194
  201: {
199
195
  content: {
200
196
  "application/json": {
201
- deletedCount?: number;
202
- dryRun?: boolean;
203
197
  message?: string;
198
+ deletedCount?: number;
204
199
  uploads?: ({
205
- createdAt?: string;
206
200
  id?: string;
207
- lastUsedDate?: null | string;
208
201
  uploadType?: string;
202
+ createdAt?: string;
203
+ lastUsedDate?: string | null;
209
204
  })[];
205
+ dryRun?: boolean;
210
206
  };
211
207
  };
212
208
  };
213
209
  };
214
210
  };
215
- FrontendController_checkDomainSaml: {
216
- /** @description Domain to check for SAML configuration */
211
+ UploadsController_createBinary: {
212
+ parameters: {
213
+ header: {
214
+ "x-app-api-key": string;
215
+ };
216
+ };
217
217
  requestBody: {
218
218
  content: {
219
- "application/json": {
220
- /** @example example.com */
221
- domain: string;
222
- };
219
+ "multipart/form-data": components["schemas"]["ICreateBinaryUploadArgs"];
223
220
  };
224
221
  };
225
222
  responses: {
226
- /** @description SAML status for the domain */
227
- 200: {
228
- content: {
229
- "application/json": {
230
- forceSaml?: boolean;
231
- };
232
- };
233
- };
223
+ /**
224
+ * @description The record has been successfully created.
225
+ * @example {
226
+ * "message": "Binary uploaded successfully",
227
+ * "binaryId": "binary-123-abc-def"
228
+ * }
229
+ */
234
230
  201: {
235
- content: never;
236
- };
237
- /** @description Bad request - invalid domain or API error */
238
- 400: {
239
231
  content: {
240
- "application/json": {
241
- error?: string;
242
- };
232
+ "application/json": components["schemas"]["ICreateBinaryResponse"];
243
233
  };
244
234
  };
245
235
  };
246
236
  };
247
- HealthController_health: {
237
+ UploadsController_getBinaryUploadUrl: {
238
+ parameters: {
239
+ header: {
240
+ "x-app-api-key": string;
241
+ };
242
+ };
243
+ requestBody: {
244
+ content: {
245
+ "application/json": components["schemas"]["IGetBinaryUploadUrlArgs"];
246
+ };
247
+ };
248
248
  responses: {
249
- /** @description Health check endpoint */
250
- 200: {
249
+ /** @description The url has been successfully created. */
250
+ 201: {
251
251
  content: {
252
- "application/json": {
253
- /** @example ok */
254
- status?: string;
255
- };
252
+ "application/json": components["schemas"]["IGetBinaryUploadUrlResponse"];
256
253
  };
257
254
  };
258
255
  };
259
256
  };
260
- MoropoController_runScheduledJobs: {
257
+ UploadsController_checkForExistingUpload: {
258
+ parameters: {
259
+ header: {
260
+ "x-app-api-key": string;
261
+ };
262
+ };
263
+ requestBody: {
264
+ content: {
265
+ "application/json": components["schemas"]["ICheckForExistingUploadArgs"];
266
+ };
267
+ };
261
268
  responses: {
262
- 200: {
263
- content: never;
269
+ /** @description The url has been successfully created. */
270
+ 201: {
271
+ content: {
272
+ "application/json": components["schemas"]["ICheckForExistingUploadResponse"];
273
+ };
264
274
  };
265
275
  };
266
276
  };
267
- OrgController_acceptInvite: {
277
+ UploadsController_finaliseUpload: {
268
278
  parameters: {
269
279
  header: {
270
280
  "x-app-api-key": string;
@@ -272,21 +282,19 @@ export interface operations {
272
282
  };
273
283
  requestBody: {
274
284
  content: {
275
- "application/json": {
276
- orgId: string;
277
- };
285
+ "application/json": components["schemas"]["IFinaliseUploadArgs"];
278
286
  };
279
287
  };
280
288
  responses: {
281
- /** @description Team invite accepted successfully. */
289
+ /** @description The upload has been completed. */
282
290
  201: {
283
291
  content: {
284
- "application/json": boolean;
292
+ "application/json": components["schemas"]["IFinaliseUploadResponse"];
285
293
  };
286
294
  };
287
295
  };
288
296
  };
289
- OrgController_inviteTeamMember: {
297
+ UploadsController_createTest: {
290
298
  parameters: {
291
299
  header: {
292
300
  "x-app-api-key": string;
@@ -294,40 +302,45 @@ export interface operations {
294
302
  };
295
303
  requestBody: {
296
304
  content: {
297
- "application/json": {
298
- inviteEmail: string;
299
- link: string;
300
- orgId: string;
301
- orgName: string;
302
- requesterEmail: string;
303
- };
305
+ "multipart/form-data": components["schemas"]["ICreateTestUploadArgs"];
304
306
  };
305
307
  };
306
308
  responses: {
307
- /** @description Team member invited successfully. */
309
+ /** @description The record has been successfully created. */
308
310
  201: {
309
311
  content: {
310
- "application/json": boolean;
312
+ "application/json": {
313
+ message?: string;
314
+ results?: components["schemas"]["IDBResult"][];
315
+ };
311
316
  };
312
317
  };
313
318
  };
314
319
  };
315
- OrgController_paddleTransactionCompleted: {
320
+ UploadsController_retryTest: {
316
321
  parameters: {
317
322
  header: {
318
- "paddle-signature": string;
323
+ "x-app-api-key": string;
324
+ };
325
+ };
326
+ requestBody: {
327
+ content: {
328
+ "application/json": components["schemas"]["IRetryTestArgs"];
319
329
  };
320
330
  };
321
331
  responses: {
322
- /** @description Success. */
332
+ /** @description The record has been successfully created. */
323
333
  201: {
324
334
  content: {
325
- "application/json": string;
335
+ "application/json": {
336
+ message?: string;
337
+ results?: components["schemas"]["IDBResult"][];
338
+ };
326
339
  };
327
340
  };
328
341
  };
329
342
  };
330
- OrgController_updateOrgName: {
343
+ UploadsController_cancelTest: {
331
344
  parameters: {
332
345
  header: {
333
346
  "x-app-api-key": string;
@@ -335,50 +348,62 @@ export interface operations {
335
348
  };
336
349
  requestBody: {
337
350
  content: {
338
- "application/json": {
339
- name: string;
340
- orgId: string;
341
- };
351
+ "application/json": components["schemas"]["ICancelTestArgs"];
342
352
  };
343
353
  };
344
354
  responses: {
345
- /** @description Organization name updated successfully. */
355
+ /** @description The record has been successfully cancelled. */
346
356
  201: {
347
357
  content: {
348
- "application/json": boolean;
358
+ "application/json": {
359
+ message?: string;
360
+ success?: boolean;
361
+ cancelledCount?: number;
362
+ };
349
363
  };
350
364
  };
351
365
  };
352
366
  };
353
- ResultsController_createResult: {
367
+ UploadsController_getUploadStatus: {
354
368
  parameters: {
369
+ query?: {
370
+ /** @description Upload ID to get status for */
371
+ uploadId?: string;
372
+ /** @description Upload name to get status for */
373
+ name?: string;
374
+ };
355
375
  header: {
356
376
  "x-app-api-key": string;
357
377
  };
358
378
  };
359
379
  responses: {
360
- 201: {
361
- content: never;
380
+ /** @description Upload status */
381
+ 200: {
382
+ content: {
383
+ "application/json": Record<string, never>;
384
+ };
362
385
  };
363
386
  };
364
387
  };
365
- ResultsController_getCompatibilityData: {
388
+ UploadsController_deleteUpload: {
366
389
  parameters: {
367
390
  header: {
368
391
  "x-app-api-key": string;
369
392
  };
393
+ path: {
394
+ uploadId: string;
395
+ };
370
396
  };
371
397
  responses: {
372
- /** @description Device compatibility lookup data */
373
398
  200: {
399
+ content: never;
400
+ };
401
+ /** @description The upload has been successfully deleted. */
402
+ 201: {
374
403
  content: {
375
404
  "application/json": {
376
- data?: {
377
- android?: Record<string, never>;
378
- androidPlay?: Record<string, never>;
379
- ios?: Record<string, never>;
380
- };
381
- statusCode?: number;
405
+ success?: boolean;
406
+ message?: string;
382
407
  };
383
408
  };
384
409
  };
@@ -398,8 +423,8 @@ export interface operations {
398
423
  200: {
399
424
  content: {
400
425
  "application/json": {
401
- results?: components["schemas"]["TResultResponse"][];
402
426
  statusCode?: number;
427
+ results?: components["schemas"]["TResultResponse"][];
403
428
  };
404
429
  };
405
430
  };
@@ -420,6 +445,18 @@ export interface operations {
420
445
  };
421
446
  };
422
447
  };
448
+ ResultsController_createResult: {
449
+ parameters: {
450
+ header: {
451
+ "x-app-api-key": string;
452
+ };
453
+ };
454
+ responses: {
455
+ 201: {
456
+ content: never;
457
+ };
458
+ };
459
+ };
423
460
  ResultsController_notifyTestRunComplete: {
424
461
  parameters: {
425
462
  header: {
@@ -438,58 +475,44 @@ export interface operations {
438
475
  };
439
476
  };
440
477
  };
441
- StatsController_getPublicStats: {
442
- responses: {
443
- 200: {
444
- content: never;
445
- };
446
- };
447
- };
448
- StatsController_reportSlowTestsDev: {
449
- responses: {
450
- 201: {
451
- content: never;
452
- };
453
- };
454
- };
455
- StatsController_reportSlowTestsProd: {
456
- responses: {
457
- 201: {
458
- content: never;
478
+ ResultsController_getCompatibilityData: {
479
+ parameters: {
480
+ header: {
481
+ "x-app-api-key": string;
459
482
  };
460
483
  };
461
- };
462
- StatsController_sendDailyReport: {
463
484
  responses: {
464
- 201: {
465
- content: never;
485
+ /** @description Device compatibility lookup data */
486
+ 200: {
487
+ content: {
488
+ "application/json": {
489
+ statusCode?: number;
490
+ data?: {
491
+ ios?: Record<string, never>;
492
+ android?: Record<string, never>;
493
+ androidPlay?: Record<string, never>;
494
+ };
495
+ };
496
+ };
466
497
  };
467
498
  };
468
499
  };
469
- UploadsController_cancelTest: {
500
+ OrgController_paddleTransactionCompleted: {
470
501
  parameters: {
471
502
  header: {
472
- "x-app-api-key": string;
473
- };
474
- };
475
- requestBody: {
476
- content: {
477
- "application/json": components["schemas"]["ICancelTestArgs"];
503
+ "paddle-signature": string;
478
504
  };
479
505
  };
480
506
  responses: {
481
- /** @description The record has been successfully cancelled. */
507
+ /** @description Success. */
482
508
  201: {
483
509
  content: {
484
- "application/json": {
485
- message?: string;
486
- results?: components["schemas"]["IDBResult"][];
487
- };
510
+ "application/json": string;
488
511
  };
489
512
  };
490
513
  };
491
514
  };
492
- UploadsController_checkForExistingUpload: {
515
+ OrgController_updateOrgName: {
493
516
  parameters: {
494
517
  header: {
495
518
  "x-app-api-key": string;
@@ -497,19 +520,22 @@ export interface operations {
497
520
  };
498
521
  requestBody: {
499
522
  content: {
500
- "application/json": components["schemas"]["ICheckForExistingUploadArgs"];
523
+ "application/json": {
524
+ orgId: string;
525
+ name: string;
526
+ };
501
527
  };
502
528
  };
503
529
  responses: {
504
- /** @description The url has been successfully created. */
530
+ /** @description Organization name updated successfully. */
505
531
  201: {
506
532
  content: {
507
- "application/json": components["schemas"]["ICheckForExistingUploadResponse"];
533
+ "application/json": boolean;
508
534
  };
509
535
  };
510
536
  };
511
537
  };
512
- UploadsController_createBinary: {
538
+ OrgController_inviteTeamMember: {
513
539
  parameters: {
514
540
  header: {
515
541
  "x-app-api-key": string;
@@ -517,25 +543,25 @@ export interface operations {
517
543
  };
518
544
  requestBody: {
519
545
  content: {
520
- "multipart/form-data": components["schemas"]["ICreateBinaryUploadArgs"];
546
+ "application/json": {
547
+ inviteEmail: string;
548
+ requesterEmail: string;
549
+ link: string;
550
+ orgId: string;
551
+ orgName: string;
552
+ };
521
553
  };
522
554
  };
523
555
  responses: {
524
- /**
525
- * @description The record has been successfully created.
526
- * @example {
527
- * "message": "Binary uploaded successfully",
528
- * "binaryId": "binary-123-abc-def"
529
- * }
530
- */
556
+ /** @description Team member invited successfully. */
531
557
  201: {
532
558
  content: {
533
- "application/json": components["schemas"]["ICreateBinaryResponse"];
559
+ "application/json": boolean;
534
560
  };
535
561
  };
536
562
  };
537
563
  };
538
- UploadsController_createTest: {
564
+ OrgController_acceptInvite: {
539
565
  parameters: {
540
566
  header: {
541
567
  "x-app-api-key": string;
@@ -543,124 +569,95 @@ export interface operations {
543
569
  };
544
570
  requestBody: {
545
571
  content: {
546
- "multipart/form-data": components["schemas"]["ICreateTestUploadArgs"];
572
+ "application/json": {
573
+ orgId: string;
574
+ };
547
575
  };
548
576
  };
549
577
  responses: {
550
- /** @description The record has been successfully created. */
578
+ /** @description Team invite accepted successfully. */
551
579
  201: {
552
580
  content: {
553
- "application/json": {
554
- message?: string;
555
- results?: components["schemas"]["IDBResult"][];
556
- };
581
+ "application/json": boolean;
557
582
  };
558
583
  };
559
584
  };
560
585
  };
561
- UploadsController_deleteUpload: {
562
- parameters: {
563
- header: {
564
- "x-app-api-key": string;
565
- };
566
- path: {
567
- uploadId: string;
568
- };
569
- };
586
+ StatsController_getPublicStats: {
570
587
  responses: {
571
588
  200: {
572
589
  content: never;
573
590
  };
574
- /** @description The upload has been successfully deleted. */
575
- 201: {
576
- content: {
577
- "application/json": {
578
- message?: string;
579
- success?: boolean;
580
- };
581
- };
582
- };
583
591
  };
584
592
  };
585
- UploadsController_finaliseUpload: {
586
- parameters: {
587
- header: {
588
- "x-app-api-key": string;
593
+ StatsController_sendDailyReport: {
594
+ responses: {
595
+ 201: {
596
+ content: never;
589
597
  };
590
598
  };
591
- requestBody: {
592
- content: {
593
- "application/json": components["schemas"]["IFinaliseUploadArgs"];
599
+ };
600
+ StatsController_reportSlowTestsDev: {
601
+ responses: {
602
+ 201: {
603
+ content: never;
594
604
  };
595
605
  };
606
+ };
607
+ StatsController_reportSlowTestsProd: {
596
608
  responses: {
597
- /** @description The upload has been completed. */
598
609
  201: {
599
- content: {
600
- "application/json": components["schemas"]["IFinaliseUploadResponse"];
601
- };
610
+ content: never;
602
611
  };
603
612
  };
604
613
  };
605
- UploadsController_getBinaryUploadUrl: {
606
- parameters: {
607
- header: {
608
- "x-app-api-key": string;
614
+ MoropoController_runScheduledJobs: {
615
+ responses: {
616
+ 200: {
617
+ content: never;
609
618
  };
610
619
  };
620
+ };
621
+ FrontendController_checkDomainSaml: {
622
+ /** @description Domain to check for SAML configuration */
611
623
  requestBody: {
612
624
  content: {
613
- "application/json": components["schemas"]["IGetBinaryUploadUrlArgs"];
625
+ "application/json": {
626
+ /** @example example.com */
627
+ domain: string;
628
+ };
614
629
  };
615
630
  };
616
631
  responses: {
617
- /** @description The url has been successfully created. */
618
- 201: {
632
+ /** @description SAML status for the domain */
633
+ 200: {
619
634
  content: {
620
- "application/json": components["schemas"]["IGetBinaryUploadUrlResponse"];
635
+ "application/json": {
636
+ forceSaml?: boolean;
637
+ };
621
638
  };
622
639
  };
623
- };
624
- };
625
- UploadsController_getUploadStatus: {
626
- parameters: {
627
- header: {
628
- "x-app-api-key": string;
629
- };
630
- query?: {
631
- /** @description Upload name to get status for */
632
- name?: string;
633
- /** @description Upload ID to get status for */
634
- uploadId?: string;
640
+ 201: {
641
+ content: never;
635
642
  };
636
- };
637
- responses: {
638
- /** @description Upload status */
639
- 200: {
643
+ /** @description Bad request - invalid domain or API error */
644
+ 400: {
640
645
  content: {
641
- "application/json": Record<string, never>;
646
+ "application/json": {
647
+ error?: string;
648
+ };
642
649
  };
643
650
  };
644
651
  };
645
652
  };
646
- UploadsController_retryTest: {
647
- parameters: {
648
- header: {
649
- "x-app-api-key": string;
650
- };
651
- };
652
- requestBody: {
653
- content: {
654
- "application/json": components["schemas"]["IRetryTestArgs"];
655
- };
656
- };
653
+ HealthController_health: {
657
654
  responses: {
658
- /** @description The record has been successfully created. */
659
- 201: {
655
+ /** @description Health check endpoint */
656
+ 200: {
660
657
  content: {
661
658
  "application/json": {
662
- message?: string;
663
- results?: components["schemas"]["IDBResult"][];
659
+ /** @example ok */
660
+ status?: string;
664
661
  };
665
662
  };
666
663
  };
@@ -1,6 +1,3 @@
1
1
  "use strict";
2
- /**
3
- * This file was auto-generated by openapi-typescript.
4
- * Do not make direct changes to the file.
5
- */
2
+ /* eslint-disable prettier/prettier */
6
3
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -124,6 +124,16 @@
124
124
  "multiple": false,
125
125
  "type": "option"
126
126
  },
127
+ "junit-path": {
128
+ "dependsOn": [
129
+ "report"
130
+ ],
131
+ "description": "Custom file path for downloaded JUnit report (default: ./report.xml)",
132
+ "name": "junit-path",
133
+ "hasDynamicHelp": false,
134
+ "multiple": false,
135
+ "type": "option"
136
+ },
127
137
  "async": {
128
138
  "description": "Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)",
129
139
  "name": "async",
@@ -151,7 +161,7 @@
151
161
  "type": "option"
152
162
  },
153
163
  "download-artifacts": {
154
- "description": "Download a zip containing the logs, screenshots and videos for each result in this run. You will debited a $0.01 egress fee for each result. Use --download-artifacts=FAILED for failures only or --download-artifacts=ALL for every result.",
164
+ "description": "Download a zip containing the logs, screenshots and videos for each result in this run. You will be debited a $0.01 egress fee for each result. Options: ALL (everything), FAILED (failures only).",
155
165
  "name": "download-artifacts",
156
166
  "hasDynamicHelp": false,
157
167
  "multiple": false,
@@ -382,7 +392,7 @@
382
392
  "type": "option"
383
393
  },
384
394
  "runner-type": {
385
- "description": "[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information",
395
+ "description": "[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information. gpu1 is Android-only and requires contacting support to enable, otherwise reverts to default.",
386
396
  "name": "runner-type",
387
397
  "default": "default",
388
398
  "hasDynamicHelp": false,
@@ -390,7 +400,8 @@
390
400
  "options": [
391
401
  "default",
392
402
  "m4",
393
- "m1"
403
+ "m1",
404
+ "gpu1"
394
405
  ],
395
406
  "type": "option"
396
407
  },
@@ -570,5 +581,5 @@
570
581
  ]
571
582
  }
572
583
  },
573
- "version": "4.0.1"
584
+ "version": "4.0.2"
574
585
  }
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "@oclif/core": "^3",
8
8
  "@oclif/plugin-autocomplete": "^3.0.8",
9
9
  "@oclif/plugin-help": "^6",
10
- "@oclif/plugin-not-found": "^3.0.10",
10
+ "@oclif/plugin-not-found": "^3.2.65",
11
11
  "@oclif/plugin-plugins": "^4",
12
12
  "@oclif/plugin-update": "^4.1.11",
13
13
  "@oclif/plugin-warn-if-update-available": "^3.0.10",
@@ -38,7 +38,7 @@
38
38
  "eslint-config-oclif-typescript": "^3",
39
39
  "eslint-config-prettier": "^10.1.8",
40
40
  "mocha": "^11.7.1",
41
- "oclif": "^4",
41
+ "oclif": "^4.22.12",
42
42
  "shx": "^0.4.0",
43
43
  "ts-node": "^10.9.2",
44
44
  "typescript": "^5.9.2"
@@ -80,7 +80,7 @@
80
80
  "version": "oclif readme && git add README.md",
81
81
  "test": "node scripts/test-runner.mjs"
82
82
  },
83
- "version": "4.0.1",
83
+ "version": "4.0.2",
84
84
  "bugs": {
85
85
  "url": "https://discord.gg/gm3mJwcNw8"
86
86
  },