@devicecloud.dev/dcd 0.0.1-alpha.6 → 0.0.1-alpha.7
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 +4 -1
- package/dist/commands/cloud.js +81 -68
- package/oclif.manifest.json +39 -5
- package/package.json +1 -1
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -12,12 +12,15 @@ export default class Cloud extends Command {
|
|
|
12
12
|
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
13
|
appBinaryId: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
14
|
appFile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
-
|
|
15
|
+
arm64: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
async: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
17
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
18
|
excludeTags: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
19
|
flows: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
googlePlay: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
21
|
iOSVersion: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
22
|
includeTags: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
|
+
orientation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
24
|
};
|
|
22
25
|
run(): Promise<void>;
|
|
23
26
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -98,7 +98,7 @@ class Cloud extends core_1.Command {
|
|
|
98
98
|
static flags = {
|
|
99
99
|
androidApiLevel: core_1.Flags.integer({
|
|
100
100
|
aliases: ['android-api-level'],
|
|
101
|
-
description: 'Android API level to run your flow against',
|
|
101
|
+
description: '[Android only] Android API level to run your flow against',
|
|
102
102
|
options: ['32', '33', '34'],
|
|
103
103
|
}),
|
|
104
104
|
apiKey: core_1.Flags.string({ aliases: ['api-key'], description: 'API key' }),
|
|
@@ -116,9 +116,14 @@ class Cloud extends core_1.Command {
|
|
|
116
116
|
aliases: ['app-file'],
|
|
117
117
|
description: 'App binary to run your flows against',
|
|
118
118
|
}),
|
|
119
|
-
|
|
120
|
-
default:
|
|
119
|
+
arm64: core_1.Flags.boolean({
|
|
120
|
+
default: false,
|
|
121
|
+
description: '[Android only] Run your flow against arm64 devices',
|
|
122
|
+
}),
|
|
123
|
+
async: core_1.Flags.string({
|
|
124
|
+
default: 'true',
|
|
121
125
|
description: 'Wait for the results of the run',
|
|
126
|
+
options: ['true', 'false'],
|
|
122
127
|
}),
|
|
123
128
|
env: core_1.Flags.file({
|
|
124
129
|
char: 'e',
|
|
@@ -135,9 +140,14 @@ class Cloud extends core_1.Command {
|
|
|
135
140
|
flows: core_1.Flags.string({
|
|
136
141
|
description: 'The path to the flow file or folder containing your flows',
|
|
137
142
|
}),
|
|
143
|
+
googlePlay: core_1.Flags.boolean({
|
|
144
|
+
aliases: ['google-play'],
|
|
145
|
+
default: false,
|
|
146
|
+
description: '[Android only] Run your flow against Google Play devices',
|
|
147
|
+
}),
|
|
138
148
|
iOSVersion: core_1.Flags.string({
|
|
139
149
|
aliases: ['ios-version'],
|
|
140
|
-
description: 'iOS version to run your flow against',
|
|
150
|
+
description: '[iOS only] iOS version to run your flow against',
|
|
141
151
|
options: ['16.4', '17.2'],
|
|
142
152
|
}),
|
|
143
153
|
includeTags: core_1.Flags.string({
|
|
@@ -147,10 +157,18 @@ class Cloud extends core_1.Command {
|
|
|
147
157
|
multiple: true,
|
|
148
158
|
parse: (input) => input.split(','),
|
|
149
159
|
}),
|
|
160
|
+
orientation: core_1.Flags.string({
|
|
161
|
+
description: '[Android only] The orientation of the device to run your flow against in degrees',
|
|
162
|
+
options: ['0', '90', '180', '270'],
|
|
163
|
+
}),
|
|
150
164
|
};
|
|
151
165
|
async run() {
|
|
152
166
|
const { args, flags } = await this.parse(Cloud);
|
|
153
|
-
const { apiKey, apiUrl, appBinaryId, appFile, async, env, excludeTags, flows, includeTags, ...rest } = flags;
|
|
167
|
+
const { apiKey, apiUrl, appBinaryId, appFile, arm64, async, env, excludeTags, flows, googlePlay, includeTags, ...rest } = flags;
|
|
168
|
+
if (arm64) {
|
|
169
|
+
(0, cli_ux_1.info)('Contact hello@devicecloud.dev to enquire about arm64 devices');
|
|
170
|
+
(0, cli_ux_1.exit)();
|
|
171
|
+
}
|
|
154
172
|
const { firstFile, secondFile } = args;
|
|
155
173
|
let finalBinaryId = appBinaryId;
|
|
156
174
|
const finalAppFile = appFile ?? firstFile;
|
|
@@ -160,25 +178,40 @@ class Cloud extends core_1.Command {
|
|
|
160
178
|
throw new Error('You cannot provide both an appBinaryId and a binary file');
|
|
161
179
|
}
|
|
162
180
|
flowFile = flows ?? firstFile;
|
|
163
|
-
// this.log(
|
|
164
|
-
// `you want to run the flow(s) ${flowFile} against the binary with id ${appBinaryId} with the following flags: ${JSON.stringify(
|
|
165
|
-
// flags,
|
|
166
|
-
// )}`,
|
|
167
|
-
// );
|
|
168
181
|
}
|
|
169
|
-
|
|
182
|
+
if (!flowFile) {
|
|
183
|
+
throw new Error('You must provide a flow file');
|
|
184
|
+
}
|
|
185
|
+
const testFileNames = [];
|
|
186
|
+
if (!flowFile?.endsWith('.yaml') &&
|
|
187
|
+
!flowFile?.endsWith('.yml') &&
|
|
188
|
+
!flowFile?.endsWith('/')) {
|
|
189
|
+
flowFile += '/';
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat());
|
|
193
|
+
for (const file of executionPlan.flowsToRun) {
|
|
194
|
+
testFileNames.push(file);
|
|
195
|
+
}
|
|
196
|
+
for (const file of executionPlan.sequence?.flows ?? []) {
|
|
197
|
+
// todo: handle continueOnFailure and other sequence properties
|
|
198
|
+
testFileNames.push(file);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.error(error);
|
|
203
|
+
}
|
|
204
|
+
if (!appBinaryId) {
|
|
170
205
|
if (!(flowFile && finalAppFile)) {
|
|
171
206
|
throw new Error('You must provide a flow file and an app binary id');
|
|
172
207
|
}
|
|
173
208
|
if (!finalAppFile.endsWith('.apk') && !finalAppFile.endsWith('.zip')) {
|
|
174
209
|
throw new Error('App file must be a .apk or .zip file');
|
|
175
210
|
}
|
|
176
|
-
this.log(`you want to run the flow(s) ${flowFile} against the app ${finalAppFile} with the following flags: ${JSON.stringify(flags)}`);
|
|
177
|
-
}
|
|
178
|
-
if (!flowFile) {
|
|
179
|
-
throw new Error('You must provide a flow file');
|
|
211
|
+
this.log(`you want to run the flow(s) ${flowFile} against the app ${finalAppFile} with the following flags: ${JSON.stringify(flags)}\n`);
|
|
180
212
|
}
|
|
181
213
|
if (!finalBinaryId) {
|
|
214
|
+
core_1.ux.action.start('Uploading binary', 'Initializing', { stdout: true });
|
|
182
215
|
const binaryFormData = new FormData();
|
|
183
216
|
const binaryBlob = new Blob([await (0, promises_1.readFile)(finalAppFile)], {
|
|
184
217
|
type: mimeTypeLookupByExtension[finalAppFile.split('.').pop()],
|
|
@@ -188,34 +221,13 @@ class Cloud extends core_1.Command {
|
|
|
188
221
|
body: binaryFormData,
|
|
189
222
|
headers: { 'x-app-api-key': apiKey },
|
|
190
223
|
};
|
|
224
|
+
core_1.ux.action.status = `Uploading`;
|
|
191
225
|
const { binaryId, message } = await typeSafePost(apiUrl, '/uploads/binary', options);
|
|
192
226
|
if (!binaryId)
|
|
193
227
|
throw new Error(message);
|
|
194
|
-
|
|
228
|
+
core_1.ux.action.stop(`\nBinary uploaded with id: ${binaryId}`);
|
|
195
229
|
finalBinaryId = binaryId;
|
|
196
230
|
}
|
|
197
|
-
const testFileNames = [];
|
|
198
|
-
let flowFileDirectory = flowFile;
|
|
199
|
-
if (!flowFile?.endsWith('.yaml') && !flowFile?.endsWith('.yml')) {
|
|
200
|
-
try {
|
|
201
|
-
const executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat());
|
|
202
|
-
for (const file of executionPlan.flowsToRun) {
|
|
203
|
-
testFileNames.push(file);
|
|
204
|
-
}
|
|
205
|
-
for (const file of executionPlan.sequence?.flows ?? []) {
|
|
206
|
-
// todo: handle continueOnFailure and other sequence properties
|
|
207
|
-
testFileNames.push(file);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
console.error(error);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
// we are working with a single file
|
|
216
|
-
flowFileDirectory = flowFile.split('/').slice(0, -1).join('/');
|
|
217
|
-
testFileNames.push(flowFile.split('/').pop());
|
|
218
|
-
}
|
|
219
231
|
const testFormData = new FormData();
|
|
220
232
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
221
233
|
const envObject = (env ?? []).reduce((acc, cur) => {
|
|
@@ -223,8 +235,8 @@ class Cloud extends core_1.Command {
|
|
|
223
235
|
acc[key] = value;
|
|
224
236
|
return acc;
|
|
225
237
|
}, {});
|
|
226
|
-
const buffer =
|
|
227
|
-
? await compressDir(
|
|
238
|
+
const buffer = testFileNames?.length > 1
|
|
239
|
+
? await compressDir(flowFile.split('/').slice(0, -1).join('/'))
|
|
228
240
|
: await compressFile(flowFile);
|
|
229
241
|
const blob = new Blob([buffer], {
|
|
230
242
|
type: mimeTypeLookupByExtension.zip,
|
|
@@ -233,6 +245,7 @@ class Cloud extends core_1.Command {
|
|
|
233
245
|
testFormData.set('appBinaryId', finalBinaryId);
|
|
234
246
|
testFormData.set('testFileNames', JSON.stringify(testFileNames));
|
|
235
247
|
testFormData.set('env', JSON.stringify(envObject));
|
|
248
|
+
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
|
|
236
249
|
for (const [key, value] of Object.entries(rest)) {
|
|
237
250
|
if (value) {
|
|
238
251
|
testFormData.set(key, value);
|
|
@@ -250,37 +263,37 @@ class Cloud extends core_1.Command {
|
|
|
250
263
|
.join(', ')}\n`);
|
|
251
264
|
(0, cli_ux_1.info)('Run triggered, you can access the results at:');
|
|
252
265
|
(0, cli_ux_1.info)(`https://console.devicecloud.dev/results/${results[0].test_upload_id}/${results[0].id}/\n`);
|
|
253
|
-
if (
|
|
254
|
-
(0, cli_ux_1.info)('
|
|
266
|
+
if (async === 'false') {
|
|
267
|
+
(0, cli_ux_1.info)('Not waiting for results as async flag is set to false');
|
|
255
268
|
(0, cli_ux_1.exit)(0);
|
|
256
269
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
core_1.ux.action.status = `\nStatus | Test
|
|
270
|
+
// poll for the run status every 5 seconds
|
|
271
|
+
core_1.ux.action.start('Waiting for results', 'Initializing', { stdout: true });
|
|
272
|
+
(0, cli_ux_1.info)('You can safely close this terminal and the tests will continue\n');
|
|
273
|
+
const intervalId = setInterval(async () => {
|
|
274
|
+
const { results: updatedResults } = await typeSafeGet(apiUrl, `/results/${results[0].test_upload_id}`, {
|
|
275
|
+
headers: { 'x-app-api-key': apiKey },
|
|
276
|
+
});
|
|
277
|
+
if (!updatedResults) {
|
|
278
|
+
clearInterval(intervalId);
|
|
279
|
+
(0, errors_1.error)('No results found');
|
|
280
|
+
}
|
|
281
|
+
core_1.ux.action.status = `\nStatus | Test
|
|
270
282
|
────────── ─────────── `;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
for (const { status, test_file_name: test } of updatedResults) {
|
|
284
|
+
core_1.ux.action.status += `\n${status.padEnd(10, ' ')} | ${test}`;
|
|
285
|
+
}
|
|
286
|
+
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
287
|
+
core_1.ux.action.stop('completed');
|
|
288
|
+
(0, cli_ux_1.info)('\n');
|
|
289
|
+
(0, cli_ux_1.table)(updatedResults, {
|
|
290
|
+
status: { get: (row) => row.status },
|
|
291
|
+
test: { get: (row) => row.test_file_name },
|
|
292
|
+
}, { printLine: this.log.bind(this) });
|
|
293
|
+
(0, cli_ux_1.info)('\n');
|
|
294
|
+
clearInterval(intervalId);
|
|
295
|
+
}
|
|
296
|
+
}, 5000);
|
|
284
297
|
}
|
|
285
298
|
}
|
|
286
299
|
exports.default = Cloud;
|
package/oclif.manifest.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"aliases": [
|
|
24
24
|
"android-api-level"
|
|
25
25
|
],
|
|
26
|
-
"description": "Android API level to run your flow against",
|
|
26
|
+
"description": "[Android only] Android API level to run your flow against",
|
|
27
27
|
"name": "androidApiLevel",
|
|
28
28
|
"hasDynamicHelp": false,
|
|
29
29
|
"multiple": false,
|
|
@@ -76,11 +76,23 @@
|
|
|
76
76
|
"multiple": false,
|
|
77
77
|
"type": "option"
|
|
78
78
|
},
|
|
79
|
+
"arm64": {
|
|
80
|
+
"description": "[Android only] Run your flow against arm64 devices",
|
|
81
|
+
"name": "arm64",
|
|
82
|
+
"allowNo": false,
|
|
83
|
+
"type": "boolean"
|
|
84
|
+
},
|
|
79
85
|
"async": {
|
|
80
86
|
"description": "Wait for the results of the run",
|
|
81
87
|
"name": "async",
|
|
82
|
-
"
|
|
83
|
-
"
|
|
88
|
+
"default": "true",
|
|
89
|
+
"hasDynamicHelp": false,
|
|
90
|
+
"multiple": false,
|
|
91
|
+
"options": [
|
|
92
|
+
"true",
|
|
93
|
+
"false"
|
|
94
|
+
],
|
|
95
|
+
"type": "option"
|
|
84
96
|
},
|
|
85
97
|
"env": {
|
|
86
98
|
"char": "e",
|
|
@@ -108,11 +120,20 @@
|
|
|
108
120
|
"multiple": false,
|
|
109
121
|
"type": "option"
|
|
110
122
|
},
|
|
123
|
+
"googlePlay": {
|
|
124
|
+
"aliases": [
|
|
125
|
+
"google-play"
|
|
126
|
+
],
|
|
127
|
+
"description": "[Android only] Run your flow against Google Play devices",
|
|
128
|
+
"name": "googlePlay",
|
|
129
|
+
"allowNo": false,
|
|
130
|
+
"type": "boolean"
|
|
131
|
+
},
|
|
111
132
|
"iOSVersion": {
|
|
112
133
|
"aliases": [
|
|
113
134
|
"ios-version"
|
|
114
135
|
],
|
|
115
|
-
"description": "iOS version to run your flow against",
|
|
136
|
+
"description": "[iOS only] iOS version to run your flow against",
|
|
116
137
|
"name": "iOSVersion",
|
|
117
138
|
"hasDynamicHelp": false,
|
|
118
139
|
"multiple": false,
|
|
@@ -132,6 +153,19 @@
|
|
|
132
153
|
"hasDynamicHelp": false,
|
|
133
154
|
"multiple": true,
|
|
134
155
|
"type": "option"
|
|
156
|
+
},
|
|
157
|
+
"orientation": {
|
|
158
|
+
"description": "[Android only] The orientation of the device to run your flow against in degrees",
|
|
159
|
+
"name": "orientation",
|
|
160
|
+
"hasDynamicHelp": false,
|
|
161
|
+
"multiple": false,
|
|
162
|
+
"options": [
|
|
163
|
+
"0",
|
|
164
|
+
"90",
|
|
165
|
+
"180",
|
|
166
|
+
"270"
|
|
167
|
+
],
|
|
168
|
+
"type": "option"
|
|
135
169
|
}
|
|
136
170
|
},
|
|
137
171
|
"hasDynamicHelp": false,
|
|
@@ -150,5 +184,5 @@
|
|
|
150
184
|
]
|
|
151
185
|
}
|
|
152
186
|
},
|
|
153
|
-
"version": "0.0.1-alpha.
|
|
187
|
+
"version": "0.0.1-alpha.7"
|
|
154
188
|
}
|
package/package.json
CHANGED