@devicecloud.dev/dcd 4.1.3 → 4.1.5
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 +30 -0
- package/dist/commands/cloud.js +67 -3
- package/dist/config/flags/execution.flags.js +2 -2
- package/dist/gateways/api-gateway.d.ts +2 -2
- package/dist/gateways/api-gateway.js +1 -1
- package/dist/services/device-validation.service.d.ts +2 -0
- package/dist/services/device-validation.service.js +2 -0
- package/dist/services/execution-plan.service.d.ts +32 -1
- package/dist/services/execution-plan.service.js +43 -1
- package/dist/services/moropo.service.d.ts +5 -0
- package/dist/services/moropo.service.js +60 -55
- package/dist/services/report-download.service.d.ts +3 -0
- package/dist/services/report-download.service.js +3 -0
- package/dist/services/results-polling.service.d.ts +6 -0
- package/dist/services/results-polling.service.js +131 -117
- package/dist/services/test-submission.service.d.ts +6 -0
- package/dist/services/test-submission.service.js +56 -50
- package/dist/services/version.service.d.ts +3 -2
- package/dist/services/version.service.js +3 -2
- package/dist/types/generated/schema.types.d.ts +1086 -348
- package/oclif.manifest.json +4 -3
- package/package.json +1 -1
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* Primary CLI command for executing tests on DeviceCloud.
|
|
4
|
+
* Orchestrates the complete test workflow:
|
|
5
|
+
* - Binary upload with SHA deduplication
|
|
6
|
+
* - Flow file analysis and dependency resolution
|
|
7
|
+
* - Device compatibility validation
|
|
8
|
+
* - Test submission with parallel execution
|
|
9
|
+
* - Real-time result polling with 5-second intervals
|
|
10
|
+
* - Artifact download (reports, videos, logs)
|
|
11
|
+
*
|
|
12
|
+
* Replaces `maestro cloud` with DeviceCloud-specific functionality.
|
|
13
|
+
*/
|
|
2
14
|
export default class Cloud extends Command {
|
|
3
15
|
static args: {
|
|
4
16
|
firstFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -50,12 +62,30 @@ export default class Cloud extends Command {
|
|
|
50
62
|
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
51
63
|
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
52
64
|
};
|
|
65
|
+
/** Service for device/OS compatibility validation */
|
|
53
66
|
private deviceValidationService;
|
|
67
|
+
/** Service for Moropo test framework integration */
|
|
54
68
|
private moropoService;
|
|
69
|
+
/** Service for downloading test reports and artifacts */
|
|
55
70
|
private reportDownloadService;
|
|
71
|
+
/** Service for polling test results with 5-second intervals */
|
|
56
72
|
private resultsPollingService;
|
|
73
|
+
/** Service for submitting tests to the API */
|
|
57
74
|
private testSubmissionService;
|
|
75
|
+
/**
|
|
76
|
+
* Check for CLI updates and notify user if outdated
|
|
77
|
+
* Compares current version with latest npm release
|
|
78
|
+
* @returns Promise that resolves when version check is complete
|
|
79
|
+
*/
|
|
58
80
|
private versionCheck;
|
|
81
|
+
/** Service for CLI version checking */
|
|
59
82
|
private versionService;
|
|
83
|
+
/**
|
|
84
|
+
* Main command execution entry point
|
|
85
|
+
* Orchestrates the complete test submission and monitoring workflow
|
|
86
|
+
* @returns Promise that resolves when command execution is complete
|
|
87
|
+
* @throws RunFailedError if tests fail
|
|
88
|
+
* @throws Error for infrastructure or configuration errors
|
|
89
|
+
*/
|
|
60
90
|
run(): Promise<any>;
|
|
61
91
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -23,6 +23,18 @@ process.on('warning', (warning) => {
|
|
|
23
23
|
// Ignore punycode deprecation warnings
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
|
+
/**
|
|
27
|
+
* Primary CLI command for executing tests on DeviceCloud.
|
|
28
|
+
* Orchestrates the complete test workflow:
|
|
29
|
+
* - Binary upload with SHA deduplication
|
|
30
|
+
* - Flow file analysis and dependency resolution
|
|
31
|
+
* - Device compatibility validation
|
|
32
|
+
* - Test submission with parallel execution
|
|
33
|
+
* - Real-time result polling with 5-second intervals
|
|
34
|
+
* - Artifact download (reports, videos, logs)
|
|
35
|
+
*
|
|
36
|
+
* Replaces `maestro cloud` with DeviceCloud-specific functionality.
|
|
37
|
+
*/
|
|
26
38
|
class Cloud extends core_1.Command {
|
|
27
39
|
static args = {
|
|
28
40
|
firstFile: core_1.Args.string({
|
|
@@ -40,11 +52,21 @@ class Cloud extends core_1.Command {
|
|
|
40
52
|
static enableJsonFlag = true;
|
|
41
53
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
42
54
|
static flags = constants_1.flags;
|
|
55
|
+
/** Service for device/OS compatibility validation */
|
|
43
56
|
deviceValidationService = new device_validation_service_1.DeviceValidationService();
|
|
57
|
+
/** Service for Moropo test framework integration */
|
|
44
58
|
moropoService = new moropo_service_1.MoropoService();
|
|
59
|
+
/** Service for downloading test reports and artifacts */
|
|
45
60
|
reportDownloadService = new report_download_service_1.ReportDownloadService();
|
|
61
|
+
/** Service for polling test results with 5-second intervals */
|
|
46
62
|
resultsPollingService = new results_polling_service_1.ResultsPollingService();
|
|
63
|
+
/** Service for submitting tests to the API */
|
|
47
64
|
testSubmissionService = new test_submission_service_1.TestSubmissionService();
|
|
65
|
+
/**
|
|
66
|
+
* Check for CLI updates and notify user if outdated
|
|
67
|
+
* Compares current version with latest npm release
|
|
68
|
+
* @returns Promise that resolves when version check is complete
|
|
69
|
+
*/
|
|
48
70
|
versionCheck = async () => {
|
|
49
71
|
const latestVersion = await this.versionService.checkLatestCliVersion();
|
|
50
72
|
if (latestVersion &&
|
|
@@ -57,7 +79,15 @@ class Cloud extends core_1.Command {
|
|
|
57
79
|
`);
|
|
58
80
|
}
|
|
59
81
|
};
|
|
82
|
+
/** Service for CLI version checking */
|
|
60
83
|
versionService = new version_service_1.VersionService();
|
|
84
|
+
/**
|
|
85
|
+
* Main command execution entry point
|
|
86
|
+
* Orchestrates the complete test submission and monitoring workflow
|
|
87
|
+
* @returns Promise that resolves when command execution is complete
|
|
88
|
+
* @throws RunFailedError if tests fail
|
|
89
|
+
* @throws Error for infrastructure or configuration errors
|
|
90
|
+
*/
|
|
61
91
|
async run() {
|
|
62
92
|
let output = null;
|
|
63
93
|
// Store debug flag outside try/catch to access it in catch block
|
|
@@ -186,7 +216,14 @@ class Cloud extends core_1.Command {
|
|
|
186
216
|
if (debug) {
|
|
187
217
|
this.log('DEBUG: Generating execution plan...');
|
|
188
218
|
}
|
|
189
|
-
executionPlan = await (0, execution_plan_service_1.plan)(
|
|
219
|
+
executionPlan = await (0, execution_plan_service_1.plan)({
|
|
220
|
+
input: flowFile,
|
|
221
|
+
includeTags: includeTags.flat(),
|
|
222
|
+
excludeTags: excludeTags.flat(),
|
|
223
|
+
excludeFlows: excludeFlows.flat(),
|
|
224
|
+
configFile,
|
|
225
|
+
debug,
|
|
226
|
+
});
|
|
190
227
|
if (debug) {
|
|
191
228
|
this.log(`DEBUG: Execution plan generated`);
|
|
192
229
|
this.log(`DEBUG: Total flow files: ${executionPlan.totalFlowFiles}`);
|
|
@@ -395,7 +432,7 @@ class Cloud extends core_1.Command {
|
|
|
395
432
|
quiet,
|
|
396
433
|
uploadId: results[0].test_upload_id,
|
|
397
434
|
})
|
|
398
|
-
.catch((error) => {
|
|
435
|
+
.catch(async (error) => {
|
|
399
436
|
if (error instanceof results_polling_service_1.RunFailedError) {
|
|
400
437
|
// Handle failed test run
|
|
401
438
|
const jsonOutput = error.result;
|
|
@@ -406,11 +443,38 @@ class Cloud extends core_1.Command {
|
|
|
406
443
|
if (json) {
|
|
407
444
|
output = jsonOutput;
|
|
408
445
|
}
|
|
446
|
+
// Download artifacts and reports even when tests fail
|
|
447
|
+
if (downloadArtifacts) {
|
|
448
|
+
await this.reportDownloadService.downloadArtifacts({
|
|
449
|
+
apiKey,
|
|
450
|
+
apiUrl,
|
|
451
|
+
artifactsPath,
|
|
452
|
+
debug,
|
|
453
|
+
downloadType: downloadArtifacts,
|
|
454
|
+
logger: this.log.bind(this),
|
|
455
|
+
uploadId: results[0].test_upload_id,
|
|
456
|
+
warnLogger: this.warn.bind(this),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (report && ['allure', 'html', 'junit'].includes(report)) {
|
|
460
|
+
await this.reportDownloadService.downloadReports({
|
|
461
|
+
allurePath,
|
|
462
|
+
apiKey,
|
|
463
|
+
apiUrl,
|
|
464
|
+
debug,
|
|
465
|
+
htmlPath,
|
|
466
|
+
junitPath,
|
|
467
|
+
logger: this.log.bind(this),
|
|
468
|
+
reportType: report,
|
|
469
|
+
uploadId: results[0].test_upload_id,
|
|
470
|
+
warnLogger: this.warn.bind(this),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
409
473
|
throw new Error('RUN_FAILED');
|
|
410
474
|
}
|
|
411
475
|
throw error;
|
|
412
476
|
});
|
|
413
|
-
// Handle successful completion
|
|
477
|
+
// Handle successful completion - download artifacts and reports
|
|
414
478
|
if (downloadArtifacts) {
|
|
415
479
|
await this.reportDownloadService.downloadArtifacts({
|
|
416
480
|
apiKey,
|
|
@@ -44,7 +44,7 @@ exports.executionFlags = {
|
|
|
44
44
|
}),
|
|
45
45
|
'runner-type': core_1.Flags.string({
|
|
46
46
|
default: 'default',
|
|
47
|
-
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
|
|
48
|
-
options: ['default', 'm4', 'm1', 'gpu1'],
|
|
47
|
+
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 and cpu1 are Android-only and require contacting support to enable, otherwise reverts to default.',
|
|
48
|
+
options: ['default', 'm4', 'm1', 'gpu1', 'cpu1'],
|
|
49
49
|
}),
|
|
50
50
|
};
|
|
@@ -14,14 +14,14 @@ export declare const ApiGateway: {
|
|
|
14
14
|
downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
|
|
15
15
|
finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
|
|
16
16
|
getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
|
|
17
|
-
id: string;
|
|
18
17
|
message: string;
|
|
19
18
|
path: string;
|
|
20
19
|
token: string;
|
|
20
|
+
id: string;
|
|
21
21
|
}>;
|
|
22
22
|
getResultsForUpload(baseUrl: string, apiKey: string, uploadId: string): Promise<{
|
|
23
|
-
results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
|
|
24
23
|
statusCode?: number;
|
|
24
|
+
results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
|
|
25
25
|
}>;
|
|
26
26
|
getUploadStatus(baseUrl: string, apiKey: string, options: {
|
|
27
27
|
name?: string;
|
|
@@ -95,7 +95,7 @@ exports.ApiGateway = {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
const fileStream = createWriteStream(artifactsPath, { flags: '
|
|
98
|
+
const fileStream = createWriteStream(artifactsPath, { flags: 'w' });
|
|
99
99
|
await finished(Readable.fromWeb(res.body).pipe(fileStream));
|
|
100
100
|
},
|
|
101
101
|
// eslint-disable-next-line max-params
|
|
@@ -14,6 +14,7 @@ export declare class DeviceValidationService {
|
|
|
14
14
|
* @param googlePlay Whether Google Play services are enabled
|
|
15
15
|
* @param compatibilityData Compatibility data from API
|
|
16
16
|
* @param options Validation options
|
|
17
|
+
* @returns void
|
|
17
18
|
* @throws Error if device/API level combination is not supported
|
|
18
19
|
*/
|
|
19
20
|
validateAndroidDevice(androidApiLevel: string | undefined, androidDevice: string | undefined, googlePlay: boolean, compatibilityData: CompatibilityData, options?: DeviceValidationOptions): void;
|
|
@@ -23,6 +24,7 @@ export declare class DeviceValidationService {
|
|
|
23
24
|
* @param iOSDevice iOS device model to validate
|
|
24
25
|
* @param compatibilityData Compatibility data from API
|
|
25
26
|
* @param options Validation options
|
|
27
|
+
* @returns void
|
|
26
28
|
* @throws Error if device/version combination is not supported
|
|
27
29
|
*/
|
|
28
30
|
validateiOSDevice(iOSVersion: string | undefined, iOSDevice: string | undefined, compatibilityData: CompatibilityData, options?: DeviceValidationOptions): void;
|
|
@@ -12,6 +12,7 @@ class DeviceValidationService {
|
|
|
12
12
|
* @param googlePlay Whether Google Play services are enabled
|
|
13
13
|
* @param compatibilityData Compatibility data from API
|
|
14
14
|
* @param options Validation options
|
|
15
|
+
* @returns void
|
|
15
16
|
* @throws Error if device/API level combination is not supported
|
|
16
17
|
*/
|
|
17
18
|
validateAndroidDevice(androidApiLevel, androidDevice, googlePlay, compatibilityData, options = {}) {
|
|
@@ -45,6 +46,7 @@ class DeviceValidationService {
|
|
|
45
46
|
* @param iOSDevice iOS device model to validate
|
|
46
47
|
* @param compatibilityData Compatibility data from API
|
|
47
48
|
* @param options Validation options
|
|
49
|
+
* @returns void
|
|
48
50
|
* @throws Error if device/version combination is not supported
|
|
49
51
|
*/
|
|
50
52
|
validateiOSDevice(iOSVersion, iOSDevice, compatibilityData, options = {}) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Email notification configuration */
|
|
1
2
|
interface INotificationsConfig {
|
|
2
3
|
email?: {
|
|
3
4
|
enabled?: boolean;
|
|
@@ -5,6 +6,7 @@ interface INotificationsConfig {
|
|
|
5
6
|
recipients?: string[];
|
|
6
7
|
};
|
|
7
8
|
}
|
|
9
|
+
/** Workspace configuration from config.yaml */
|
|
8
10
|
interface IWorkspaceConfig {
|
|
9
11
|
excludeTags?: null | string[];
|
|
10
12
|
executionOrder?: IExecutionOrder | null;
|
|
@@ -13,13 +15,25 @@ interface IWorkspaceConfig {
|
|
|
13
15
|
local?: ILocal | null;
|
|
14
16
|
notifications?: INotificationsConfig;
|
|
15
17
|
}
|
|
18
|
+
/** Local execution configuration */
|
|
16
19
|
interface ILocal {
|
|
17
20
|
deterministicOrder: boolean | null;
|
|
18
21
|
}
|
|
22
|
+
/** Sequential execution configuration */
|
|
19
23
|
interface IExecutionOrder {
|
|
20
24
|
continueOnFailure: boolean;
|
|
21
25
|
flowsOrder: string[];
|
|
22
26
|
}
|
|
27
|
+
/** Options for execution plan generation */
|
|
28
|
+
export interface PlanOptions {
|
|
29
|
+
configFile?: string;
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
excludeFlows?: string[];
|
|
32
|
+
excludeTags?: string[];
|
|
33
|
+
includeTags?: string[];
|
|
34
|
+
input: string;
|
|
35
|
+
}
|
|
36
|
+
/** Execution plan containing all flows to run with metadata and dependencies */
|
|
23
37
|
export interface IExecutionPlan {
|
|
24
38
|
allExcludeTags?: null | string[];
|
|
25
39
|
allIncludeTags?: null | string[];
|
|
@@ -31,9 +45,26 @@ export interface IExecutionPlan {
|
|
|
31
45
|
totalFlowFiles: number;
|
|
32
46
|
workspaceConfig?: IWorkspaceConfig;
|
|
33
47
|
}
|
|
48
|
+
/** Flow sequence configuration for ordered execution */
|
|
34
49
|
interface IFlowSequence {
|
|
35
50
|
continueOnFailure?: boolean;
|
|
36
51
|
flows: string[];
|
|
37
52
|
}
|
|
38
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Generate execution plan for test flows
|
|
55
|
+
*
|
|
56
|
+
* Handles:
|
|
57
|
+
* - Single file or directory input
|
|
58
|
+
* - Workspace configuration (config.yaml)
|
|
59
|
+
* - Flow inclusion/exclusion patterns
|
|
60
|
+
* - Tag-based filtering (include/exclude)
|
|
61
|
+
* - Dependency resolution (runFlow, scripts, media)
|
|
62
|
+
* - Sequential execution ordering
|
|
63
|
+
* - DeviceCloud-specific overrides
|
|
64
|
+
*
|
|
65
|
+
* @param options - Plan generation options
|
|
66
|
+
* @returns Complete execution plan with flows, dependencies, and metadata
|
|
67
|
+
* @throws Error if input path doesn't exist, no flows found, or dependencies missing
|
|
68
|
+
*/
|
|
69
|
+
export declare function plan(options: PlanOptions): Promise<IExecutionPlan>;
|
|
39
70
|
export {};
|
|
@@ -5,6 +5,13 @@ const glob_1 = require("glob");
|
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const execution_plan_utils_1 = require("./execution-plan.utils");
|
|
8
|
+
/**
|
|
9
|
+
* Recursively check and resolve all dependencies for a flow file
|
|
10
|
+
* Includes runFlow references, JavaScript scripts, and media files
|
|
11
|
+
* @param input - Path to flow file to check
|
|
12
|
+
* @returns Array of all dependency file paths (deduplicated)
|
|
13
|
+
* @throws Error if any referenced files are missing
|
|
14
|
+
*/
|
|
8
15
|
async function checkDependencies(input) {
|
|
9
16
|
const checkedDependencies = [];
|
|
10
17
|
const uncheckedDependencies = [input];
|
|
@@ -35,12 +42,24 @@ async function checkDependencies(input) {
|
|
|
35
42
|
}
|
|
36
43
|
return checkedDependencies;
|
|
37
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Filter flow files based on exclude patterns
|
|
47
|
+
* @param unfilteredFlowFiles - All discovered flow files
|
|
48
|
+
* @param excludeFlows - Patterns to exclude
|
|
49
|
+
* @returns Filtered array of flow file paths
|
|
50
|
+
*/
|
|
38
51
|
function filterFlowFiles(unfilteredFlowFiles, excludeFlows) {
|
|
39
52
|
if (excludeFlows) {
|
|
40
53
|
return unfilteredFlowFiles.filter((file) => !excludeFlows.some((flow) => path.normalize(file).startsWith(path.normalize(path.resolve(flow)))));
|
|
41
54
|
}
|
|
42
55
|
return unfilteredFlowFiles;
|
|
43
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Load workspace configuration from config.yaml/yml if present
|
|
59
|
+
* @param input - Input directory path
|
|
60
|
+
* @param unfilteredFlowFiles - List of discovered flow files
|
|
61
|
+
* @returns Workspace configuration object (empty if no config file found)
|
|
62
|
+
*/
|
|
44
63
|
function getWorkspaceConfig(input, unfilteredFlowFiles) {
|
|
45
64
|
const possibleConfigPaths = new Set([path.join(input, 'config.yaml'), path.join(input, 'config.yml')].map((p) => path.normalize(p)));
|
|
46
65
|
const configFilePath = unfilteredFlowFiles.find((file) => possibleConfigPaths.has(path.normalize(file)));
|
|
@@ -49,6 +68,12 @@ function getWorkspaceConfig(input, unfilteredFlowFiles) {
|
|
|
49
68
|
: {};
|
|
50
69
|
return config;
|
|
51
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Extract DeviceCloud-specific override environment variables
|
|
73
|
+
* Looks for env vars prefixed with DEVICECLOUD_OVERRIDE_
|
|
74
|
+
* @param config - Flow configuration object
|
|
75
|
+
* @returns Object containing override key-value pairs
|
|
76
|
+
*/
|
|
52
77
|
function extractDeviceCloudOverrides(config) {
|
|
53
78
|
if (!config || !config.env || typeof config.env !== 'object') {
|
|
54
79
|
return {};
|
|
@@ -64,7 +89,24 @@ function extractDeviceCloudOverrides(config) {
|
|
|
64
89
|
}
|
|
65
90
|
return overrides;
|
|
66
91
|
}
|
|
67
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Generate execution plan for test flows
|
|
94
|
+
*
|
|
95
|
+
* Handles:
|
|
96
|
+
* - Single file or directory input
|
|
97
|
+
* - Workspace configuration (config.yaml)
|
|
98
|
+
* - Flow inclusion/exclusion patterns
|
|
99
|
+
* - Tag-based filtering (include/exclude)
|
|
100
|
+
* - Dependency resolution (runFlow, scripts, media)
|
|
101
|
+
* - Sequential execution ordering
|
|
102
|
+
* - DeviceCloud-specific overrides
|
|
103
|
+
*
|
|
104
|
+
* @param options - Plan generation options
|
|
105
|
+
* @returns Complete execution plan with flows, dependencies, and metadata
|
|
106
|
+
* @throws Error if input path doesn't exist, no flows found, or dependencies missing
|
|
107
|
+
*/
|
|
108
|
+
async function plan(options) {
|
|
109
|
+
const { input, includeTags = [], excludeTags = [], excludeFlows, configFile, debug = false, } = options;
|
|
68
110
|
const normalizedInput = path.normalize(input);
|
|
69
111
|
const flowMetadata = {};
|
|
70
112
|
if (!fs.existsSync(normalizedInput)) {
|
|
@@ -17,4 +17,9 @@ export declare class MoropoService {
|
|
|
17
17
|
* @returns Path to the extracted Moropo tests directory
|
|
18
18
|
*/
|
|
19
19
|
downloadAndExtract(options: MoropoDownloadOptions): Promise<string>;
|
|
20
|
+
private createConfigFile;
|
|
21
|
+
private downloadZipFile;
|
|
22
|
+
private extractZipFile;
|
|
23
|
+
private logDebug;
|
|
24
|
+
private showProgress;
|
|
20
25
|
}
|
|
@@ -18,10 +18,8 @@ class MoropoService {
|
|
|
18
18
|
*/
|
|
19
19
|
async downloadAndExtract(options) {
|
|
20
20
|
const { apiKey, branchName = 'main', debug = false, quiet = false, json = false, logger } = options;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
logger(`DEBUG: Using branch name: ${branchName}`);
|
|
24
|
-
}
|
|
21
|
+
this.logDebug(debug, logger, 'DEBUG: Moropo v1 API key detected, downloading tests from Moropo API');
|
|
22
|
+
this.logDebug(debug, logger, `DEBUG: Using branch name: ${branchName}`);
|
|
25
23
|
try {
|
|
26
24
|
if (!quiet && !json) {
|
|
27
25
|
core_1.ux.action.start('Downloading Moropo tests', 'Initializing', {
|
|
@@ -38,76 +36,83 @@ class MoropoService {
|
|
|
38
36
|
if (!response.ok) {
|
|
39
37
|
throw new Error(`Failed to download Moropo tests: ${response.statusText}`);
|
|
40
38
|
}
|
|
41
|
-
const contentLength = response.headers.get('content-length');
|
|
42
|
-
const totalSize = contentLength
|
|
43
|
-
? Number.parseInt(contentLength, 10)
|
|
44
|
-
: 0;
|
|
45
|
-
let downloadedSize = 0;
|
|
46
39
|
const moropoDir = path.join(os.tmpdir(), `moropo-tests-${Date.now()}`);
|
|
47
|
-
|
|
48
|
-
logger(`DEBUG: Extracting Moropo tests to: ${moropoDir}`);
|
|
49
|
-
}
|
|
40
|
+
this.logDebug(debug, logger, `DEBUG: Extracting Moropo tests to: ${moropoDir}`);
|
|
50
41
|
// Create moropo directory if it doesn't exist
|
|
51
42
|
if (!fs.existsSync(moropoDir)) {
|
|
52
43
|
fs.mkdirSync(moropoDir, { recursive: true });
|
|
53
44
|
}
|
|
54
|
-
//
|
|
45
|
+
// Download zip file
|
|
55
46
|
const zipPath = path.join(moropoDir, 'moropo-tests.zip');
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!reader) {
|
|
59
|
-
throw new Error('Failed to get response reader');
|
|
60
|
-
}
|
|
61
|
-
let readerResult = await reader.read();
|
|
62
|
-
while (!readerResult.done) {
|
|
63
|
-
const { value } = readerResult;
|
|
64
|
-
downloadedSize += value.length;
|
|
65
|
-
if (!quiet && !json && totalSize) {
|
|
66
|
-
const progress = Math.round((downloadedSize / totalSize) * 100);
|
|
67
|
-
core_1.ux.action.status = `Downloading: ${progress}%`;
|
|
68
|
-
}
|
|
69
|
-
fileStream.write(value);
|
|
70
|
-
readerResult = await reader.read();
|
|
71
|
-
}
|
|
72
|
-
fileStream.end();
|
|
73
|
-
await new Promise((resolve) => {
|
|
74
|
-
fileStream.on('finish', () => {
|
|
75
|
-
resolve();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
if (!quiet && !json) {
|
|
79
|
-
core_1.ux.action.status = 'Extracting tests...';
|
|
80
|
-
}
|
|
47
|
+
await this.downloadZipFile(response, zipPath, { quiet, json });
|
|
48
|
+
this.showProgress(quiet, json, 'Extracting tests...');
|
|
81
49
|
// Extract zip file
|
|
82
|
-
|
|
83
|
-
const zip = new StreamZip.async({ file: zipPath });
|
|
84
|
-
await zip.extract(null, moropoDir);
|
|
85
|
-
await zip.close();
|
|
86
|
-
// Delete zip file after extraction
|
|
87
|
-
fs.unlinkSync(zipPath);
|
|
50
|
+
await this.extractZipFile(zipPath, moropoDir);
|
|
88
51
|
if (!quiet && !json) {
|
|
89
52
|
core_1.ux.action.stop('completed');
|
|
90
53
|
}
|
|
91
|
-
|
|
92
|
-
logger('DEBUG: Successfully extracted Moropo tests');
|
|
93
|
-
}
|
|
54
|
+
this.logDebug(debug, logger, 'DEBUG: Successfully extracted Moropo tests');
|
|
94
55
|
// Create config.yaml file
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (debug && logger) {
|
|
98
|
-
logger('DEBUG: Created config.yaml file');
|
|
99
|
-
}
|
|
56
|
+
this.createConfigFile(moropoDir);
|
|
57
|
+
this.logDebug(debug, logger, 'DEBUG: Created config.yaml file');
|
|
100
58
|
return moropoDir;
|
|
101
59
|
}
|
|
102
60
|
catch (error) {
|
|
103
61
|
if (!quiet && !json) {
|
|
104
62
|
core_1.ux.action.stop('failed');
|
|
105
63
|
}
|
|
106
|
-
|
|
107
|
-
logger(`DEBUG: Error downloading/extracting Moropo tests: ${error}`);
|
|
108
|
-
}
|
|
64
|
+
this.logDebug(debug, logger, `DEBUG: Error downloading/extracting Moropo tests: ${error}`);
|
|
109
65
|
throw new Error(`Failed to download/extract Moropo tests: ${error}`);
|
|
110
66
|
}
|
|
111
67
|
}
|
|
68
|
+
createConfigFile(moropoDir) {
|
|
69
|
+
const configPath = path.join(moropoDir, 'config.yaml');
|
|
70
|
+
fs.writeFileSync(configPath, 'flows:\n- ./**/*.yaml\n- ./*.yaml\n');
|
|
71
|
+
}
|
|
72
|
+
async downloadZipFile(response, zipPath, options) {
|
|
73
|
+
const { quiet, json } = options;
|
|
74
|
+
const contentLength = response.headers.get('content-length');
|
|
75
|
+
const totalSize = contentLength ? Number.parseInt(contentLength, 10) : 0;
|
|
76
|
+
let downloadedSize = 0;
|
|
77
|
+
const fileStream = fs.createWriteStream(zipPath);
|
|
78
|
+
const reader = response.body?.getReader();
|
|
79
|
+
if (!reader) {
|
|
80
|
+
throw new Error('Failed to get response reader');
|
|
81
|
+
}
|
|
82
|
+
let readerResult = await reader.read();
|
|
83
|
+
while (!readerResult.done) {
|
|
84
|
+
const { value } = readerResult;
|
|
85
|
+
downloadedSize += value.length;
|
|
86
|
+
if (!quiet && !json && totalSize) {
|
|
87
|
+
const progress = Math.round((downloadedSize / totalSize) * 100);
|
|
88
|
+
core_1.ux.action.status = `Downloading: ${progress}%`;
|
|
89
|
+
}
|
|
90
|
+
fileStream.write(value);
|
|
91
|
+
readerResult = await reader.read();
|
|
92
|
+
}
|
|
93
|
+
fileStream.end();
|
|
94
|
+
await new Promise((resolve) => {
|
|
95
|
+
fileStream.on('finish', () => {
|
|
96
|
+
resolve();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async extractZipFile(zipPath, extractPath) {
|
|
101
|
+
// eslint-disable-next-line new-cap
|
|
102
|
+
const zip = new StreamZip.async({ file: zipPath });
|
|
103
|
+
await zip.extract(null, extractPath);
|
|
104
|
+
await zip.close();
|
|
105
|
+
fs.unlinkSync(zipPath);
|
|
106
|
+
}
|
|
107
|
+
logDebug(debug, logger, message) {
|
|
108
|
+
if (debug && logger) {
|
|
109
|
+
logger(message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
showProgress(quiet, json, message) {
|
|
113
|
+
if (!quiet && !json) {
|
|
114
|
+
core_1.ux.action.status = message;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
112
117
|
}
|
|
113
118
|
exports.MoropoService = MoropoService;
|
|
@@ -23,11 +23,13 @@ export declare class ReportDownloadService {
|
|
|
23
23
|
/**
|
|
24
24
|
* Download test artifacts as a zip file
|
|
25
25
|
* @param options Download configuration
|
|
26
|
+
* @returns Promise that resolves when download is complete
|
|
26
27
|
*/
|
|
27
28
|
downloadArtifacts(options: ArtifactsDownloadOptions): Promise<void>;
|
|
28
29
|
/**
|
|
29
30
|
* Handle downloading reports based on the report type specified
|
|
30
31
|
* @param options Report download configuration
|
|
32
|
+
* @returns Promise that resolves when download is complete
|
|
31
33
|
*/
|
|
32
34
|
downloadReports(options: ReportDownloadOptions): Promise<void>;
|
|
33
35
|
/**
|
|
@@ -35,6 +37,7 @@ export declare class ReportDownloadService {
|
|
|
35
37
|
* @param type Report type to download
|
|
36
38
|
* @param filePath Path where report should be saved
|
|
37
39
|
* @param options Download configuration
|
|
40
|
+
* @returns Promise that resolves when download is complete
|
|
38
41
|
*/
|
|
39
42
|
private downloadReport;
|
|
40
43
|
}
|
|
@@ -10,6 +10,7 @@ class ReportDownloadService {
|
|
|
10
10
|
/**
|
|
11
11
|
* Download test artifacts as a zip file
|
|
12
12
|
* @param options Download configuration
|
|
13
|
+
* @returns Promise that resolves when download is complete
|
|
13
14
|
*/
|
|
14
15
|
async downloadArtifacts(options) {
|
|
15
16
|
const { apiUrl, apiKey, uploadId, downloadType, artifactsPath = './artifacts.zip', debug = false, logger, warnLogger, } = options;
|
|
@@ -35,6 +36,7 @@ class ReportDownloadService {
|
|
|
35
36
|
/**
|
|
36
37
|
* Handle downloading reports based on the report type specified
|
|
37
38
|
* @param options Report download configuration
|
|
39
|
+
* @returns Promise that resolves when download is complete
|
|
38
40
|
*/
|
|
39
41
|
async downloadReports(options) {
|
|
40
42
|
const { reportType, junitPath, allurePath, htmlPath, warnLogger, ...downloadOptions } = options;
|
|
@@ -75,6 +77,7 @@ class ReportDownloadService {
|
|
|
75
77
|
* @param type Report type to download
|
|
76
78
|
* @param filePath Path where report should be saved
|
|
77
79
|
* @param options Download configuration
|
|
80
|
+
* @returns Promise that resolves when download is complete
|
|
78
81
|
*/
|
|
79
82
|
async downloadReport(type, filePath, options) {
|
|
80
83
|
const { apiUrl, apiKey, uploadId, debug = false, logger, warnLogger } = options;
|
|
@@ -41,5 +41,11 @@ export declare class ResultsPollingService {
|
|
|
41
41
|
* @returns Promise that resolves with final test results or rejects if tests fail
|
|
42
42
|
*/
|
|
43
43
|
pollUntilComplete(results: TestResult[], options: PollingOptions): Promise<PollingResult>;
|
|
44
|
+
private buildPollingResult;
|
|
45
|
+
private calculateStatusSummary;
|
|
46
|
+
private displayFinalResults;
|
|
47
|
+
private filterLatestResults;
|
|
48
|
+
private handlePollingError;
|
|
49
|
+
private updateDisplayStatus;
|
|
44
50
|
}
|
|
45
51
|
export {};
|