@devicecloud.dev/dcd 4.0.4 → 4.1.1
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.
- package/dist/commands/cloud.d.ts +15 -3
- package/dist/commands/cloud.js +67 -67
- package/dist/constants.d.ts +2 -3
- package/dist/constants.js +13 -21
- package/dist/gateways/api-gateway.d.ts +17 -1
- package/dist/gateways/api-gateway.js +129 -26
- package/dist/methods.d.ts +0 -2
- package/dist/methods.js +1 -16
- package/dist/types/device.types.d.ts +2 -1
- package/dist/types/device.types.js +1 -0
- package/dist/types/schema.types.d.ts +189 -79
- package/oclif.manifest.json +28 -28
- package/package.json +2 -2
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ export default class Cloud extends Command {
|
|
|
9
9
|
static enableJsonFlag: boolean;
|
|
10
10
|
static examples: string[];
|
|
11
11
|
static flags: {
|
|
12
|
-
'additional-app-binary-ids': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
-
'additional-app-files': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
12
|
'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
13
|
'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
14
|
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -19,6 +17,8 @@ export default class Cloud extends Command {
|
|
|
19
17
|
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
18
|
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
19
|
'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
'allure-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
'html-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
22
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
23
23
|
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
24
|
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
@@ -50,8 +50,20 @@ export default class Cloud extends Command {
|
|
|
50
50
|
'runner-type': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
51
51
|
'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
52
52
|
'skip-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
53
|
-
'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
54
53
|
};
|
|
55
54
|
private versionCheck;
|
|
56
55
|
run(): Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Handle downloading reports based on the report type specified
|
|
58
|
+
* @param reportType The type of report to download ('junit', 'allure', or 'html')
|
|
59
|
+
* @param apiUrl The base URL for the API server
|
|
60
|
+
* @param apiKey The API key for authentication
|
|
61
|
+
* @param uploadId The unique identifier for the test upload
|
|
62
|
+
* @param junitPath Optional file path where the JUnit report should be saved
|
|
63
|
+
* @param allurePath Optional file path where the Allure report should be saved
|
|
64
|
+
* @param htmlPath Optional file path where the HTML report should be saved
|
|
65
|
+
* @param debug Whether debug logging is enabled
|
|
66
|
+
* @returns Promise that resolves when all reports have been downloaded
|
|
67
|
+
*/
|
|
68
|
+
private handleReportDownloads;
|
|
57
69
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -9,12 +9,12 @@ const node_child_process_1 = require("node:child_process");
|
|
|
9
9
|
const fs = require("node:fs");
|
|
10
10
|
const os = require("node:os");
|
|
11
11
|
const path = require("node:path");
|
|
12
|
-
const streamZip = require("node-stream-zip");
|
|
13
12
|
const constants_1 = require("../constants");
|
|
14
13
|
const api_gateway_1 = require("../gateways/api-gateway");
|
|
15
14
|
const methods_1 = require("../methods");
|
|
16
15
|
const plan_1 = require("../plan");
|
|
17
16
|
const compatibility_1 = require("../utils/compatibility");
|
|
17
|
+
const StreamZip = require("node-stream-zip");
|
|
18
18
|
exports.mimeTypeLookupByExtension = {
|
|
19
19
|
apk: 'application/vnd.android.package-archive',
|
|
20
20
|
yaml: 'application/x-yaml',
|
|
@@ -71,7 +71,7 @@ class Cloud extends core_1.Command {
|
|
|
71
71
|
let jsonFile = false;
|
|
72
72
|
try {
|
|
73
73
|
const { args, flags, raw } = await this.parse(Cloud);
|
|
74
|
-
let { '
|
|
74
|
+
let { 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, 'junit-path': junitPath, 'allure-path': allurePath, 'html-path': htmlPath, 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, ...rest } = flags;
|
|
75
75
|
// Resolve "latest" maestro version to actual version
|
|
76
76
|
const resolvedMaestroVersion = (0, constants_1.resolveMaestroVersion)(maestroVersion);
|
|
77
77
|
// Store debug flag for use in catch block
|
|
@@ -170,7 +170,6 @@ class Cloud extends core_1.Command {
|
|
|
170
170
|
core_1.ux.action.status = 'Extracting tests...';
|
|
171
171
|
}
|
|
172
172
|
// Extract zip file
|
|
173
|
-
const StreamZip = streamZip;
|
|
174
173
|
// eslint-disable-next-line new-cap
|
|
175
174
|
const zip = new StreamZip.async({ file: zipPath });
|
|
176
175
|
await zip.extract(null, moropoDir);
|
|
@@ -222,7 +221,6 @@ class Cloud extends core_1.Command {
|
|
|
222
221
|
}
|
|
223
222
|
if (debug) {
|
|
224
223
|
this.log(`DEBUG: API URL: ${apiUrl}`);
|
|
225
|
-
this.log(`DEBUG: API Key provided: ${apiKey ? 'Yes' : 'No'}`);
|
|
226
224
|
}
|
|
227
225
|
if (retry && retry > 2) {
|
|
228
226
|
this.log("Retries are now free of charge but limited to 2. If you're test is still failing after 2 retries, please ask for help on Discord.");
|
|
@@ -238,11 +236,8 @@ class Cloud extends core_1.Command {
|
|
|
238
236
|
if (runnerType === 'gpu1') {
|
|
239
237
|
this.log('Note: runnerType gpu1 is Android-only and requires contacting support to enable. Without support enablement, your runner type will revert to default.');
|
|
240
238
|
}
|
|
241
|
-
const additionalAppBinaryIds = nonFlatAdditionalAppBinaryIds?.flat();
|
|
242
|
-
const additionalAppFiles = nonFlatAdditionalAppFiles?.flat();
|
|
243
239
|
const { firstFile, secondFile } = args;
|
|
244
240
|
let finalBinaryId = appBinaryId;
|
|
245
|
-
let finalAdditionalBinaryIds = additionalAppBinaryIds;
|
|
246
241
|
const finalAppFile = appFile ?? firstFile;
|
|
247
242
|
let flowFile = flows ?? secondFile;
|
|
248
243
|
if (debug) {
|
|
@@ -251,8 +246,6 @@ class Cloud extends core_1.Command {
|
|
|
251
246
|
this.log(`DEBUG: App binary ID: ${appBinaryId || 'not provided'}`);
|
|
252
247
|
this.log(`DEBUG: App file: ${finalAppFile || 'not provided'}`);
|
|
253
248
|
this.log(`DEBUG: Flow file: ${flowFile || 'not provided'}`);
|
|
254
|
-
this.log(`DEBUG: Additional app binary IDs: ${additionalAppBinaryIds?.join(', ') || 'none'}`);
|
|
255
|
-
this.log(`DEBUG: Additional app files: ${additionalAppFiles?.join(', ') || 'none'}`);
|
|
256
249
|
}
|
|
257
250
|
if (appBinaryId) {
|
|
258
251
|
if (secondFile) {
|
|
@@ -370,13 +363,10 @@ class Cloud extends core_1.Command {
|
|
|
370
363
|
await (0, methods_1.verifyAppZip)(finalAppFile);
|
|
371
364
|
}
|
|
372
365
|
}
|
|
373
|
-
if (debug && additionalAppFiles?.length) {
|
|
374
|
-
this.log(`DEBUG: Verifying additional app files: ${additionalAppFiles.join(', ')}`);
|
|
375
|
-
}
|
|
376
|
-
await (0, methods_1.verifyAdditionalAppFiles)(additionalAppFiles);
|
|
377
366
|
const flagLogs = [];
|
|
367
|
+
const sensitiveFlags = new Set(['api-key', 'apiKey', 'moropo-v1-api-key']);
|
|
378
368
|
for (const [k, v] of Object.entries(flags)) {
|
|
379
|
-
if (v && v.toString().length > 0) {
|
|
369
|
+
if (v && v.toString().length > 0 && !sensitiveFlags.has(k)) {
|
|
380
370
|
flagLogs.push(`${k}: ${v}`);
|
|
381
371
|
}
|
|
382
372
|
}
|
|
@@ -402,9 +392,6 @@ class Cloud extends core_1.Command {
|
|
|
402
392
|
Submitting new job
|
|
403
393
|
→ Flow(s): ${flowFile}
|
|
404
394
|
→ App: ${appBinaryId || finalAppFile}
|
|
405
|
-
${additionalAppBinaryIds.length > 0 || additionalAppFiles.length > 0
|
|
406
|
-
? `→ Additional app(s): ${additionalAppBinaryIds} ${additionalAppFiles}`
|
|
407
|
-
: ''}
|
|
408
395
|
|
|
409
396
|
With options
|
|
410
397
|
→ ${flagLogs.join(`
|
|
@@ -440,21 +427,6 @@ class Cloud extends core_1.Command {
|
|
|
440
427
|
this.log(`DEBUG: Binary uploaded with ID: ${binaryId}`);
|
|
441
428
|
}
|
|
442
429
|
}
|
|
443
|
-
let uploadedBinaryIds = [];
|
|
444
|
-
if (additionalAppFiles?.length) {
|
|
445
|
-
if (debug) {
|
|
446
|
-
this.log(`DEBUG: Uploading additional binary files: ${additionalAppFiles.join(', ')}`);
|
|
447
|
-
}
|
|
448
|
-
uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
449
|
-
finalAdditionalBinaryIds = [
|
|
450
|
-
...finalAdditionalBinaryIds,
|
|
451
|
-
...uploadedBinaryIds,
|
|
452
|
-
];
|
|
453
|
-
if (debug) {
|
|
454
|
-
this.log(`DEBUG: Additional binaries uploaded with IDs: ${uploadedBinaryIds.join(', ')}`);
|
|
455
|
-
this.log(`DEBUG: Final additional binary IDs: ${finalAdditionalBinaryIds.join(', ')}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
430
|
const testFormData = new FormData();
|
|
459
431
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
460
432
|
const envObject = (env ?? []).reduce((acc, cur) => {
|
|
@@ -526,16 +498,8 @@ class Cloud extends core_1.Command {
|
|
|
526
498
|
report,
|
|
527
499
|
showCrosshairs: flags['show-crosshairs'],
|
|
528
500
|
skipChromeOnboarding: flags['skip-chrome-onboarding'],
|
|
529
|
-
uploadedBinaryIds,
|
|
530
501
|
version: this.config.version,
|
|
531
|
-
x86Arch,
|
|
532
502
|
};
|
|
533
|
-
if (finalAdditionalBinaryIds?.length > 0) {
|
|
534
|
-
config.additionalAppBinaryIds = finalAdditionalBinaryIds;
|
|
535
|
-
}
|
|
536
|
-
if (uploadedBinaryIds?.length > 0) {
|
|
537
|
-
config.uploadedBinaryIds = uploadedBinaryIds;
|
|
538
|
-
}
|
|
539
503
|
testFormData.set('config', JSON.stringify(config));
|
|
540
504
|
if (Object.keys(metadataObject).length > 0) {
|
|
541
505
|
const metadataPayload = { userMetadata: metadataObject };
|
|
@@ -691,33 +655,9 @@ class Cloud extends core_1.Command {
|
|
|
691
655
|
this.warn('Failed to download artifacts');
|
|
692
656
|
}
|
|
693
657
|
}
|
|
694
|
-
// Handle report
|
|
695
|
-
if (report
|
|
696
|
-
|
|
697
|
-
if (debug) {
|
|
698
|
-
this.log('DEBUG: Downloading JUNIT report');
|
|
699
|
-
}
|
|
700
|
-
const reportPath = path.resolve(process.cwd(), junitPath || 'report.xml');
|
|
701
|
-
await api_gateway_1.ApiGateway.downloadReport(apiUrl, apiKey, results[0].test_upload_id, reportPath);
|
|
702
|
-
this.log('\n');
|
|
703
|
-
this.log(`JUNIT test report has been downloaded to ${reportPath}`);
|
|
704
|
-
}
|
|
705
|
-
catch (error) {
|
|
706
|
-
if (debug) {
|
|
707
|
-
this.log(`DEBUG: Error downloading JUNIT report: ${error}`);
|
|
708
|
-
}
|
|
709
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
710
|
-
this.warn(`Failed to download JUNIT report: ${errorMessage}`);
|
|
711
|
-
if (errorMessage.includes('404')) {
|
|
712
|
-
this.warn('No JUNIT reports found for this upload. Make sure your tests were run with --report junit flag.');
|
|
713
|
-
}
|
|
714
|
-
else if (errorMessage.includes('EACCES') || errorMessage.includes('EPERM')) {
|
|
715
|
-
this.warn('Permission denied. Check write permissions for the current directory.');
|
|
716
|
-
}
|
|
717
|
-
else if (errorMessage.includes('ENOENT')) {
|
|
718
|
-
this.warn('Directory does not exist. Make sure you have write access to the current directory.');
|
|
719
|
-
}
|
|
720
|
-
}
|
|
658
|
+
// Handle report downloads based on --report flag
|
|
659
|
+
if (report && ['allure', 'html', 'junit'].includes(report)) {
|
|
660
|
+
await this.handleReportDownloads(report, apiUrl, apiKey, results[0].test_upload_id, junitPath, allurePath, htmlPath, debug);
|
|
721
661
|
}
|
|
722
662
|
const resultsWithoutEarlierTries = updatedResults.filter((result) => {
|
|
723
663
|
const originalTryId = result.retry_of || result.id;
|
|
@@ -818,5 +758,65 @@ class Cloud extends core_1.Command {
|
|
|
818
758
|
}
|
|
819
759
|
}
|
|
820
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Handle downloading reports based on the report type specified
|
|
763
|
+
* @param reportType The type of report to download ('junit', 'allure', or 'html')
|
|
764
|
+
* @param apiUrl The base URL for the API server
|
|
765
|
+
* @param apiKey The API key for authentication
|
|
766
|
+
* @param uploadId The unique identifier for the test upload
|
|
767
|
+
* @param junitPath Optional file path where the JUnit report should be saved
|
|
768
|
+
* @param allurePath Optional file path where the Allure report should be saved
|
|
769
|
+
* @param htmlPath Optional file path where the HTML report should be saved
|
|
770
|
+
* @param debug Whether debug logging is enabled
|
|
771
|
+
* @returns Promise that resolves when all reports have been downloaded
|
|
772
|
+
*/
|
|
773
|
+
// eslint-disable-next-line max-params
|
|
774
|
+
async handleReportDownloads(reportType, apiUrl, apiKey, uploadId, junitPath, allurePath, htmlPath, debug) {
|
|
775
|
+
const downloadReport = async (type, filePath) => {
|
|
776
|
+
try {
|
|
777
|
+
if (debug) {
|
|
778
|
+
this.log(`DEBUG: Downloading ${type.toUpperCase()} report`);
|
|
779
|
+
}
|
|
780
|
+
await api_gateway_1.ApiGateway.downloadReportGeneric(apiUrl, apiKey, uploadId, type, filePath);
|
|
781
|
+
this.log(`${type.toUpperCase()} test report has been downloaded to ${filePath}`);
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
if (debug) {
|
|
785
|
+
this.log(`DEBUG: Error downloading ${type.toUpperCase()} report: ${error}`);
|
|
786
|
+
}
|
|
787
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
788
|
+
this.warn(`Failed to download ${type.toUpperCase()} report: ${errorMessage}`);
|
|
789
|
+
if (errorMessage.includes('404')) {
|
|
790
|
+
this.warn(`No ${type.toUpperCase()} reports found for this upload. Make sure your tests generated results.`);
|
|
791
|
+
}
|
|
792
|
+
else if (errorMessage.includes('EACCES') || errorMessage.includes('EPERM')) {
|
|
793
|
+
this.warn('Permission denied. Check write permissions for the current directory.');
|
|
794
|
+
}
|
|
795
|
+
else if (errorMessage.includes('ENOENT')) {
|
|
796
|
+
this.warn('Directory does not exist. Make sure you have write access to the current directory.');
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
switch (reportType) {
|
|
801
|
+
case 'junit': {
|
|
802
|
+
const reportPath = path.resolve(process.cwd(), junitPath || 'report.xml');
|
|
803
|
+
await downloadReport('junit', reportPath);
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
case 'allure': {
|
|
807
|
+
const reportPath = path.resolve(process.cwd(), allurePath || 'report.html');
|
|
808
|
+
await downloadReport('allure', reportPath);
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
case 'html': {
|
|
812
|
+
const htmlReportPath = path.resolve(process.cwd(), htmlPath || 'report.html');
|
|
813
|
+
await downloadReport('html', htmlReportPath);
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
default: {
|
|
817
|
+
this.warn(`Unknown report type: ${reportType}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
821
|
}
|
|
822
822
|
exports.default = Cloud;
|
package/dist/constants.d.ts
CHANGED
|
@@ -3,8 +3,6 @@ export declare const DEFAULT_MAESTRO_VERSION = "1.41.0";
|
|
|
3
3
|
export declare const getLatestMaestroVersion: () => string;
|
|
4
4
|
export declare const resolveMaestroVersion: (version?: string) => string;
|
|
5
5
|
export declare const flags: {
|
|
6
|
-
'additional-app-binary-ids': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
-
'additional-app-files': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
6
|
'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
7
|
'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
8
|
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -13,6 +11,8 @@ export declare const flags: {
|
|
|
13
11
|
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
12
|
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
13
|
'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
'allure-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
'html-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
16
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
18
|
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
@@ -44,5 +44,4 @@ export declare const flags: {
|
|
|
44
44
|
'runner-type': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
45
45
|
'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
46
46
|
'skip-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
47
|
-
'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
48
47
|
};
|
package/dist/constants.js
CHANGED
|
@@ -27,20 +27,6 @@ const resolveMaestroVersion = (version) => {
|
|
|
27
27
|
};
|
|
28
28
|
exports.resolveMaestroVersion = resolveMaestroVersion;
|
|
29
29
|
exports.flags = {
|
|
30
|
-
'additional-app-binary-ids': core_1.Flags.string({
|
|
31
|
-
default: [],
|
|
32
|
-
description: 'The ID of the additional app binary(s) previously uploaded to devicecloud.dev to install before execution',
|
|
33
|
-
multiple: true,
|
|
34
|
-
multipleNonGreedy: true,
|
|
35
|
-
parse: (input) => input.split(','),
|
|
36
|
-
}),
|
|
37
|
-
'additional-app-files': core_1.Flags.file({
|
|
38
|
-
default: [],
|
|
39
|
-
description: 'Additional app binary(s) to install before execution',
|
|
40
|
-
multiple: true,
|
|
41
|
-
multipleNonGreedy: true,
|
|
42
|
-
parse: (input) => input.split(','),
|
|
43
|
-
}),
|
|
44
30
|
'android-api-level': core_1.Flags.string({
|
|
45
31
|
description: '[Android only] Android API level to run your flow against',
|
|
46
32
|
options: Object.values(device_types_1.EAndroidApiLevels),
|
|
@@ -75,6 +61,14 @@ exports.flags = {
|
|
|
75
61
|
dependsOn: ['report'],
|
|
76
62
|
description: 'Custom file path for downloaded JUnit report (default: ./report.xml)',
|
|
77
63
|
}),
|
|
64
|
+
'allure-path': core_1.Flags.string({
|
|
65
|
+
dependsOn: ['report'],
|
|
66
|
+
description: 'Custom file path for downloaded Allure report (default: ./report.html)',
|
|
67
|
+
}),
|
|
68
|
+
'html-path': core_1.Flags.string({
|
|
69
|
+
dependsOn: ['report'],
|
|
70
|
+
description: 'Custom file path for downloaded HTML report (default: ./report.html)',
|
|
71
|
+
}),
|
|
78
72
|
async: core_1.Flags.boolean({
|
|
79
73
|
description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)',
|
|
80
74
|
}),
|
|
@@ -169,10 +163,12 @@ exports.flags = {
|
|
|
169
163
|
}),
|
|
170
164
|
mitmHost: core_1.Flags.string({
|
|
171
165
|
description: 'used for mitmproxy support, enterprise only, contact support if interested',
|
|
166
|
+
hidden: true,
|
|
172
167
|
}),
|
|
173
168
|
mitmPath: core_1.Flags.string({
|
|
174
169
|
dependsOn: ['mitmHost'],
|
|
175
170
|
description: 'used for mitmproxy support, enterprise only, contact support if interested',
|
|
171
|
+
hidden: true,
|
|
176
172
|
}),
|
|
177
173
|
'moropo-v1-api-key': core_1.Flags.string({
|
|
178
174
|
description: 'API key for Moropo v1 integration',
|
|
@@ -183,7 +179,7 @@ exports.flags = {
|
|
|
183
179
|
}),
|
|
184
180
|
orientation: core_1.Flags.string({
|
|
185
181
|
description: '[Android only] The orientation of the device to run your flow against in degrees',
|
|
186
|
-
options: ['0', '90'
|
|
182
|
+
options: ['0', '90'],
|
|
187
183
|
}),
|
|
188
184
|
quiet: core_1.Flags.boolean({
|
|
189
185
|
char: 'q',
|
|
@@ -192,8 +188,8 @@ exports.flags = {
|
|
|
192
188
|
}),
|
|
193
189
|
report: core_1.Flags.string({
|
|
194
190
|
aliases: ['format'],
|
|
195
|
-
description: '
|
|
196
|
-
options: ['junit', 'html'],
|
|
191
|
+
description: 'Generate and download test reports in the specified format. Use "allure" for a complete HTML report.',
|
|
192
|
+
options: ['allure', 'junit', 'html'],
|
|
197
193
|
}),
|
|
198
194
|
retry: core_1.Flags.integer({
|
|
199
195
|
description: 'Automatically retry the run up to the number of times specified (same as pressing retry in the UI) - this is free of charge',
|
|
@@ -211,8 +207,4 @@ exports.flags = {
|
|
|
211
207
|
default: false,
|
|
212
208
|
description: '[Android only] Skip Chrome browser onboarding screens when running tests',
|
|
213
209
|
}),
|
|
214
|
-
'x86-arch': core_1.Flags.boolean({
|
|
215
|
-
default: false,
|
|
216
|
-
description: '[iOS only, experimental] Run your flow against x86 architecture simulator instead of arm64',
|
|
217
|
-
}),
|
|
218
210
|
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { TAppMetadata } from '../types';
|
|
2
2
|
export declare const ApiGateway: {
|
|
3
|
+
/**
|
|
4
|
+
* Standardized error handling for API responses
|
|
5
|
+
* @param res - The fetch response object
|
|
6
|
+
* @param operation - Description of the operation that failed
|
|
7
|
+
* @returns Never returns, always throws
|
|
8
|
+
*/
|
|
9
|
+
handleApiError(res: Response, operation: string): Promise<never>;
|
|
3
10
|
checkForExistingUpload(baseUrl: string, apiKey: string, sha: string): Promise<{
|
|
4
11
|
appBinaryId: string;
|
|
5
12
|
exists: boolean;
|
|
6
13
|
}>;
|
|
7
|
-
downloadReport(baseUrl: string, apiKey: string, uploadId: string, reportPath?: string): Promise<void>;
|
|
8
14
|
downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
|
|
9
15
|
finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
|
|
10
16
|
getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
|
|
@@ -33,4 +39,14 @@ export declare const ApiGateway: {
|
|
|
33
39
|
message?: string;
|
|
34
40
|
results?: import("../types/schema.types").components["schemas"]["IDBResult"][];
|
|
35
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Generic report download method that handles both junit and allure reports
|
|
44
|
+
* @param baseUrl - API base URL
|
|
45
|
+
* @param apiKey - API key for authentication
|
|
46
|
+
* @param uploadId - Upload ID to download report for
|
|
47
|
+
* @param reportType - Type of report to download ('junit' or 'allure')
|
|
48
|
+
* @param reportPath - Optional custom path for the downloaded report
|
|
49
|
+
* @returns Promise that resolves when download is complete
|
|
50
|
+
*/
|
|
51
|
+
downloadReportGeneric(baseUrl: string, apiKey: string, uploadId: string, reportType: "allure" | "html" | "junit", reportPath?: string): Promise<void>;
|
|
36
52
|
};
|
|
@@ -1,9 +1,50 @@
|
|
|
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
4
|
const path = require("node:path");
|
|
6
5
|
exports.ApiGateway = {
|
|
6
|
+
/**
|
|
7
|
+
* Standardized error handling for API responses
|
|
8
|
+
* @param res - The fetch response object
|
|
9
|
+
* @param operation - Description of the operation that failed
|
|
10
|
+
* @returns Never returns, always throws
|
|
11
|
+
*/
|
|
12
|
+
async handleApiError(res, operation) {
|
|
13
|
+
const errorText = await res.text();
|
|
14
|
+
let userMessage;
|
|
15
|
+
// Parse common API error formats
|
|
16
|
+
try {
|
|
17
|
+
const errorData = JSON.parse(errorText);
|
|
18
|
+
userMessage = errorData.message || errorData.error || errorText;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
userMessage = errorText;
|
|
22
|
+
}
|
|
23
|
+
// Add context and improve readability
|
|
24
|
+
switch (res.status) {
|
|
25
|
+
case 400: {
|
|
26
|
+
throw new Error(`Invalid request: ${userMessage}`);
|
|
27
|
+
}
|
|
28
|
+
case 401: {
|
|
29
|
+
throw new Error(`Authentication failed. Please check your API key.`);
|
|
30
|
+
}
|
|
31
|
+
case 403: {
|
|
32
|
+
throw new Error(`Access denied. ${userMessage}`);
|
|
33
|
+
}
|
|
34
|
+
case 404: {
|
|
35
|
+
throw new Error(`Resource not found. ${userMessage}`);
|
|
36
|
+
}
|
|
37
|
+
case 429: {
|
|
38
|
+
throw new Error(`Rate limit exceeded. Please try again later.`);
|
|
39
|
+
}
|
|
40
|
+
case 500: {
|
|
41
|
+
throw new Error(`Server error occurred. Please try again or contact support.`);
|
|
42
|
+
}
|
|
43
|
+
default: {
|
|
44
|
+
throw new Error(`${operation} failed: ${userMessage} (HTTP ${res.status})`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
7
48
|
async checkForExistingUpload(baseUrl, apiKey, sha) {
|
|
8
49
|
const res = await fetch(`${baseUrl}/uploads/checkForExistingUpload`, {
|
|
9
50
|
body: JSON.stringify({ sha }),
|
|
@@ -15,25 +56,6 @@ exports.ApiGateway = {
|
|
|
15
56
|
});
|
|
16
57
|
return res.json();
|
|
17
58
|
},
|
|
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
|
-
},
|
|
37
59
|
async downloadArtifactsZip(baseUrl, apiKey, uploadId, results, artifactsPath = './artifacts.zip') {
|
|
38
60
|
const res = await fetch(`${baseUrl}/results/${uploadId}/download`, {
|
|
39
61
|
body: JSON.stringify({ results }),
|
|
@@ -44,7 +66,7 @@ exports.ApiGateway = {
|
|
|
44
66
|
method: 'POST',
|
|
45
67
|
});
|
|
46
68
|
if (!res.ok) {
|
|
47
|
-
|
|
69
|
+
await this.handleApiError(res, 'Failed to download artifacts');
|
|
48
70
|
}
|
|
49
71
|
// Handle tilde expansion for home directory
|
|
50
72
|
if (artifactsPath.startsWith('~/') || artifactsPath === '~') {
|
|
@@ -87,7 +109,7 @@ exports.ApiGateway = {
|
|
|
87
109
|
method: 'POST',
|
|
88
110
|
});
|
|
89
111
|
if (!res.ok) {
|
|
90
|
-
|
|
112
|
+
await this.handleApiError(res, 'Failed to finalize upload');
|
|
91
113
|
}
|
|
92
114
|
return res.json();
|
|
93
115
|
},
|
|
@@ -101,7 +123,7 @@ exports.ApiGateway = {
|
|
|
101
123
|
method: 'POST',
|
|
102
124
|
});
|
|
103
125
|
if (!res.ok) {
|
|
104
|
-
|
|
126
|
+
await this.handleApiError(res, 'Failed to get upload URL');
|
|
105
127
|
}
|
|
106
128
|
return res.json();
|
|
107
129
|
},
|
|
@@ -111,7 +133,7 @@ exports.ApiGateway = {
|
|
|
111
133
|
headers: { 'x-app-api-key': apiKey },
|
|
112
134
|
});
|
|
113
135
|
if (!res.ok) {
|
|
114
|
-
|
|
136
|
+
await this.handleApiError(res, 'Failed to get results');
|
|
115
137
|
}
|
|
116
138
|
return res.json();
|
|
117
139
|
},
|
|
@@ -129,7 +151,7 @@ exports.ApiGateway = {
|
|
|
129
151
|
},
|
|
130
152
|
});
|
|
131
153
|
if (!response.ok) {
|
|
132
|
-
|
|
154
|
+
await this.handleApiError(response, 'Failed to get upload status');
|
|
133
155
|
}
|
|
134
156
|
return response.json();
|
|
135
157
|
},
|
|
@@ -142,8 +164,89 @@ exports.ApiGateway = {
|
|
|
142
164
|
method: 'POST',
|
|
143
165
|
});
|
|
144
166
|
if (!res.ok) {
|
|
145
|
-
|
|
167
|
+
await this.handleApiError(res, 'Failed to upload test flows');
|
|
146
168
|
}
|
|
147
169
|
return res.json();
|
|
148
170
|
},
|
|
171
|
+
/**
|
|
172
|
+
* Generic report download method that handles both junit and allure reports
|
|
173
|
+
* @param baseUrl - API base URL
|
|
174
|
+
* @param apiKey - API key for authentication
|
|
175
|
+
* @param uploadId - Upload ID to download report for
|
|
176
|
+
* @param reportType - Type of report to download ('junit' or 'allure')
|
|
177
|
+
* @param reportPath - Optional custom path for the downloaded report
|
|
178
|
+
* @returns Promise that resolves when download is complete
|
|
179
|
+
*/
|
|
180
|
+
async downloadReportGeneric(baseUrl, apiKey, uploadId, reportType, reportPath) {
|
|
181
|
+
// Define endpoint and default filename mappings
|
|
182
|
+
const config = {
|
|
183
|
+
junit: {
|
|
184
|
+
endpoint: `/results/${uploadId}/report`,
|
|
185
|
+
defaultFilename: `report-${uploadId}.xml`,
|
|
186
|
+
notFoundMessage: `Upload ID '${uploadId}' not found or no results available for this upload`,
|
|
187
|
+
errorPrefix: 'Failed to download report',
|
|
188
|
+
},
|
|
189
|
+
allure: {
|
|
190
|
+
endpoint: `/allure/${uploadId}/download`,
|
|
191
|
+
defaultFilename: `report-${uploadId}.html`,
|
|
192
|
+
notFoundMessage: `Upload ID '${uploadId}' not found or no Allure report available for this upload`,
|
|
193
|
+
errorPrefix: 'Failed to download Allure report',
|
|
194
|
+
},
|
|
195
|
+
html: {
|
|
196
|
+
endpoint: `/results/${uploadId}/html-report`,
|
|
197
|
+
defaultFilename: `report-${uploadId}.html`,
|
|
198
|
+
notFoundMessage: `Upload ID '${uploadId}' not found or no HTML report available for this upload`,
|
|
199
|
+
errorPrefix: 'Failed to download HTML report',
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
const { endpoint, defaultFilename, notFoundMessage, errorPrefix } = config[reportType];
|
|
203
|
+
const finalReportPath = reportPath || path.resolve(process.cwd(), defaultFilename);
|
|
204
|
+
const url = `${baseUrl}${endpoint}`;
|
|
205
|
+
// Make the download request
|
|
206
|
+
const res = await fetch(url, {
|
|
207
|
+
headers: {
|
|
208
|
+
'x-app-api-key': apiKey,
|
|
209
|
+
},
|
|
210
|
+
method: 'GET',
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok) {
|
|
213
|
+
const errorText = await res.text();
|
|
214
|
+
if (res.status === 404) {
|
|
215
|
+
throw new Error(notFoundMessage);
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`${errorPrefix}: ${res.status} ${errorText}`);
|
|
218
|
+
}
|
|
219
|
+
// Handle tilde expansion for home directory (applies to all report types)
|
|
220
|
+
let expandedPath = finalReportPath;
|
|
221
|
+
if (finalReportPath.startsWith('~/') || finalReportPath === '~') {
|
|
222
|
+
expandedPath = finalReportPath.replace(/^~/,
|
|
223
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
224
|
+
require('node:os').homedir());
|
|
225
|
+
}
|
|
226
|
+
// Create directory structure if it doesn't exist
|
|
227
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
228
|
+
const { dirname } = require('node:path');
|
|
229
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
230
|
+
const { createWriteStream, mkdirSync } = require('node:fs');
|
|
231
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
232
|
+
const { finished } = require('node:stream/promises');
|
|
233
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
234
|
+
const { Readable } = require('node:stream');
|
|
235
|
+
const directory = dirname(expandedPath);
|
|
236
|
+
if (directory !== '.') {
|
|
237
|
+
try {
|
|
238
|
+
mkdirSync(directory, { recursive: true });
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
// Ignore EEXIST errors (directory already exists)
|
|
242
|
+
if (error.code !== 'EEXIST') {
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Write the file using streaming for better memory efficiency
|
|
248
|
+
// Use 'w' flag to overwrite existing files instead of failing
|
|
249
|
+
const fileStream = createWriteStream(expandedPath, { flags: 'w' });
|
|
250
|
+
await finished(Readable.fromWeb(res.body).pipe(fileStream));
|
|
251
|
+
},
|
|
149
252
|
};
|
package/dist/methods.d.ts
CHANGED
|
@@ -8,8 +8,6 @@ export declare const extractAppMetadataAndroid: (appFilePath: string) => Promise
|
|
|
8
8
|
export declare const extractAppMetadataIosZip: (appFilePath: string) => Promise<TAppMetadata>;
|
|
9
9
|
export declare const extractAppMetadataIos: (appFolderPath: string) => Promise<TAppMetadata>;
|
|
10
10
|
export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string>;
|
|
11
|
-
export declare const uploadBinaries: (finalAppFiles: string[], apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string[]>;
|
|
12
|
-
export declare const verifyAdditionalAppFiles: (appFiles: string[] | undefined) => Promise<void>;
|
|
13
11
|
/**
|
|
14
12
|
* Writes JSON data to a file with error handling
|
|
15
13
|
* @param filePath - Path to the output JSON file
|
package/dist/methods.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatDurationSeconds = exports.writeJSONFile = exports.
|
|
3
|
+
exports.formatDurationSeconds = exports.writeJSONFile = exports.uploadBinary = exports.extractAppMetadataIos = exports.extractAppMetadataIosZip = exports.extractAppMetadataAndroid = exports.verifyAppZip = exports.compressFilesFromRelativePath = exports.compressFolderToBlob = exports.toBuffer = void 0;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
// required polyfill for node 18
|
|
6
6
|
const file_1 = require("@web-std/file");
|
|
@@ -201,21 +201,6 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false, lo
|
|
|
201
201
|
return id;
|
|
202
202
|
};
|
|
203
203
|
exports.uploadBinary = uploadBinary;
|
|
204
|
-
const uploadBinaries = async (finalAppFiles, apiUrl, apiKey, ignoreShaCheck = false, log = true) => Promise.all(finalAppFiles.map((f) => (0, exports.uploadBinary)(f, apiUrl, apiKey, ignoreShaCheck, log)));
|
|
205
|
-
exports.uploadBinaries = uploadBinaries;
|
|
206
|
-
const verifyAdditionalAppFiles = async (appFiles) => {
|
|
207
|
-
if (appFiles?.length) {
|
|
208
|
-
if (!appFiles.every((f) => ['apk', '.app', '.zip'].some((ext) => f.endsWith(ext)))) {
|
|
209
|
-
throw new Error('App file must be a .apk for android or .app/.zip file for iOS');
|
|
210
|
-
}
|
|
211
|
-
await Promise.all(appFiles.map(async (f) => {
|
|
212
|
-
if (f.endsWith('.zip')) {
|
|
213
|
-
await (0, exports.verifyAppZip)(f);
|
|
214
|
-
}
|
|
215
|
-
}));
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
exports.verifyAdditionalAppFiles = verifyAdditionalAppFiles;
|
|
219
204
|
async function getFileHashFromFile(file) {
|
|
220
205
|
return new Promise((resolve, reject) => {
|
|
221
206
|
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
@@ -30,6 +30,7 @@ var EiOSVersions;
|
|
|
30
30
|
EiOSVersions["eighteen"] = "18";
|
|
31
31
|
EiOSVersions["seventeen"] = "17";
|
|
32
32
|
EiOSVersions["sixteen"] = "16";
|
|
33
|
+
EiOSVersions["twentySix"] = "26";
|
|
33
34
|
})(EiOSVersions || (exports.EiOSVersions = EiOSVersions = {}));
|
|
34
35
|
var EAndroidApiLevels;
|
|
35
36
|
(function (EAndroidApiLevels) {
|
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
* Do not make direct changes to the file.
|
|
4
4
|
*/
|
|
5
5
|
export interface paths {
|
|
6
|
-
"/admin/cleanupOldUploads": {
|
|
7
|
-
post: operations["AdminController_cleanupOldUploads"];
|
|
8
|
-
};
|
|
9
6
|
"/uploads/binary": {
|
|
10
7
|
post: operations["UploadsController_createBinary"];
|
|
11
8
|
};
|
|
@@ -39,15 +36,36 @@ export interface paths {
|
|
|
39
36
|
"/results/{uploadId}/download": {
|
|
40
37
|
post: operations["ResultsController_getTestRunArtifacts"];
|
|
41
38
|
};
|
|
42
|
-
"/results": {
|
|
43
|
-
post: operations["ResultsController_createResult"];
|
|
44
|
-
};
|
|
45
39
|
"/results/notify/{uploadId}": {
|
|
46
40
|
post: operations["ResultsController_notifyTestRunComplete"];
|
|
47
41
|
};
|
|
42
|
+
"/results/{uploadId}/report": {
|
|
43
|
+
get: operations["ResultsController_downloadReport"];
|
|
44
|
+
};
|
|
45
|
+
"/results/{uploadId}/html-report": {
|
|
46
|
+
get: operations["ResultsController_downloadHtmlReport"];
|
|
47
|
+
};
|
|
48
48
|
"/results/compatibility/data": {
|
|
49
49
|
get: operations["ResultsController_getCompatibilityData"];
|
|
50
50
|
};
|
|
51
|
+
"/allure/{uploadId}/download": {
|
|
52
|
+
/**
|
|
53
|
+
* Download Allure report as HTML
|
|
54
|
+
* @description Downloads a single-file Allure report as HTML containing all test results. Report is generated once and stored in Supabase Storage for subsequent downloads.
|
|
55
|
+
*/
|
|
56
|
+
get: operations["AllureController_downloadAllureReport"];
|
|
57
|
+
};
|
|
58
|
+
"/webhooks": {
|
|
59
|
+
get: operations["WebhooksController_getWebhook"];
|
|
60
|
+
post: operations["WebhooksController_setWebhook"];
|
|
61
|
+
delete: operations["WebhooksController_deleteWebhook"];
|
|
62
|
+
};
|
|
63
|
+
"/webhooks/regenerate-secret": {
|
|
64
|
+
post: operations["WebhooksController_regenerateWebhookSecret"];
|
|
65
|
+
};
|
|
66
|
+
"/webhooks/test": {
|
|
67
|
+
post: operations["WebhooksController_testWebhook"];
|
|
68
|
+
};
|
|
51
69
|
"/org/increase_credits": {
|
|
52
70
|
post: operations["OrgController_paddleTransactionCompleted"];
|
|
53
71
|
};
|
|
@@ -60,12 +78,6 @@ export interface paths {
|
|
|
60
78
|
"/org/accept-invite": {
|
|
61
79
|
post: operations["OrgController_acceptInvite"];
|
|
62
80
|
};
|
|
63
|
-
"/public/stats": {
|
|
64
|
-
get: operations["StatsController_getPublicStats"];
|
|
65
|
-
};
|
|
66
|
-
"/moropo/run-scheduled-jobs": {
|
|
67
|
-
get: operations["MoropoController_runScheduledJobs"];
|
|
68
|
-
};
|
|
69
81
|
"/frontend/check-domain-saml": {
|
|
70
82
|
post: operations["FrontendController_checkDomainSaml"];
|
|
71
83
|
};
|
|
@@ -137,7 +149,7 @@ export interface components {
|
|
|
137
149
|
apiUrl?: string;
|
|
138
150
|
appFile?: string;
|
|
139
151
|
/** @enum {string} */
|
|
140
|
-
iOSVersion?: "16" | "17" | "18";
|
|
152
|
+
iOSVersion?: "16" | "17" | "18" | "26";
|
|
141
153
|
/** @enum {string} */
|
|
142
154
|
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
155
|
platform?: string;
|
|
@@ -182,32 +194,6 @@ export interface components {
|
|
|
182
194
|
export type $defs = Record<string, never>;
|
|
183
195
|
export type external = Record<string, never>;
|
|
184
196
|
export interface operations {
|
|
185
|
-
AdminController_cleanupOldUploads: {
|
|
186
|
-
parameters: {
|
|
187
|
-
query?: {
|
|
188
|
-
/** @description If true, shows what would be deleted without actually deleting */
|
|
189
|
-
dryRun?: boolean;
|
|
190
|
-
};
|
|
191
|
-
};
|
|
192
|
-
responses: {
|
|
193
|
-
/** @description Cleanup of old uploads has been triggered. */
|
|
194
|
-
201: {
|
|
195
|
-
content: {
|
|
196
|
-
"application/json": {
|
|
197
|
-
message?: string;
|
|
198
|
-
deletedCount?: number;
|
|
199
|
-
uploads?: ({
|
|
200
|
-
id?: string;
|
|
201
|
-
uploadType?: string;
|
|
202
|
-
createdAt?: string;
|
|
203
|
-
lastUsedDate?: string | null;
|
|
204
|
-
})[];
|
|
205
|
-
dryRun?: boolean;
|
|
206
|
-
};
|
|
207
|
-
};
|
|
208
|
-
};
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
197
|
UploadsController_createBinary: {
|
|
212
198
|
parameters: {
|
|
213
199
|
header: {
|
|
@@ -445,19 +431,25 @@ export interface operations {
|
|
|
445
431
|
};
|
|
446
432
|
};
|
|
447
433
|
};
|
|
448
|
-
|
|
434
|
+
ResultsController_notifyTestRunComplete: {
|
|
449
435
|
parameters: {
|
|
450
436
|
header: {
|
|
451
437
|
"x-app-api-key": string;
|
|
452
438
|
};
|
|
439
|
+
path: {
|
|
440
|
+
uploadId: string;
|
|
441
|
+
};
|
|
453
442
|
};
|
|
454
443
|
responses: {
|
|
444
|
+
/** @description Send results summary email. */
|
|
455
445
|
201: {
|
|
456
|
-
content:
|
|
446
|
+
content: {
|
|
447
|
+
"application/json": string;
|
|
448
|
+
};
|
|
457
449
|
};
|
|
458
450
|
};
|
|
459
451
|
};
|
|
460
|
-
|
|
452
|
+
ResultsController_downloadReport: {
|
|
461
453
|
parameters: {
|
|
462
454
|
header: {
|
|
463
455
|
"x-app-api-key": string;
|
|
@@ -467,8 +459,26 @@ export interface operations {
|
|
|
467
459
|
};
|
|
468
460
|
};
|
|
469
461
|
responses: {
|
|
470
|
-
/** @description
|
|
471
|
-
|
|
462
|
+
/** @description Download combined JUNIT test report (report.xml) for the upload */
|
|
463
|
+
200: {
|
|
464
|
+
content: {
|
|
465
|
+
"application/json": string;
|
|
466
|
+
};
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
ResultsController_downloadHtmlReport: {
|
|
471
|
+
parameters: {
|
|
472
|
+
header: {
|
|
473
|
+
"x-app-api-key": string;
|
|
474
|
+
};
|
|
475
|
+
path: {
|
|
476
|
+
uploadId: string;
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
responses: {
|
|
480
|
+
/** @description Download combined HTML test report (report.html) for the upload */
|
|
481
|
+
200: {
|
|
472
482
|
content: {
|
|
473
483
|
"application/json": string;
|
|
474
484
|
};
|
|
@@ -497,6 +507,141 @@ export interface operations {
|
|
|
497
507
|
};
|
|
498
508
|
};
|
|
499
509
|
};
|
|
510
|
+
/**
|
|
511
|
+
* Download Allure report as HTML
|
|
512
|
+
* @description Downloads a single-file Allure report as HTML containing all test results. Report is generated once and stored in Supabase Storage for subsequent downloads.
|
|
513
|
+
*/
|
|
514
|
+
AllureController_downloadAllureReport: {
|
|
515
|
+
parameters: {
|
|
516
|
+
header: {
|
|
517
|
+
"x-app-api-key": string;
|
|
518
|
+
};
|
|
519
|
+
path: {
|
|
520
|
+
/** @description The upload ID to generate Allure report for */
|
|
521
|
+
uploadId: string;
|
|
522
|
+
};
|
|
523
|
+
};
|
|
524
|
+
responses: {
|
|
525
|
+
/** @description Allure report HTML file download */
|
|
526
|
+
200: {
|
|
527
|
+
content: {
|
|
528
|
+
"text/html": string;
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
/** @description Upload not found or no results available */
|
|
532
|
+
404: {
|
|
533
|
+
content: never;
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
WebhooksController_getWebhook: {
|
|
538
|
+
parameters: {
|
|
539
|
+
query?: {
|
|
540
|
+
/** @description Set to true to return full secret instead of masked version */
|
|
541
|
+
show_secret?: boolean;
|
|
542
|
+
};
|
|
543
|
+
header: {
|
|
544
|
+
"x-app-api-key": string;
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
responses: {
|
|
548
|
+
/** @description Current webhook configuration */
|
|
549
|
+
200: {
|
|
550
|
+
content: {
|
|
551
|
+
"application/json": {
|
|
552
|
+
webhook_url?: string;
|
|
553
|
+
/** @description Full secret (only when show_secret=true) */
|
|
554
|
+
secret_key?: string;
|
|
555
|
+
/** @description Masked secret (default) */
|
|
556
|
+
secret_key_masked?: string;
|
|
557
|
+
/** Format: date-time */
|
|
558
|
+
created_at?: string;
|
|
559
|
+
/** Format: date-time */
|
|
560
|
+
updated_at?: string;
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
};
|
|
566
|
+
WebhooksController_setWebhook: {
|
|
567
|
+
parameters: {
|
|
568
|
+
header: {
|
|
569
|
+
"x-app-api-key": string;
|
|
570
|
+
};
|
|
571
|
+
};
|
|
572
|
+
requestBody: {
|
|
573
|
+
content: {
|
|
574
|
+
"application/json": {
|
|
575
|
+
/**
|
|
576
|
+
* Format: uri
|
|
577
|
+
* @example https://api.example.com/webhook
|
|
578
|
+
*/
|
|
579
|
+
url: string;
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
};
|
|
583
|
+
responses: {
|
|
584
|
+
/** @description Webhook URL set successfully */
|
|
585
|
+
201: {
|
|
586
|
+
content: {
|
|
587
|
+
"application/json": Record<string, never>;
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
WebhooksController_deleteWebhook: {
|
|
593
|
+
parameters: {
|
|
594
|
+
header: {
|
|
595
|
+
"x-app-api-key": string;
|
|
596
|
+
};
|
|
597
|
+
};
|
|
598
|
+
responses: {
|
|
599
|
+
/** @description Webhook configuration deleted successfully */
|
|
600
|
+
200: {
|
|
601
|
+
content: {
|
|
602
|
+
"application/json": Record<string, never>;
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
};
|
|
606
|
+
};
|
|
607
|
+
WebhooksController_regenerateWebhookSecret: {
|
|
608
|
+
parameters: {
|
|
609
|
+
header: {
|
|
610
|
+
"x-app-api-key": string;
|
|
611
|
+
};
|
|
612
|
+
};
|
|
613
|
+
responses: {
|
|
614
|
+
/** @description Webhook secret regenerated successfully */
|
|
615
|
+
201: {
|
|
616
|
+
content: {
|
|
617
|
+
"application/json": Record<string, never>;
|
|
618
|
+
};
|
|
619
|
+
};
|
|
620
|
+
};
|
|
621
|
+
};
|
|
622
|
+
WebhooksController_testWebhook: {
|
|
623
|
+
parameters: {
|
|
624
|
+
header: {
|
|
625
|
+
"x-app-api-key": string;
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
requestBody: {
|
|
629
|
+
content: {
|
|
630
|
+
"application/json": {
|
|
631
|
+
/** Format: uri */
|
|
632
|
+
url?: string;
|
|
633
|
+
};
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
responses: {
|
|
637
|
+
/** @description Test webhook sent successfully */
|
|
638
|
+
201: {
|
|
639
|
+
content: {
|
|
640
|
+
"application/json": Record<string, never>;
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
};
|
|
644
|
+
};
|
|
500
645
|
OrgController_paddleTransactionCompleted: {
|
|
501
646
|
parameters: {
|
|
502
647
|
header: {
|
|
@@ -583,41 +728,6 @@ export interface operations {
|
|
|
583
728
|
};
|
|
584
729
|
};
|
|
585
730
|
};
|
|
586
|
-
StatsController_getPublicStats: {
|
|
587
|
-
responses: {
|
|
588
|
-
200: {
|
|
589
|
-
content: never;
|
|
590
|
-
};
|
|
591
|
-
};
|
|
592
|
-
};
|
|
593
|
-
StatsController_sendDailyReport: {
|
|
594
|
-
responses: {
|
|
595
|
-
201: {
|
|
596
|
-
content: never;
|
|
597
|
-
};
|
|
598
|
-
};
|
|
599
|
-
};
|
|
600
|
-
StatsController_reportSlowTestsDev: {
|
|
601
|
-
responses: {
|
|
602
|
-
201: {
|
|
603
|
-
content: never;
|
|
604
|
-
};
|
|
605
|
-
};
|
|
606
|
-
};
|
|
607
|
-
StatsController_reportSlowTestsProd: {
|
|
608
|
-
responses: {
|
|
609
|
-
201: {
|
|
610
|
-
content: never;
|
|
611
|
-
};
|
|
612
|
-
};
|
|
613
|
-
};
|
|
614
|
-
MoropoController_runScheduledJobs: {
|
|
615
|
-
responses: {
|
|
616
|
-
200: {
|
|
617
|
-
content: never;
|
|
618
|
-
};
|
|
619
|
-
};
|
|
620
|
-
};
|
|
621
731
|
FrontendController_checkDomainSaml: {
|
|
622
732
|
/** @description Domain to check for SAML configuration */
|
|
623
733
|
requestBody: {
|
package/oclif.manifest.json
CHANGED
|
@@ -25,22 +25,6 @@
|
|
|
25
25
|
"allowNo": false,
|
|
26
26
|
"type": "boolean"
|
|
27
27
|
},
|
|
28
|
-
"additional-app-binary-ids": {
|
|
29
|
-
"description": "The ID of the additional app binary(s) previously uploaded to devicecloud.dev to install before execution",
|
|
30
|
-
"name": "additional-app-binary-ids",
|
|
31
|
-
"default": [],
|
|
32
|
-
"hasDynamicHelp": false,
|
|
33
|
-
"multiple": true,
|
|
34
|
-
"type": "option"
|
|
35
|
-
},
|
|
36
|
-
"additional-app-files": {
|
|
37
|
-
"description": "Additional app binary(s) to install before execution",
|
|
38
|
-
"name": "additional-app-files",
|
|
39
|
-
"default": [],
|
|
40
|
-
"hasDynamicHelp": false,
|
|
41
|
-
"multiple": true,
|
|
42
|
-
"type": "option"
|
|
43
|
-
},
|
|
44
28
|
"android-api-level": {
|
|
45
29
|
"description": "[Android only] Android API level to run your flow against",
|
|
46
30
|
"name": "android-api-level",
|
|
@@ -134,6 +118,26 @@
|
|
|
134
118
|
"multiple": false,
|
|
135
119
|
"type": "option"
|
|
136
120
|
},
|
|
121
|
+
"allure-path": {
|
|
122
|
+
"dependsOn": [
|
|
123
|
+
"report"
|
|
124
|
+
],
|
|
125
|
+
"description": "Custom file path for downloaded Allure report (default: ./report.html)",
|
|
126
|
+
"name": "allure-path",
|
|
127
|
+
"hasDynamicHelp": false,
|
|
128
|
+
"multiple": false,
|
|
129
|
+
"type": "option"
|
|
130
|
+
},
|
|
131
|
+
"html-path": {
|
|
132
|
+
"dependsOn": [
|
|
133
|
+
"report"
|
|
134
|
+
],
|
|
135
|
+
"description": "Custom file path for downloaded HTML report (default: ./report.html)",
|
|
136
|
+
"name": "html-path",
|
|
137
|
+
"hasDynamicHelp": false,
|
|
138
|
+
"multiple": false,
|
|
139
|
+
"type": "option"
|
|
140
|
+
},
|
|
137
141
|
"async": {
|
|
138
142
|
"description": "Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)",
|
|
139
143
|
"name": "async",
|
|
@@ -263,7 +267,8 @@
|
|
|
263
267
|
"options": [
|
|
264
268
|
"18",
|
|
265
269
|
"17",
|
|
266
|
-
"16"
|
|
270
|
+
"16",
|
|
271
|
+
"26"
|
|
267
272
|
],
|
|
268
273
|
"type": "option"
|
|
269
274
|
},
|
|
@@ -318,6 +323,7 @@
|
|
|
318
323
|
},
|
|
319
324
|
"mitmHost": {
|
|
320
325
|
"description": "used for mitmproxy support, enterprise only, contact support if interested",
|
|
326
|
+
"hidden": true,
|
|
321
327
|
"name": "mitmHost",
|
|
322
328
|
"hasDynamicHelp": false,
|
|
323
329
|
"multiple": false,
|
|
@@ -328,6 +334,7 @@
|
|
|
328
334
|
"mitmHost"
|
|
329
335
|
],
|
|
330
336
|
"description": "used for mitmproxy support, enterprise only, contact support if interested",
|
|
337
|
+
"hidden": true,
|
|
331
338
|
"name": "mitmPath",
|
|
332
339
|
"hasDynamicHelp": false,
|
|
333
340
|
"multiple": false,
|
|
@@ -355,9 +362,7 @@
|
|
|
355
362
|
"multiple": false,
|
|
356
363
|
"options": [
|
|
357
364
|
"0",
|
|
358
|
-
"90"
|
|
359
|
-
"180",
|
|
360
|
-
"270"
|
|
365
|
+
"90"
|
|
361
366
|
],
|
|
362
367
|
"type": "option"
|
|
363
368
|
},
|
|
@@ -372,11 +377,12 @@
|
|
|
372
377
|
"aliases": [
|
|
373
378
|
"format"
|
|
374
379
|
],
|
|
375
|
-
"description": "
|
|
380
|
+
"description": "Generate and download test reports in the specified format. Use \"allure\" for a complete HTML report.",
|
|
376
381
|
"name": "report",
|
|
377
382
|
"hasDynamicHelp": false,
|
|
378
383
|
"multiple": false,
|
|
379
384
|
"options": [
|
|
385
|
+
"allure",
|
|
380
386
|
"junit",
|
|
381
387
|
"html"
|
|
382
388
|
],
|
|
@@ -414,12 +420,6 @@
|
|
|
414
420
|
"name": "skip-chrome-onboarding",
|
|
415
421
|
"allowNo": false,
|
|
416
422
|
"type": "boolean"
|
|
417
|
-
},
|
|
418
|
-
"x86-arch": {
|
|
419
|
-
"description": "[iOS only, experimental] Run your flow against x86 architecture simulator instead of arm64",
|
|
420
|
-
"name": "x86-arch",
|
|
421
|
-
"allowNo": false,
|
|
422
|
-
"type": "boolean"
|
|
423
423
|
}
|
|
424
424
|
},
|
|
425
425
|
"hasDynamicHelp": false,
|
|
@@ -579,5 +579,5 @@
|
|
|
579
579
|
]
|
|
580
580
|
}
|
|
581
581
|
},
|
|
582
|
-
"version": "4.
|
|
582
|
+
"version": "4.1.1"
|
|
583
583
|
}
|
package/package.json
CHANGED
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"type": "git",
|
|
73
73
|
"url": "https://devicecloud.dev"
|
|
74
74
|
},
|
|
75
|
-
"version": "4.
|
|
75
|
+
"version": "4.1.1",
|
|
76
76
|
"bugs": {
|
|
77
77
|
"url": "https://discord.gg/gm3mJwcNw8"
|
|
78
78
|
},
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"dcd": "./bin/dev.js",
|
|
90
90
|
"prod": "./bin/run.js",
|
|
91
91
|
"build": "shx rm -rf dist && tsc -b",
|
|
92
|
-
"lint": "
|
|
92
|
+
"lint": "eslint . --ext .ts",
|
|
93
93
|
"version": "oclif readme && git add README.md",
|
|
94
94
|
"test": "node scripts/test-runner.mjs"
|
|
95
95
|
}
|