@mablhq/mabl-cli 2.29.7 → 2.30.0
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/commands/constants.js +14 -3
- package/commands/tests/tests_cmds/import.js +3 -297
- package/commands/tests/tests_cmds/import_cmds/import_playwright.js +440 -0
- package/commands/tests/tests_cmds/import_cmds/import_selenium.js +295 -0
- package/domUtil/index.js +1 -1
- package/execution/index.js +2 -2
- package/mablApi/index.js +1 -1
- package/mablscriptFind/index.js +1 -1
- package/package.json +3 -1
- package/proxy/index.js +1 -1
- package/resources/mablFind.js +1 -1
- package/upload/index.js +1 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const env_1 = require("../../../../env/env");
|
|
30
|
+
const Timers = __importStar(require("timers"));
|
|
31
|
+
const pluralize_1 = __importDefault(require("pluralize"));
|
|
32
|
+
const inquirer = __importStar(require("inquirer"));
|
|
33
|
+
const mablApiClientFactory_1 = require("../../../../api/mablApiClientFactory");
|
|
34
|
+
const util_1 = require("../../../commandUtil/util");
|
|
35
|
+
const mablApi_1 = require("../../../../mablApi");
|
|
36
|
+
const index_1 = require("../../../../proxy/index");
|
|
37
|
+
const RichPromise_1 = __importDefault(require("../../../../util/RichPromise"));
|
|
38
|
+
const pureUtil_1 = require("../../../../util/pureUtil");
|
|
39
|
+
const constants_1 = require("../../../constants");
|
|
40
|
+
const authenticationProvider_1 = require("../../../../providers/authenticationProvider");
|
|
41
|
+
const asyncUtil_1 = require("../../../../util/asyncUtil");
|
|
42
|
+
const loggingProvider_1 = require("../../../../providers/logging/loggingProvider");
|
|
43
|
+
const chromiumBrowserEngine_1 = require("../../../../browserEngines/chromiumBrowserEngine");
|
|
44
|
+
const { MablTestRunner, MablTestsRunner } = require('../../../../execution');
|
|
45
|
+
const DEFAULT_ASYNC_TIMEOUT_MILLIS = 120000;
|
|
46
|
+
var PostImportActions;
|
|
47
|
+
(function (PostImportActions) {
|
|
48
|
+
PostImportActions["Discard"] = "Discard the test";
|
|
49
|
+
PostImportActions["Run"] = "Run the test locally";
|
|
50
|
+
PostImportActions["Save"] = "Save the test";
|
|
51
|
+
PostImportActions["View"] = "View the test description";
|
|
52
|
+
})(PostImportActions || (PostImportActions = {}));
|
|
53
|
+
exports.command = 'selenium';
|
|
54
|
+
exports.describe = false;
|
|
55
|
+
exports.builder = (yargs) => {
|
|
56
|
+
yargs
|
|
57
|
+
.option(constants_1.CommandArgAuto, {
|
|
58
|
+
default: false,
|
|
59
|
+
describe: 'Automatically save imported test when complete',
|
|
60
|
+
required: false,
|
|
61
|
+
requiresArg: false,
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
})
|
|
64
|
+
.option(constants_1.CommandArgDebug, {
|
|
65
|
+
default: false,
|
|
66
|
+
describe: 'Enable debug mode',
|
|
67
|
+
hidden: true,
|
|
68
|
+
required: false,
|
|
69
|
+
requiresArg: false,
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
})
|
|
72
|
+
.option(constants_1.CommandArgMulti, {
|
|
73
|
+
default: false,
|
|
74
|
+
describe: 'Import multiple tests at once',
|
|
75
|
+
required: false,
|
|
76
|
+
requiresArg: false,
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
})
|
|
79
|
+
.option(constants_1.CommandArgName, {
|
|
80
|
+
alias: constants_1.CommandArgAliases.Name,
|
|
81
|
+
describe: 'The name to give this test in mabl',
|
|
82
|
+
nargs: 1,
|
|
83
|
+
required: false,
|
|
84
|
+
type: 'string',
|
|
85
|
+
})
|
|
86
|
+
.option(constants_1.CommandArgPortNumber, {
|
|
87
|
+
default: 8889,
|
|
88
|
+
describe: 'The port on which the Selenium proxy should listen',
|
|
89
|
+
min: 1,
|
|
90
|
+
max: 65535,
|
|
91
|
+
nargs: 1,
|
|
92
|
+
required: false,
|
|
93
|
+
type: 'number',
|
|
94
|
+
})
|
|
95
|
+
.option(constants_1.CommandArgWorkspaceId, {
|
|
96
|
+
alias: constants_1.CommandArgAliases.WorkspaceId,
|
|
97
|
+
describe: 'Workspace into which tests should be imported',
|
|
98
|
+
nargs: 1,
|
|
99
|
+
required: false,
|
|
100
|
+
requiresArg: true,
|
|
101
|
+
type: 'string',
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
exports.handler = (0, util_1.failWrapper)(importTest);
|
|
105
|
+
async function importTest(parsed) {
|
|
106
|
+
const workspaceId = await (0, util_1.getWorkspaceId)(parsed);
|
|
107
|
+
const importMultipleTests = parsed.multi;
|
|
108
|
+
const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
|
|
109
|
+
const name = await getTestName(parsed.name);
|
|
110
|
+
const importedTests = await importTests(parsed.port, parsed.debug, importMultipleTests);
|
|
111
|
+
if (!importedTests.length) {
|
|
112
|
+
return Promise.reject(new Error(`No ${importMultipleTests ? 'tests' : 'steps'} were imported.`));
|
|
113
|
+
}
|
|
114
|
+
if (importedTests.length > 1) {
|
|
115
|
+
loggingProvider_1.logger.info(`Imported ${importedTests.length} tests.\n`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
loggingProvider_1.logger.info(`Imported ${(0, pluralize_1.default)('step', importedTests[0].length, true)}.\n`);
|
|
119
|
+
}
|
|
120
|
+
return handlePostImportActions(workspaceId, name, apiClient, importedTests, parsed.autoSave);
|
|
121
|
+
}
|
|
122
|
+
function getTestName(name) {
|
|
123
|
+
const nameValidator = (value) => !(value === null || value === void 0 ? void 0 : value.length) ? 'Name must not be empty' : true;
|
|
124
|
+
return name
|
|
125
|
+
? Promise.resolve(name)
|
|
126
|
+
: inquirer
|
|
127
|
+
.prompt([
|
|
128
|
+
{
|
|
129
|
+
type: 'input',
|
|
130
|
+
name: 'name',
|
|
131
|
+
message: 'Enter a name for this test',
|
|
132
|
+
validate: nameValidator,
|
|
133
|
+
},
|
|
134
|
+
])
|
|
135
|
+
.then((answers) => answers.name);
|
|
136
|
+
}
|
|
137
|
+
async function importTests(port, debug, importMultipleTests) {
|
|
138
|
+
const importedTests = [];
|
|
139
|
+
const importCancelPromise = new RichPromise_1.default();
|
|
140
|
+
const sessionClosedPromise = new RichPromise_1.default();
|
|
141
|
+
const seleniumProxy = new index_1.SeleniumProxy(port, debug, () => {
|
|
142
|
+
const steps = seleniumProxy.reset();
|
|
143
|
+
if (steps.length) {
|
|
144
|
+
importedTests.push(steps);
|
|
145
|
+
if (importMultipleTests) {
|
|
146
|
+
loggingProvider_1.logger.info(`Detected end of test ${importedTests.length} with ${(0, pluralize_1.default)('step', steps.length, true)}.`);
|
|
147
|
+
loggingProvider_1.logger.info('Press CTRL+C to end multiple test import.');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (sessionClosedPromise.isPending()) {
|
|
151
|
+
sessionClosedPromise.resolve();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
process.once('SIGINT', () => {
|
|
155
|
+
loggingProvider_1.logger.logNewLine();
|
|
156
|
+
importCancelPromise.resolve();
|
|
157
|
+
});
|
|
158
|
+
await seleniumProxy.start();
|
|
159
|
+
loggingProvider_1.logger.info(`Configure your test to use a selenium proxy at localhost:${port} and run the test now.`);
|
|
160
|
+
if (importMultipleTests) {
|
|
161
|
+
loggingProvider_1.logger.info('Press CTRL+C to end multiple test import.');
|
|
162
|
+
await importCancelPromise;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
loggingProvider_1.logger.info('Import will end automatically when the Selenium session is closed or CTRL+C is pressed');
|
|
166
|
+
await Promise.race([importCancelPromise, sessionClosedPromise]);
|
|
167
|
+
}
|
|
168
|
+
Timers.setImmediate(async () => {
|
|
169
|
+
try {
|
|
170
|
+
await seleniumProxy.stop();
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (debug) {
|
|
174
|
+
loggingProvider_1.logger.error(`Error stopping selenium proxy: ${error}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return importedTests;
|
|
179
|
+
}
|
|
180
|
+
async function handlePostImportActions(workspaceId, name, apiClient, importedTests, autoSave) {
|
|
181
|
+
let nextAction;
|
|
182
|
+
do {
|
|
183
|
+
nextAction = await getNextAction(autoSave);
|
|
184
|
+
loggingProvider_1.logger.logNewLine();
|
|
185
|
+
switch (nextAction) {
|
|
186
|
+
case PostImportActions.Save:
|
|
187
|
+
return saveTests(workspaceId, name, apiClient, importedTests).then(() => {
|
|
188
|
+
loggingProvider_1.logger.info('Import complete.');
|
|
189
|
+
});
|
|
190
|
+
case PostImportActions.View:
|
|
191
|
+
displayTestDescriptions(importedTests);
|
|
192
|
+
break;
|
|
193
|
+
case PostImportActions.Run:
|
|
194
|
+
await runTests(workspaceId, name, apiClient, importedTests);
|
|
195
|
+
break;
|
|
196
|
+
case PostImportActions.Discard:
|
|
197
|
+
loggingProvider_1.logger.info('Exiting without saving.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
loggingProvider_1.logger.logNewLine();
|
|
201
|
+
} while ([PostImportActions.View, PostImportActions.Run].includes(nextAction));
|
|
202
|
+
}
|
|
203
|
+
async function getNextAction(autoSave) {
|
|
204
|
+
if (autoSave) {
|
|
205
|
+
return PostImportActions.Save;
|
|
206
|
+
}
|
|
207
|
+
const answer = await inquirer.prompt([
|
|
208
|
+
{
|
|
209
|
+
type: 'list',
|
|
210
|
+
name: 'action',
|
|
211
|
+
hint: false,
|
|
212
|
+
message: 'What do you want to do next?',
|
|
213
|
+
choices: Object.values(PostImportActions),
|
|
214
|
+
},
|
|
215
|
+
]);
|
|
216
|
+
return answer.action;
|
|
217
|
+
}
|
|
218
|
+
function saveTests(workspaceId, name, apiClient, importedTests) {
|
|
219
|
+
loggingProvider_1.logger.info(`Saving ${(0, pluralize_1.default)('test', importedTests.length, true)}...`);
|
|
220
|
+
const savePromises = importedTests.map((steps, index) => apiClient
|
|
221
|
+
.createFlow(stepsToFlow(workspaceId, steps))
|
|
222
|
+
.then((flow) => apiClient.createJourney(flowToJourney(importedTests.length === 1
|
|
223
|
+
? name
|
|
224
|
+
: `${name} (${index + 1} of ${importedTests.length})`, flow)))
|
|
225
|
+
.then((journey) => {
|
|
226
|
+
loggingProvider_1.logger.info(`Saved test ${journey.invariant_id} : ${env_1.BASE_APP_URL}/workspaces/${journey.organization_id}/train/tests/${journey.invariant_id}/current`);
|
|
227
|
+
return journey;
|
|
228
|
+
}));
|
|
229
|
+
return Promise.all(savePromises);
|
|
230
|
+
}
|
|
231
|
+
function displayTestDescriptions(importedTests) {
|
|
232
|
+
importedTests.forEach((steps, index) => {
|
|
233
|
+
if (importedTests.length > 1) {
|
|
234
|
+
loggingProvider_1.logger.info(`Test ${index + 1} of ${importedTests.length}:`);
|
|
235
|
+
}
|
|
236
|
+
loggingProvider_1.logger.info(steps.map((step) => step.description).join('\n'));
|
|
237
|
+
loggingProvider_1.logger.logNewLine();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function runTests(workspaceId, name, apiClient, importedTests) {
|
|
241
|
+
var _a, _b;
|
|
242
|
+
const flows = importedTests.map((steps) => stepsToFlow(workspaceId, steps));
|
|
243
|
+
const firstUrl = (_a = flows.find((flow) => flow.url)) === null || _a === void 0 ? void 0 : _a.url;
|
|
244
|
+
const browserPath = await new chromiumBrowserEngine_1.ChromiumBrowserEngine().findBrowserExecutable();
|
|
245
|
+
for (let flowIndex = 0; flowIndex < flows.length; flowIndex++) {
|
|
246
|
+
loggingProvider_1.logger.info(`Running test ${flowIndex + 1} of ${flows.length}.`);
|
|
247
|
+
const flow = flows[flowIndex];
|
|
248
|
+
const journey = {
|
|
249
|
+
organization_id: workspaceId,
|
|
250
|
+
name,
|
|
251
|
+
flows: [],
|
|
252
|
+
url: (_b = flow.url) !== null && _b !== void 0 ? _b : firstUrl,
|
|
253
|
+
};
|
|
254
|
+
const testRunner = await createTestRunner(apiClient, journey, flow, journey.url, workspaceId, browserPath);
|
|
255
|
+
const results = await testRunner.run();
|
|
256
|
+
loggingProvider_1.logger.info(`Test result: ${results.status}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function stepsToFlow(workspaceId, steps) {
|
|
260
|
+
const prototype = {
|
|
261
|
+
import_source_id: mablApi_1.Flow.ImportSourceIdEnum.SeleniumNodeProxy,
|
|
262
|
+
import_source_version: (0, pureUtil_1.getCliVersion)(),
|
|
263
|
+
flow_type: mablApi_1.Flow.FlowTypeEnum.Mablscript,
|
|
264
|
+
organization_id: workspaceId,
|
|
265
|
+
reusable: false,
|
|
266
|
+
selectors: steps
|
|
267
|
+
.map((step) => step.selector)
|
|
268
|
+
.map((selector) => selector === null || selector === void 0 ? void 0 : selector.toMablscriptSelector())
|
|
269
|
+
.filter((selector) => selector)
|
|
270
|
+
.map((selector) => selector),
|
|
271
|
+
script: steps.map((step) => step.mablscript).join('\n'),
|
|
272
|
+
script_description: steps.map((step) => step.description).join('\n'),
|
|
273
|
+
url: steps.map((step) => step.url).find((url) => url),
|
|
274
|
+
};
|
|
275
|
+
return prototype;
|
|
276
|
+
}
|
|
277
|
+
function flowToJourney(name, flow) {
|
|
278
|
+
const prototype = {
|
|
279
|
+
organization_id: flow.organization_id,
|
|
280
|
+
name,
|
|
281
|
+
flows: [flow.id],
|
|
282
|
+
url: flow.url,
|
|
283
|
+
};
|
|
284
|
+
return prototype;
|
|
285
|
+
}
|
|
286
|
+
async function createTestRunner(apiClient, test, flow, url, workspaceId, browserPath) {
|
|
287
|
+
const authConfig = await (0, asyncUtil_1.promiseWithTimeout)(new authenticationProvider_1.AuthenticationProvider().getAuthConfigWithAutoRenew(), DEFAULT_ASYNC_TIMEOUT_MILLIS, 'Get auth config with auto renew');
|
|
288
|
+
await (0, asyncUtil_1.promiseWithTimeout)(MablTestsRunner.validateAuth(apiClient, authConfig), DEFAULT_ASYNC_TIMEOUT_MILLIS, 'Validate auth');
|
|
289
|
+
const runner = new MablTestRunner({
|
|
290
|
+
mablApiClient: apiClient,
|
|
291
|
+
testRunConfig: { imported: true, url, workspaceId },
|
|
292
|
+
});
|
|
293
|
+
await runner.initializeTestRunner(test, [flow], 'master', apiClient, browserPath, false, { url });
|
|
294
|
+
return runner;
|
|
295
|
+
}
|