@devicecloud.dev/dcd 4.2.2 → 4.2.4

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.
@@ -55,6 +55,7 @@ export default class Cloud extends Command {
55
55
  'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
56
56
  orientation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
57
57
  'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
58
+ 'maestro-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
58
59
  'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
59
60
  'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
60
61
  'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -202,6 +202,10 @@ class Cloud extends core_1.Command {
202
202
  this.deviceValidationService.validateiOSDevice(iOSVersion, iOSDevice, compatibilityData, { debug, logger: this.log.bind(this) });
203
203
  // Validate Android device configuration
204
204
  this.deviceValidationService.validateAndroidDevice(androidApiLevel, androidDevice, googlePlay, compatibilityData, { debug, logger: this.log.bind(this) });
205
+ // Warn if maestro-chrome-onboarding flag is used without Android devices
206
+ if (flags['maestro-chrome-onboarding'] && !androidApiLevel && !androidDevice) {
207
+ this.warn('The --maestro-chrome-onboarding flag only applies to Android tests and will be ignored for iOS tests.');
208
+ }
205
209
  flowFile = path.resolve(flowFile);
206
210
  if (!flowFile?.endsWith('.yaml') &&
207
211
  !flowFile?.endsWith('.yml') &&
@@ -383,6 +387,7 @@ class Cloud extends core_1.Command {
383
387
  retry,
384
388
  runnerType,
385
389
  showCrosshairs: flags['show-crosshairs'],
390
+ maestroChromeOnboarding: flags['maestro-chrome-onboarding'],
386
391
  });
387
392
  if (debug) {
388
393
  this.log(`[DEBUG] Submitting flow upload request to ${apiUrl}/uploads/flow`);
@@ -12,13 +12,13 @@ type StatusResponse = {
12
12
  createdAt?: string;
13
13
  error?: string;
14
14
  name?: string;
15
- status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'RUNNING';
15
+ status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'QUEUED' | 'RUNNING';
16
16
  tests: {
17
17
  createdAt?: string;
18
18
  durationSeconds?: number;
19
19
  failReason?: string;
20
20
  name: string;
21
- status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'RUNNING';
21
+ status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'QUEUED' | 'RUNNING';
22
22
  }[];
23
23
  uploadId?: string;
24
24
  };
@@ -10,4 +10,5 @@ export declare const deviceFlags: {
10
10
  'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
11
11
  orientation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
+ 'maestro-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
14
  };
@@ -32,11 +32,15 @@ exports.deviceFlags = {
32
32
  options: Object.values(device_types_1.EiOSVersions),
33
33
  }),
34
34
  orientation: core_1.Flags.string({
35
- description: '[Android only] The orientation of the device to run your flow against in degrees',
35
+ description: '[Android only] The orientation of the device to run your flow against (0 = portrait, 90 = landscape)',
36
36
  options: ['0', '90'],
37
37
  }),
38
38
  'show-crosshairs': core_1.Flags.boolean({
39
39
  default: false,
40
40
  description: '[Android only] Display crosshairs for screen interactions during test execution',
41
41
  }),
42
+ 'maestro-chrome-onboarding': core_1.Flags.boolean({
43
+ default: false,
44
+ description: '[Android only] Force Maestro-based Chrome onboarding - note: this will slow your tests but can fix browser related crashes. See https://docs.devicecloud.dev/reference/chrome-onboarding for more information.',
45
+ }),
42
46
  };
@@ -12,15 +12,15 @@ exports.outputFlags = {
12
12
  }),
13
13
  'junit-path': core_1.Flags.string({
14
14
  dependsOn: ['report'],
15
- description: 'Custom file path for downloaded JUnit report (default: ./report.xml)',
15
+ description: 'Custom file path for downloaded JUnit report (requires --report junit, default: ./report.xml)',
16
16
  }),
17
17
  'allure-path': core_1.Flags.string({
18
18
  dependsOn: ['report'],
19
- description: 'Custom file path for downloaded Allure report (default: ./report.html)',
19
+ description: 'Custom file path for downloaded Allure report (requires --report allure, default: ./report.html)',
20
20
  }),
21
21
  'html-path': core_1.Flags.string({
22
22
  dependsOn: ['report'],
23
- description: 'Custom file path for downloaded HTML report (default: ./report.html)',
23
+ description: 'Custom file path for downloaded HTML report (requires --report html, default: ./report.html)',
24
24
  }),
25
25
  async: core_1.Flags.boolean({
26
26
  description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)',
@@ -41,7 +41,7 @@ exports.outputFlags = {
41
41
  description: 'Output results in JSON format - note: will always provide exit code 0',
42
42
  }),
43
43
  'json-file': core_1.Flags.boolean({
44
- description: 'Write JSON output to a file. File be called <upload_id>_dcd.json unless you supply the --json-file-name flag - note: will always exit with code 0',
44
+ description: 'Write JSON output to a file. File will be called <upload_id>_dcd.json unless you supply the --json-file-name flag - note: will always exit with code 0',
45
45
  required: false,
46
46
  }),
47
47
  'json-file-name': core_1.Flags.string({
@@ -51,7 +51,7 @@ exports.outputFlags = {
51
51
  quiet: core_1.Flags.boolean({
52
52
  char: 'q',
53
53
  default: false,
54
- description: 'Quieter console output that wont provide progress updates',
54
+ description: "Quieter console output that won't provide progress updates",
55
55
  }),
56
56
  report: core_1.Flags.string({
57
57
  aliases: ['format'],
@@ -42,6 +42,7 @@ export declare const flags: {
42
42
  'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
43
43
  orientation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
44
44
  'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
45
+ 'maestro-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
45
46
  'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
46
47
  'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
47
48
  'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -259,7 +259,8 @@ async function plan(options) {
259
259
  if (workspaceConfig.executionOrder?.flowsOrder &&
260
260
  workspaceConfig.executionOrder.flowsOrder.length > 0 &&
261
261
  flowsToRunInSequence.length === 0) {
262
- throw new Error(`executionOrder specified ${workspaceConfig.executionOrder.flowsOrder.length} flow(s) but none were found.\n\n` +
262
+ console.warn(`Warning: executionOrder specified ${workspaceConfig.executionOrder.flowsOrder.length} flow(s) but none were found.\n` +
263
+ `This may be intentional if flows were excluded by tags.\n\n` +
263
264
  `Expected flows: ${workspaceConfig.executionOrder.flowsOrder.join(', ')}\n` +
264
265
  `Available flow names: ${Object.keys(pathsByName).join(', ')}\n\n` +
265
266
  `Hint: Flow names come from either the 'name' field in the flow config or the filename without extension.`);
@@ -27,15 +27,20 @@ function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
27
27
  }
28
28
  if (namesInOrder.length === 0) {
29
29
  const notFound = [...orderSet].filter((item) => !availableNames.includes(item));
30
- throw new Error(`Could not find flows specified in executionOrder.flowsOrder: ${notFound.join(', ')}\n\n` +
30
+ console.warn(`Warning: Could not find flows specified in executionOrder.flowsOrder: ${notFound.join(', ')}\n` +
31
+ `This may be intentional if flows were excluded by tags.\n` +
31
32
  `Available flow names:\n${availableNames.join('\n')}`);
33
+ return [];
32
34
  }
33
35
  const result = [...orderSet].filter((item) => namesInOrder.includes(item));
34
36
  if (result.length === 0) {
35
37
  const notFound = [...orderSet].filter((item) => !namesInOrder.includes(item));
36
- throw new Error(`Could not find flows needed for execution in order: ${notFound.join(', ')}\n\nAvailable flow names:\n${availableNames.join('\n')}`);
38
+ console.warn(`Warning: Could not find flows needed for execution in order: ${notFound.join(', ')}\n` +
39
+ `This may be intentional if flows were excluded by tags.\n` +
40
+ `Available flow names:\n${availableNames.join('\n')}`);
41
+ return [];
37
42
  }
38
- else if (flowOrder
43
+ if (flowOrder
39
44
  .slice(0, result.length)
40
45
  .every((value, index) => value === result[index])) {
41
46
  const resolvedPaths = result.map((item) => paths[item]);
@@ -44,12 +49,10 @@ function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
44
49
  }
45
50
  return resolvedPaths;
46
51
  }
47
- else {
48
- throw new Error(`Flow order mismatch in executionOrder.flowsOrder.\n\n` +
49
- `Expected order: [${flowOrder.slice(0, result.length).join(', ')}]\n` +
50
- `Actual order: [${result.join(', ')}]\n\n` +
51
- `Please ensure flows are specified in the correct order.`);
52
- }
52
+ throw new Error(`Flow order mismatch in executionOrder.flowsOrder.\n\n` +
53
+ `Expected order: [${flowOrder.slice(0, result.length).join(', ')}]\n` +
54
+ `Actual order: [${result.join(', ')}]\n\n` +
55
+ `Please ensure flows are specified in the correct order.`);
53
56
  }
54
57
  function isFlowFile(filePath) {
55
58
  // Exclude files inside .app bundles
@@ -46,7 +46,7 @@ class ResultsPollingService {
46
46
  const updatedResults = await this.fetchAndLogResults(apiUrl, apiKey, uploadId, debug, logger);
47
47
  const { summary } = this.calculateStatusSummary(updatedResults);
48
48
  previousSummary = this.updateDisplayStatus(updatedResults, quiet, json, summary, previousSummary);
49
- const allComplete = updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status));
49
+ const allComplete = updatedResults.every((result) => !['PENDING', 'QUEUED', 'RUNNING'].includes(result.status));
50
50
  if (allComplete) {
51
51
  return await this.handleCompletedTests(updatedResults, {
52
52
  consoleUrl,
@@ -98,6 +98,7 @@ class ResultsPollingService {
98
98
  const passed = statusCounts.PASSED || 0;
99
99
  const failed = statusCounts.FAILED || 0;
100
100
  const pending = statusCounts.PENDING || 0;
101
+ const queued = statusCounts.QUEUED || 0;
101
102
  const running = statusCounts.RUNNING || 0;
102
103
  const total = results.length;
103
104
  const completed = passed + failed;
@@ -106,10 +107,11 @@ class ResultsPollingService {
106
107
  failed,
107
108
  passed,
108
109
  pending,
110
+ queued,
109
111
  running,
110
112
  total,
111
113
  });
112
- return { completed, failed, passed, pending, running, summary, total };
114
+ return { completed, failed, passed, pending, queued, running, summary, total };
113
115
  }
114
116
  displayFinalResults(results, consoleUrl, json, logger) {
115
117
  if (json) {
@@ -144,6 +146,9 @@ class ResultsPollingService {
144
146
  case 'PENDING': {
145
147
  return styling_1.colors.warning(row.status);
146
148
  }
149
+ case 'QUEUED': {
150
+ return styling_1.colors.dim(row.status);
151
+ }
147
152
  default: {
148
153
  return styling_1.colors.dim(row.status);
149
154
  }
@@ -309,7 +314,9 @@ class ResultsPollingService {
309
314
  ? styling_1.colors.error(status.padEnd(10, ' '))
310
315
  : status.toUpperCase() === 'RUNNING'
311
316
  ? styling_1.colors.info(status.padEnd(10, ' '))
312
- : styling_1.colors.warning(status.padEnd(10, ' '));
317
+ : status.toUpperCase() === 'QUEUED'
318
+ ? styling_1.colors.dim(status.padEnd(10, ' '))
319
+ : styling_1.colors.warning(status.padEnd(10, ' '));
313
320
  const retryText = isRetry ? styling_1.colors.dim(' (retry)') : '';
314
321
  core_1.ux.action.status += `\n${statusFormatted} ${test}${retryText}`;
315
322
  }
@@ -15,6 +15,7 @@ export interface TestSubmissionConfig {
15
15
  iOSDevice?: string;
16
16
  iOSVersion?: string;
17
17
  logger?: (message: string) => void;
18
+ maestroChromeOnboarding?: boolean;
18
19
  maestroVersion: string;
19
20
  metadata?: string[];
20
21
  mitmHost?: string;
@@ -17,7 +17,7 @@ class TestSubmissionService {
17
17
  * @returns FormData ready to be submitted to the API
18
18
  */
19
19
  async buildTestFormData(config) {
20
- const { appBinaryId, flowFile, executionPlan, commonRoot, cliVersion, env = [], metadata = [], googlePlay = false, androidApiLevel, androidDevice, iOSVersion, iOSDevice, name, runnerType, maestroVersion, deviceLocale, orientation, mitmHost, mitmPath, retry, continueOnFailure = true, report, showCrosshairs, raw, debug = false, logger, } = config;
20
+ const { appBinaryId, flowFile, executionPlan, commonRoot, cliVersion, env = [], metadata = [], googlePlay = false, androidApiLevel, androidDevice, iOSVersion, iOSDevice, name, runnerType, maestroVersion, deviceLocale, orientation, mitmHost, mitmPath, retry, continueOnFailure = true, report, showCrosshairs, maestroChromeOnboarding, raw, debug = false, logger, } = config;
21
21
  const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
22
22
  const { flows: sequentialFlows = [] } = sequence ?? {};
23
23
  const testFormData = new FormData();
@@ -79,6 +79,7 @@ class TestSubmissionService {
79
79
  raw: JSON.stringify(raw),
80
80
  report,
81
81
  showCrosshairs,
82
+ maestroChromeOnboarding,
82
83
  version: cliVersion,
83
84
  };
84
85
  testFormData.set('config', JSON.stringify(configPayload));
@@ -11,6 +11,7 @@ export declare const symbols: {
11
11
  readonly error: string;
12
12
  readonly info: string;
13
13
  readonly pending: string;
14
+ readonly queued: string;
14
15
  readonly running: string;
15
16
  readonly success: string;
16
17
  readonly unknown: string;
@@ -86,6 +87,7 @@ export declare function formatTestSummary(summary: {
86
87
  failed: number;
87
88
  passed: number;
88
89
  pending: number;
90
+ queued: number;
89
91
  running: number;
90
92
  total: number;
91
93
  }): string;
@@ -23,6 +23,7 @@ exports.symbols = {
23
23
  error: chalk.red('✗'),
24
24
  info: chalk.blue('ℹ'),
25
25
  pending: chalk.yellow('⏸'),
26
+ queued: chalk.gray('⏳'),
26
27
  running: chalk.blue('▶'),
27
28
  success: chalk.green('✓'),
28
29
  unknown: chalk.gray('?'),
@@ -69,6 +70,9 @@ function formatStatus(status) {
69
70
  case 'PENDING': {
70
71
  return `${exports.symbols.pending} ${exports.colors.warning(status)}`;
71
72
  }
73
+ case 'QUEUED': {
74
+ return `${exports.symbols.queued} ${exports.colors.dim(status)}`;
75
+ }
72
76
  case 'CANCELLED': {
73
77
  return `${exports.symbols.cancelled} ${exports.colors.dim(status)}`;
74
78
  }
@@ -132,6 +136,7 @@ function formatTestSummary(summary) {
132
136
  exports.colors.error(`✗ ${summary.failed}`),
133
137
  exports.colors.info(`▶ ${summary.running}`),
134
138
  exports.colors.warning(`⏸ ${summary.pending}`),
139
+ exports.colors.dim(`⏳ ${summary.queued}`),
135
140
  ];
136
141
  return parts.join(' │ ');
137
142
  }
@@ -153,7 +153,7 @@
153
153
  "type": "option"
154
154
  },
155
155
  "orientation": {
156
- "description": "[Android only] The orientation of the device to run your flow against in degrees",
156
+ "description": "[Android only] The orientation of the device to run your flow against (0 = portrait, 90 = landscape)",
157
157
  "name": "orientation",
158
158
  "hasDynamicHelp": false,
159
159
  "multiple": false,
@@ -169,6 +169,12 @@
169
169
  "allowNo": false,
170
170
  "type": "boolean"
171
171
  },
172
+ "maestro-chrome-onboarding": {
173
+ "description": "[Android only] Force Maestro-based Chrome onboarding - note: this will slow your tests but can fix browser related crashes. See https://docs.devicecloud.dev/reference/chrome-onboarding for more information.",
174
+ "name": "maestro-chrome-onboarding",
175
+ "allowNo": false,
176
+ "type": "boolean"
177
+ },
172
178
  "env": {
173
179
  "char": "e",
174
180
  "description": "One or more environment variables to inject into your flows",
@@ -309,7 +315,7 @@
309
315
  "dependsOn": [
310
316
  "report"
311
317
  ],
312
- "description": "Custom file path for downloaded JUnit report (default: ./report.xml)",
318
+ "description": "Custom file path for downloaded JUnit report (requires --report junit, default: ./report.xml)",
313
319
  "name": "junit-path",
314
320
  "hasDynamicHelp": false,
315
321
  "multiple": false,
@@ -319,7 +325,7 @@
319
325
  "dependsOn": [
320
326
  "report"
321
327
  ],
322
- "description": "Custom file path for downloaded Allure report (default: ./report.html)",
328
+ "description": "Custom file path for downloaded Allure report (requires --report allure, default: ./report.html)",
323
329
  "name": "allure-path",
324
330
  "hasDynamicHelp": false,
325
331
  "multiple": false,
@@ -329,7 +335,7 @@
329
335
  "dependsOn": [
330
336
  "report"
331
337
  ],
332
- "description": "Custom file path for downloaded HTML report (default: ./report.html)",
338
+ "description": "Custom file path for downloaded HTML report (requires --report html, default: ./report.html)",
333
339
  "name": "html-path",
334
340
  "hasDynamicHelp": false,
335
341
  "multiple": false,
@@ -365,7 +371,7 @@
365
371
  "type": "boolean"
366
372
  },
367
373
  "json-file": {
368
- "description": "Write JSON output to a file. File be called <upload_id>_dcd.json unless you supply the --json-file-name flag - note: will always exit with code 0",
374
+ "description": "Write JSON output to a file. File will be called <upload_id>_dcd.json unless you supply the --json-file-name flag - note: will always exit with code 0",
369
375
  "name": "json-file",
370
376
  "required": false,
371
377
  "allowNo": false,
@@ -383,7 +389,7 @@
383
389
  },
384
390
  "quiet": {
385
391
  "char": "q",
386
- "description": "Quieter console output that wont provide progress updates",
392
+ "description": "Quieter console output that won't provide progress updates",
387
393
  "name": "quiet",
388
394
  "allowNo": false,
389
395
  "type": "boolean"
@@ -661,5 +667,5 @@
661
667
  ]
662
668
  }
663
669
  },
664
- "version": "4.2.2"
670
+ "version": "4.2.4"
665
671
  }
package/package.json CHANGED
@@ -67,7 +67,7 @@
67
67
  "type": "git",
68
68
  "url": "https://devicecloud.dev"
69
69
  },
70
- "version": "4.2.2",
70
+ "version": "4.2.4",
71
71
  "bugs": {
72
72
  "url": "https://discord.gg/gm3mJwcNw8"
73
73
  },