@testim/testim-cli 3.234.0 → 3.237.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/agent/routers/cliJsCode/service.js +4 -38
- package/agent/server.js +2 -2
- package/cli.js +0 -2
- package/cliAgentMode.js +129 -15
- package/commons/chromedriverWrapper.js +1 -0
- package/commons/featureFlags.js +1 -0
- package/commons/prepareRunnerAndTestimStartUtils.js +7 -3
- package/commons/testimCloudflare.js +8 -8
- package/commons/testimCloudflare.test.js +162 -0
- package/commons/testimNgrok.js +6 -4
- package/commons/testimNgrok.test.js +140 -0
- package/commons/testimTunnel.js +5 -5
- package/commons/testimTunnel.test.js +69 -0
- package/executionQueue.js +4 -0
- package/npm-shrinkwrap.json +261 -245
- package/package.json +1 -2
- package/player/stepActions/inputFileStepAction.js +23 -14
- package/player/stepActions/textValidationStepAction.js +3 -0
- package/player/webdriver.js +13 -3
- package/runOptions.js +88 -70
- package/runOptionsAgentFlow.js +5 -0
- package/runner.js +1 -2
- package/runners/ParallelWorkerManager.js +14 -6
- package/runners/TestPlanRunner.js +10 -11
- package/services/lambdatestService.js +13 -9
- package/services/lambdatestService.test.js +259 -0
- package/testRunStatus.js +34 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testim/testim-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.237.0",
|
|
4
4
|
"description": "Command line interface for running Testing on your CI",
|
|
5
5
|
"author": "Oren Rubin",
|
|
6
6
|
"contributors": [{
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
"data-uri-to-buffer": "2.0.2",
|
|
70
70
|
"decompress": "4.2.1",
|
|
71
71
|
"express": "4.17.3",
|
|
72
|
-
"find-root": "1.1.0",
|
|
73
72
|
"fkill": "7.2.1",
|
|
74
73
|
"form-data": "3.0.0",
|
|
75
74
|
"fs-extra": "10.0.1",
|
|
@@ -4,7 +4,7 @@ const StepAction = require('./stepAction');
|
|
|
4
4
|
const _ = require('lodash');
|
|
5
5
|
const logger = require('../../commons/logger').getLogger('input-file-step-action');
|
|
6
6
|
const { codeSnippets, utils } = require('../../commons/getSessionPlayerRequire');
|
|
7
|
-
const { extractElementId } = require('../../utils');
|
|
7
|
+
const { extractElementId, download } = require('../../utils');
|
|
8
8
|
const inputFileUtils = require('../../inputFileUtils');
|
|
9
9
|
const featureFlagService = require('../../commons/featureFlags');
|
|
10
10
|
|
|
@@ -41,18 +41,15 @@ class InputFileStepAction extends StepAction {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async uploadFilesAndForceVisibility(gridLocalFiles, target) {
|
|
44
|
-
const preUploadStep = this.driver.isSafari() ?
|
|
45
|
-
// in safari we force the visibility ahead of time because making it visible the second time around doesn't work
|
|
46
|
-
this.safariPreUploadActions(target) :
|
|
47
|
-
Promise.resolve();
|
|
48
|
-
|
|
49
44
|
try {
|
|
50
|
-
|
|
45
|
+
if (this.driver.isSafari()) {
|
|
46
|
+
await this.safariPreUploadActions(target);
|
|
47
|
+
}
|
|
51
48
|
await this.uploadFiles(gridLocalFiles, target);
|
|
52
49
|
} catch (err) {
|
|
53
50
|
const edgeErrorEditableMessage = 'The element is not editable';
|
|
54
51
|
const edgeErrorFocusableMessage = 'The element is not focusable';
|
|
55
|
-
const
|
|
52
|
+
const safariErrorVisibleMessage = 'An element command could not be completed because the element is not visible on the page.';
|
|
56
53
|
const elementNotInteractable = 'element not interactable';
|
|
57
54
|
const elementNotPointerOrKeyboardInteractable = 'element is not pointer- or keyboard interactable';
|
|
58
55
|
const invalidStateMsg = 'invalid element state: Element is not currently interactable and may not be manipulated';
|
|
@@ -64,7 +61,7 @@ class InputFileStepAction extends StepAction {
|
|
|
64
61
|
_.startsWith(errorMsg, mustBeVisibleMsg) ||
|
|
65
62
|
_.startsWith(errorMsg, edgeErrorEditableMessage) ||
|
|
66
63
|
_.startsWith(errorMsg, edgeErrorFocusableMessage) ||
|
|
67
|
-
_.startsWith(errorMsg,
|
|
64
|
+
_.startsWith(errorMsg, safariErrorVisibleMessage) ||
|
|
68
65
|
_.includes(errorMsg, notReachableByKeyboard) ||
|
|
69
66
|
_.includes(errorMsg, elementNotInteractable) ||
|
|
70
67
|
_.includes(errorMsg, elementNotPointerOrKeyboardInteractable)
|
|
@@ -88,9 +85,18 @@ class InputFileStepAction extends StepAction {
|
|
|
88
85
|
async performAction() {
|
|
89
86
|
const target = this.context.data[this.step.targetId || 'targetId'];
|
|
90
87
|
const overrideAzureStorageUrl = featureFlagService.flags.overrideAzureStorageUrl.isEnabled();
|
|
91
|
-
|
|
92
|
-
const fileUrls = await utils.addTokenToFileUrl(this.context.project.id, this.step.fileUrls, this.stepActionUtils.testimServicesApi, overrideAzureStorageUrl, logger);
|
|
93
88
|
const useJsInputCodeInSafari = featureFlagService.flags.useJsInputCodeInSafari.isEnabled();
|
|
89
|
+
const downloadToBase64 = featureFlagService.flags.downloadToBase64.isEnabled();
|
|
90
|
+
|
|
91
|
+
let fileUrls = await utils.addTokenToFileUrl(this.context.project.id, this.step.fileUrls, this.stepActionUtils.testimServicesApi, overrideAzureStorageUrl, logger);
|
|
92
|
+
|
|
93
|
+
if (downloadToBase64) {
|
|
94
|
+
fileUrls = await Promise.all(fileUrls.map(async ({ name, url }) => {
|
|
95
|
+
const res = await download(url);
|
|
96
|
+
return { name, url: `data:${res.type};base64,${Buffer.from(res.body).toString('base64')}` };
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
if (this.driver.isSafari() && (useJsInputCodeInSafari || fileUrls.length > 1)) {
|
|
95
101
|
await this.driver.executeJSWithArray(`
|
|
96
102
|
const getLocatedElement = ${codeSnippets.getLocatedElementCode};
|
|
@@ -165,15 +171,18 @@ function downloadAndUpload() {
|
|
|
165
171
|
return new File([blob], name, { type: blob.type });
|
|
166
172
|
}));
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
const dt = new DataTransfer();
|
|
175
|
+
for (const file of fileList) {
|
|
176
|
+
dt.items.add(file);
|
|
177
|
+
}
|
|
178
|
+
element.files = dt.files;
|
|
179
|
+
|
|
169
180
|
let changeWasFired = false;
|
|
170
181
|
const changeFiredHandler = (e) => {
|
|
171
182
|
changeWasFired = true;
|
|
172
183
|
};
|
|
173
184
|
|
|
174
185
|
element.addEventListener("change", changeFiredHandler, true);
|
|
175
|
-
Reflect.deleteProperty(element, 'files');
|
|
176
|
-
Reflect.defineProperty(element, 'files', { get() { return fileList; }, configurable: true });
|
|
177
186
|
await Promise.resolve(); // wait microtick
|
|
178
187
|
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
179
188
|
if (!changeWasFired) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
const sessionPlayer = require('../../commons/getSessionPlayerRequire');
|
|
4
5
|
|
|
5
6
|
const StepAction = require('./stepAction');
|
|
@@ -7,6 +8,7 @@ const constants = sessionPlayer.commonConstants.stepResult;
|
|
|
7
8
|
const paramEvaluator = sessionPlayer.stepParamExpressionEvaluator;
|
|
8
9
|
const utils = sessionPlayer.utils;
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
const Promise = require('bluebird');
|
|
11
13
|
|
|
12
14
|
class TextValidationStepAction extends StepAction {
|
|
@@ -15,6 +17,7 @@ class TextValidationStepAction extends StepAction {
|
|
|
15
17
|
var context = this.context;
|
|
16
18
|
var target = this.getTarget();
|
|
17
19
|
var frameHandler = this.frameHandler;
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
return new Promise(resolve => {
|
|
20
23
|
var onFail = resultInfo => {
|
package/player/webdriver.js
CHANGED
|
@@ -59,6 +59,7 @@ class WebDriver extends WebDriverApi {
|
|
|
59
59
|
this.cdpUrl = undefined;
|
|
60
60
|
this.browserClosedCallbacks = [];
|
|
61
61
|
this.browserClosedFailedKeepAlives = 0;
|
|
62
|
+
this.ignoreHiddenTagsText = false;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
registerToClosedBrowser(callback) {
|
|
@@ -71,6 +72,7 @@ class WebDriver extends WebDriverApi {
|
|
|
71
72
|
|
|
72
73
|
async init(browserOptions, testName, testRunConfig, gridInfo, customExtensionLocalLocation, executionId, testResultId, seleniumPerfStats = new SeleniumPerfStats(), fastInit = false, lambdatestService) {
|
|
73
74
|
this.browserClosedFailedKeepAlives = 0;
|
|
75
|
+
this.ignoreHiddenTagsText = _(browserOptions).get('company.activePlan.premiumFeatures.ignoreHiddenTagsText');
|
|
74
76
|
this.browserClosedCallbacks = [];
|
|
75
77
|
const capabilities = desiredCapabilitiesBuilder.buildSeleniumOptions(browserOptions, testName, testRunConfig, gridInfo, customExtensionLocalLocation, executionId, testResultId, lambdatestService);
|
|
76
78
|
if (capabilities.desiredCapabilities) {
|
|
@@ -330,7 +332,7 @@ class WebDriver extends WebDriverApi {
|
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
getElementTextJS(locatedElement) {
|
|
333
|
-
function extractTextCode(locatedElement) {
|
|
335
|
+
function extractTextCode(locatedElement, ignoreHiddenTagsText) {
|
|
334
336
|
// copy of utils.getElementTextContent to run inside content script
|
|
335
337
|
// sadly .children doesn't work for SVG elements in IE11
|
|
336
338
|
function clearTitleTags(node) {
|
|
@@ -360,7 +362,15 @@ class WebDriver extends WebDriverApi {
|
|
|
360
362
|
return element.shadowRoot.textContent.replace(/(\r\n|\n|\r)/gm, "");
|
|
361
363
|
}
|
|
362
364
|
} catch (err) { }
|
|
363
|
-
|
|
365
|
+
if (ignoreHiddenTagsText && Array.prototype.some.call(element.children, function (elem) { return elem.hidden; })) {
|
|
366
|
+
var dupElement = element.cloneNode(true);
|
|
367
|
+
var hiddenChildren = Array.prototype.filter.call(dupElement.children, function (elem) { return elem.hidden; });
|
|
368
|
+
hiddenChildren.forEach(function (child) {
|
|
369
|
+
dupElement.removeChild(child);
|
|
370
|
+
});
|
|
371
|
+
return dupElement.textContent.replace(/(\r\n|\n|\r)/gm, '');
|
|
372
|
+
}
|
|
373
|
+
return element.textContent.replace(/(\r\n|\n|\r)/gm, '');
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
function getElementTextContent(element) {
|
|
@@ -390,7 +400,7 @@ class WebDriver extends WebDriverApi {
|
|
|
390
400
|
var getLocatedElement = ${codeSnippets().getLocatedElementCode};
|
|
391
401
|
var extractText = ${extractTextCode.toString()};
|
|
392
402
|
return extractText.apply(null, arguments)
|
|
393
|
-
`, locatedElement)
|
|
403
|
+
`, locatedElement, this.ignoreHiddenTagsText)
|
|
394
404
|
.then(result => result.value);
|
|
395
405
|
}
|
|
396
406
|
|
package/runOptions.js
CHANGED
|
@@ -187,6 +187,8 @@ program
|
|
|
187
187
|
.option('--token [token]', 'identification token to testim')
|
|
188
188
|
.option('--is-regression-baseline-run', 'save doms and run results as regression baseline data')
|
|
189
189
|
.option('--parallel [number-of-tests]', 'number of tests to run on parallel')
|
|
190
|
+
.option('--before-parallel [number-of-tests]', 'number of tests to run on parallel in the before phase of a test plan')
|
|
191
|
+
.option('--after-parallel [number-of-tests]', 'number of tests to run on parallel in the after phase of a test plan')
|
|
190
192
|
.option('--canary [canary-mode]', 'enable canary mode', false)
|
|
191
193
|
.option('--test-plan [test-plan-name]', 'test plan to run', collect, [])
|
|
192
194
|
.option('--test-plan-id [test-plan-id]', 'test plan to run', collect, [])
|
|
@@ -306,6 +308,7 @@ program
|
|
|
306
308
|
.option('--external-lambdatest-tunnel-id [tunnel-id]', 'use existing lambdatest tunnel ID')
|
|
307
309
|
.option('--external-lambdatest-use-wss', 'use wss instead of ssh for LT', false)
|
|
308
310
|
.option('--external-lambdatest-disable-automation-tunneling', 'don\'t tunnel Testim calls in LT tunnel', true)
|
|
311
|
+
.option('--external-lambdatest-mitm', 'Turn on LT Man in the middle', false)
|
|
309
312
|
|
|
310
313
|
.option('--w3c-capabilities [enable-w3c-caps-mode]', 'enable/disable w3c capabilities format (default enable)', JSON.parse, true)
|
|
311
314
|
.option('--old-capabilities [enable-old-caps-mode]', 'enable/disable old capabilities format (default enable)', JSON.parse, true)
|
|
@@ -321,6 +324,7 @@ program
|
|
|
321
324
|
// Agent mode
|
|
322
325
|
.option('connect, --agent [enable-agent-mode]', 'enable Testim CLI agent mode', false)
|
|
323
326
|
.option('start [enable-start]', 'Connect to testim and open the editor in a standalone browser', false)
|
|
327
|
+
.option('--download-browser', 'when used with the start option, downloads a fixed version to run Testim editor in', false)
|
|
324
328
|
.option('--agent-port [agent-port]', 'set agent port', Number, 42543)
|
|
325
329
|
.option('--agent-bind [agent-host-bind]', 'set agent host bind', '127.0.0.1')
|
|
326
330
|
|
|
@@ -494,7 +498,7 @@ module.exports = {
|
|
|
494
498
|
}
|
|
495
499
|
|
|
496
500
|
if (program.proxyForGrid && !program.proxy) {
|
|
497
|
-
|
|
501
|
+
throw new ArgError('missing --proxy option');
|
|
498
502
|
}
|
|
499
503
|
|
|
500
504
|
if (runOptionsAgentFlow.isAgentFlow(program)) {
|
|
@@ -553,20 +557,20 @@ module.exports = {
|
|
|
553
557
|
}
|
|
554
558
|
}
|
|
555
559
|
|
|
556
|
-
if (program.reporters && program.reporters.
|
|
560
|
+
if (program.reporters && program.reporters.includes('junit') && !program.reportFile) {
|
|
557
561
|
console.log('Warning: please define --report-file option for JUnit reporter');
|
|
558
562
|
}
|
|
559
563
|
|
|
560
564
|
if (!program.tunnel && program.externalLambdatestTunnelId) {
|
|
561
|
-
|
|
565
|
+
throw new ArgError('missing --tunnel parameter');
|
|
562
566
|
}
|
|
563
567
|
|
|
564
568
|
if (!program.tunnel && program.externalLambdatestUseWss) {
|
|
565
|
-
|
|
569
|
+
throw new ArgError('missing --tunnel parameter');
|
|
566
570
|
}
|
|
567
571
|
|
|
568
572
|
if (!program.tunnel && [program.tunnelPort, program.tunnelHostHeader, program.tunnelRegion, program.tunnelDiagnostics].some(Boolean)) {
|
|
569
|
-
|
|
573
|
+
throw new ArgError('missing --tunnel parameter');
|
|
570
574
|
}
|
|
571
575
|
if (program.chromeExtraPrefs) {
|
|
572
576
|
try {
|
|
@@ -609,7 +613,7 @@ module.exports = {
|
|
|
609
613
|
|
|
610
614
|
// SauceLabs Options
|
|
611
615
|
if ((program.sauceUser && !program.sauceKey) || (!program.sauceUser && program.sauceKey)) {
|
|
612
|
-
|
|
616
|
+
throw new ArgError('missing --sauce-user <sauce-user> or --sauce-key <sauce-key>');
|
|
613
617
|
}
|
|
614
618
|
|
|
615
619
|
if (program.sauceUser && program.sauceKey) {
|
|
@@ -655,7 +659,7 @@ module.exports = {
|
|
|
655
659
|
|
|
656
660
|
// BrowserStack options
|
|
657
661
|
if ((program.browserstackUser && !program.browserstackKey) || (!program.browserstackUser && program.browserstackKey)) {
|
|
658
|
-
|
|
662
|
+
throw new ArgError('missing --browserstack-user <browserstack-user> or --browserstack-key <browserstack-key>');
|
|
659
663
|
}
|
|
660
664
|
if (program.browserstackUser && program.browserstackKey) {
|
|
661
665
|
setHostAndPortForBrowserStack();
|
|
@@ -726,7 +730,7 @@ module.exports = {
|
|
|
726
730
|
if (projectId) {
|
|
727
731
|
program.project = projectId;
|
|
728
732
|
} else {
|
|
729
|
-
|
|
733
|
+
throw new ArgError('missing project-id info, either --login to provide new credentials or use --project <project-id>');
|
|
730
734
|
}
|
|
731
735
|
}
|
|
732
736
|
|
|
@@ -737,12 +741,12 @@ module.exports = {
|
|
|
737
741
|
|
|
738
742
|
if (program.testConfig) {
|
|
739
743
|
// convert single test config inputs to array (e.g. from configFile)
|
|
740
|
-
program.testConfig = [].
|
|
744
|
+
program.testConfig = [program.testConfig].flat();
|
|
741
745
|
}
|
|
742
746
|
|
|
743
747
|
if (program.testConfigId) {
|
|
744
748
|
// convert single test config inputs to array (e.g. from configFile)
|
|
745
|
-
program.testConfigId = [].
|
|
749
|
+
program.testConfigId = [program.testConfigId].flat();
|
|
746
750
|
}
|
|
747
751
|
|
|
748
752
|
program.retries = !program.retries || typeof program.retries === 'boolean' ? 1 : Number(program.retries) + 1;
|
|
@@ -760,25 +764,25 @@ module.exports = {
|
|
|
760
764
|
|
|
761
765
|
const timeoutWasGiven = Boolean(program.timeout);
|
|
762
766
|
program.timeout = !program.timeout || typeof program.timeout === 'boolean' ? 10 * 60 * 1000 : Number(program.timeout);
|
|
767
|
+
program.beforeParallel = !program.beforeParallel || typeof program.beforeParallel === 'boolean' ? 1 : Number(program.beforeParallel);
|
|
763
768
|
program.parallel = !program.parallel || typeof program.parallel === 'boolean' ? 1 : Number(program.parallel);
|
|
769
|
+
program.afterParallel = !program.afterParallel || typeof program.afterParallel === 'boolean' ? 1 : Number(program.afterParallel);
|
|
764
770
|
|
|
765
771
|
|
|
766
772
|
if (program.parallel > 1 && program.run && !program.gridId && !program.grid &&
|
|
767
|
-
((!program.testPlan || program.testPlan.length === 0) && (!program.testPlanId || !program.testPlanId.length))) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
process.exit(0);
|
|
781
|
-
}
|
|
773
|
+
((!program.testPlan || program.testPlan.length === 0) && (!program.testPlanId || !program.testPlanId.length)) && process.stdout.isTTY && !program.headless && !process.env.TERM) {
|
|
774
|
+
const prompts = require('prompts');
|
|
775
|
+
const response = await prompts({
|
|
776
|
+
type: 'toggle',
|
|
777
|
+
name: 'isSure',
|
|
778
|
+
message: 'Running in parallel without --headless flag will open several browsers on your computer. Are you sure?',
|
|
779
|
+
initial: false,
|
|
780
|
+
active: 'yes',
|
|
781
|
+
inactive: 'no',
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
if (!response.isSure) {
|
|
785
|
+
process.exit(0);
|
|
782
786
|
}
|
|
783
787
|
}
|
|
784
788
|
|
|
@@ -787,11 +791,11 @@ module.exports = {
|
|
|
787
791
|
program.port = program.port && Number(program.port);
|
|
788
792
|
|
|
789
793
|
if (program.retries <= 0 || _.isNaN(program.retries)) {
|
|
790
|
-
|
|
794
|
+
throw new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>');
|
|
791
795
|
}
|
|
792
796
|
|
|
793
797
|
if (program.retries > 21) {
|
|
794
|
-
|
|
798
|
+
throw new ArgError('Max number of retries exceeded. Number cannot be greater than 20, --retries <max_num_of_retries>');
|
|
795
799
|
}
|
|
796
800
|
|
|
797
801
|
if (!program.token) {
|
|
@@ -801,33 +805,40 @@ module.exports = {
|
|
|
801
805
|
if (credentialToken) {
|
|
802
806
|
program.token = credentialToken;
|
|
803
807
|
} else {
|
|
804
|
-
|
|
805
|
-
new ArgError('missing Testim Access Token, either --login to provide new credentials or use --token <testim-access-token>, contact info@testim.io if you need a new one.'));
|
|
808
|
+
throw new ArgError('missing Testim Access Token, either --login to provide new credentials or use --token <testim-access-token>, contact info@testim.io if you need a new one.');
|
|
806
809
|
}
|
|
807
810
|
}
|
|
808
811
|
|
|
809
812
|
if (program.browserTimeout <= 0 || _.isNaN(program.browserTimeout)) {
|
|
810
|
-
|
|
813
|
+
throw new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>');
|
|
811
814
|
}
|
|
812
815
|
|
|
813
816
|
if (program.newBrowserWaitTimeout <= 0 || _.isNaN(program.newBrowserWaitTimeout)) {
|
|
814
|
-
|
|
817
|
+
throw new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>');
|
|
815
818
|
}
|
|
816
819
|
|
|
817
820
|
if (program.timeout <= 0 || _.isNaN(program.timeout)) {
|
|
818
|
-
|
|
821
|
+
throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (program.beforeParallel <= 0 || _.isNaN(program.beforeParallel)) {
|
|
825
|
+
throw new ArgError('before-parallel could not be a negative number or not number, --before-parallel <number-of-tests>');
|
|
819
826
|
}
|
|
820
827
|
|
|
821
828
|
if (program.parallel <= 0 || _.isNaN(program.parallel)) {
|
|
822
|
-
|
|
829
|
+
throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (program.afterParallel <= 0 || _.isNaN(program.afterParallel)) {
|
|
833
|
+
throw new ArgError('after-parallel could not be a negative number or not number, --after-parallel <number-of-tests>');
|
|
823
834
|
}
|
|
824
835
|
|
|
825
|
-
if ([CLI_MODE.EXTENSION, CLI_MODE.SELENIUM].
|
|
826
|
-
|
|
836
|
+
if (![CLI_MODE.EXTENSION, CLI_MODE.SELENIUM].includes(program.mode)) {
|
|
837
|
+
throw new ArgError(`runner mode <${program.mode}> is not supported`);
|
|
827
838
|
}
|
|
828
839
|
|
|
829
840
|
if ((program.mode !== CLI_MODE.SELENIUM) && program.disableNativeEvents) {
|
|
830
|
-
|
|
841
|
+
throw new ArgError('disable-native-events is only applicable in selenium mode');
|
|
831
842
|
}
|
|
832
843
|
|
|
833
844
|
if (
|
|
@@ -848,9 +859,9 @@ module.exports = {
|
|
|
848
859
|
!program.useChromeLauncher &&
|
|
849
860
|
!program.createPrefechedData
|
|
850
861
|
) {
|
|
851
|
-
|
|
862
|
+
throw new ArgError(
|
|
852
863
|
'missing remote grid address parameter, specify --host <host-name-or-ip> or --grid <grid-name> or --grid-id <grid-id>'
|
|
853
|
-
)
|
|
864
|
+
);
|
|
854
865
|
}
|
|
855
866
|
} else if (
|
|
856
867
|
program.testId.length ||
|
|
@@ -862,9 +873,13 @@ module.exports = {
|
|
|
862
873
|
program.useLocalChromeDriver ||
|
|
863
874
|
program.useChromeLauncher
|
|
864
875
|
) {
|
|
865
|
-
|
|
876
|
+
throw new ArgError(
|
|
866
877
|
'cannot set --testId, --label, --name, --browser, --test-config, --test-config-id, --use-local-chrome-driver --use-chrome-launcher or --suite with --test-plan option'
|
|
867
|
-
)
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (!isTestPlanSpecified && (program.beforeParallel !== 1 || program.afterParallel !== 1)) {
|
|
882
|
+
throw new ArgError('cannot set --before-parallel or --after-parallel without --test-plan option');
|
|
868
883
|
}
|
|
869
884
|
|
|
870
885
|
if (
|
|
@@ -875,17 +890,17 @@ module.exports = {
|
|
|
875
890
|
isSuiteSpecified) &&
|
|
876
891
|
program.file
|
|
877
892
|
) {
|
|
878
|
-
|
|
893
|
+
throw new ArgError(
|
|
879
894
|
'Cannot pass codeful automation tests with --testId --label --name or --suite'
|
|
880
|
-
)
|
|
895
|
+
);
|
|
881
896
|
}
|
|
882
897
|
|
|
883
898
|
const numberOfDefinedHosts = [program.host, program.grid, program.gridId].filter(Boolean).length;
|
|
884
899
|
if (numberOfDefinedHosts > 1) {
|
|
885
|
-
|
|
900
|
+
throw new ArgError('please define exactly one of --grid or --grid-id or --host');
|
|
886
901
|
}
|
|
887
902
|
|
|
888
|
-
if (program.host && program.host.
|
|
903
|
+
if (program.host && program.host.includes('/')) {
|
|
889
904
|
if (!/^(f|ht)tps?:\/\//i.test(program.host)) {
|
|
890
905
|
program.host = `http://${program.host}`;
|
|
891
906
|
}
|
|
@@ -896,7 +911,7 @@ module.exports = {
|
|
|
896
911
|
program.resultLabel = program.resultLabel.map(label => label.trim()).filter(Boolean);
|
|
897
912
|
const invalidLabels = program.resultLabel.filter(label => label.length >= 250).filter(Boolean);
|
|
898
913
|
if (invalidLabels.length) {
|
|
899
|
-
|
|
914
|
+
throw new ArgError('A result label cannot exceed 250 characters');
|
|
900
915
|
}
|
|
901
916
|
}
|
|
902
917
|
|
|
@@ -904,66 +919,66 @@ module.exports = {
|
|
|
904
919
|
const playerUrl = runOptionsUtils.getPlayerUrl(program);
|
|
905
920
|
|
|
906
921
|
if (!program.w3cCapabilities && !program.oldCapabilities) {
|
|
907
|
-
|
|
922
|
+
throw new ArgError('cannot set --w3c-capabilities and --old-capabilities options as false');
|
|
908
923
|
}
|
|
909
924
|
program.protocol = program.protocol || (program.port === 443 ? 'https' : 'http');
|
|
910
925
|
if (!['http', 'https'].includes(program.protocol)) {
|
|
911
|
-
|
|
926
|
+
throw new ArgError('invalid --protocol value, allow --protocol http or https');
|
|
912
927
|
}
|
|
913
928
|
|
|
914
929
|
if (program.rerunFailedByRunId && program.branch) {
|
|
915
|
-
|
|
930
|
+
throw new ArgError('It is not possible to use --branch with --rerun-failed-by-run-id. Tests will automatically run on the same branch that was used in the original run');
|
|
916
931
|
}
|
|
917
932
|
|
|
918
933
|
if (program.rerunFailedByRunId &&
|
|
919
934
|
(isSuiteSpecified || program.name.length ||
|
|
920
935
|
program.testId.length || program.label.length || isTestPlanSpecified)) {
|
|
921
|
-
|
|
922
|
-
' label (--label), plan (--test-plan), or other test flags (--test) are provided. Please remove these flags and try again')
|
|
936
|
+
throw new ArgError('Re-running failed tests is not possible when suite (--suite),' +
|
|
937
|
+
' label (--label), plan (--test-plan), or other test flags (--test) are provided. Please remove these flags and try again');
|
|
923
938
|
}
|
|
924
939
|
|
|
925
940
|
if (program.run.length) {
|
|
926
941
|
const glob = require('glob');
|
|
927
942
|
program.files = _.flatMap(program.run, files => glob.sync(files));
|
|
928
943
|
if (program.files.length === 0) {
|
|
929
|
-
|
|
944
|
+
throw new ArgError(`No files found at path '${program.run}'.`);
|
|
930
945
|
}
|
|
931
946
|
} else {
|
|
932
947
|
program.files = [];
|
|
933
948
|
}
|
|
934
949
|
|
|
935
950
|
if (program.setRetention && !_.inRange(_.parseInt(program.setRetention), 1, 11)) {
|
|
936
|
-
|
|
951
|
+
throw new ArgError('Please provide the number of days that the test results will be retained for (--set-retention must be a whole number between 1 to 10)');
|
|
937
952
|
}
|
|
938
953
|
program.setRetention = program.setRetention && Number(program.setRetention);
|
|
939
954
|
|
|
940
955
|
const mockNetworkDeprecationMsg = 'is no longer supported, please use --override-mapping-file';
|
|
941
956
|
if (program.mockNetworkHar) {
|
|
942
|
-
|
|
957
|
+
throw new ArgError(`--mock-network-har ${mockNetworkDeprecationMsg}`);
|
|
943
958
|
}
|
|
944
959
|
if (program.mockNetworkPattern) {
|
|
945
|
-
|
|
960
|
+
throw new ArgError(`--mock-network-pattern ${mockNetworkDeprecationMsg}`);
|
|
946
961
|
}
|
|
947
962
|
|
|
948
963
|
if (program.disableMockNetwork && program.overrideMappingFile) {
|
|
949
|
-
|
|
964
|
+
throw new ArgError('You can either use --disable-mock-network or --override-mapping-file');
|
|
950
965
|
}
|
|
951
966
|
|
|
952
967
|
if (!program.collectCodeCoverage && program.codeCoverageSourceMapPath) {
|
|
953
|
-
|
|
968
|
+
throw new ArgError('cannot set --code-coverage-source-map-path without passing --collect-code-coverage');
|
|
954
969
|
}
|
|
955
970
|
|
|
956
971
|
if (!program.collectCodeCoverage && program.codeCoverageReporter.length) {
|
|
957
|
-
|
|
972
|
+
throw new ArgError('cannot set --code-coverage-reporter without passing --collect-code-coverage');
|
|
958
973
|
}
|
|
959
974
|
|
|
960
975
|
if (!program.collectCodeCoverage && program.codeCoverageInclude.length) {
|
|
961
|
-
|
|
976
|
+
throw new ArgError('cannot set --code-coverage-include without passing --collect-code-coverage');
|
|
962
977
|
}
|
|
963
978
|
|
|
964
979
|
if (program.collectCodeCoverage && program.codeCoverageReporter && _.difference(program.codeCoverageReporter, CODE_COVERAGE_REPORTER_OPTIONS).length) {
|
|
965
980
|
const diff = _.difference(program.codeCoverageReporter, CODE_COVERAGE_REPORTER_OPTIONS);
|
|
966
|
-
|
|
981
|
+
throw new ArgError(`invalid --code-coverage-reporter parameters ${diff.join('/')}`);
|
|
967
982
|
}
|
|
968
983
|
|
|
969
984
|
program.codeCoverageReporter = program.codeCoverageReporter.length === 0 ? ['html', 'text'] : program.codeCoverageReporter;
|
|
@@ -982,7 +997,7 @@ module.exports = {
|
|
|
982
997
|
|
|
983
998
|
if (program.mode !== CLI_MODE.EXTENSION && usedExtensionOptions.length) {
|
|
984
999
|
const multi = usedExtensionOptions.length > 1 ? 'are' : 'is';
|
|
985
|
-
|
|
1000
|
+
throw new ArgError(`${usedExtensionOptions.map(key => extensionOnlyOptions[key]).join(' and ')} ${multi} only applicable in extension mode`);
|
|
986
1001
|
}
|
|
987
1002
|
|
|
988
1003
|
if (program.tmsFieldFile) {
|
|
@@ -1054,18 +1069,18 @@ module.exports = {
|
|
|
1054
1069
|
}
|
|
1055
1070
|
|
|
1056
1071
|
if (typeof program.baseUrl === 'boolean') {
|
|
1057
|
-
|
|
1072
|
+
throw new ArgError('base url cannot be used as a flag, and must contain a string value');
|
|
1058
1073
|
}
|
|
1059
1074
|
|
|
1060
1075
|
return ({
|
|
1061
|
-
testId: [].
|
|
1062
|
-
name: [].
|
|
1063
|
-
label: [].
|
|
1064
|
-
suites: [].
|
|
1065
|
-
suiteIds: [].
|
|
1066
|
-
testPlan: [].
|
|
1067
|
-
testPlanIds: [].
|
|
1068
|
-
files: [].
|
|
1076
|
+
testId: [program.testId].flat(),
|
|
1077
|
+
name: [program.name].flat(),
|
|
1078
|
+
label: [program.label].flat(),
|
|
1079
|
+
suites: [program.suite].flat(),
|
|
1080
|
+
suiteIds: [program.suiteId].flat(),
|
|
1081
|
+
testPlan: [program.testPlan].flat(),
|
|
1082
|
+
testPlanIds: [program.testPlanId].flat(),
|
|
1083
|
+
files: [program.files].flat(),
|
|
1069
1084
|
webpackConfig: program.webpackConfig,
|
|
1070
1085
|
reportFile: program.reportFile,
|
|
1071
1086
|
reportFileClassname: program.overrideReportFileClassname,
|
|
@@ -1090,7 +1105,9 @@ module.exports = {
|
|
|
1090
1105
|
mode: program.mode,
|
|
1091
1106
|
isRegressionBaselineRun: program.isRegressionBaselineRun,
|
|
1092
1107
|
browser: program.browser,
|
|
1108
|
+
beforeParallel: program.beforeParallel,
|
|
1093
1109
|
parallel: program.parallel,
|
|
1110
|
+
afterParallel: program.afterParallel,
|
|
1094
1111
|
canary: program.canary,
|
|
1095
1112
|
rerunFailedByRunId: program.rerunFailedByRunId,
|
|
1096
1113
|
disableGridCheck: program.disableGridCheck,
|
|
@@ -1116,7 +1133,7 @@ module.exports = {
|
|
|
1116
1133
|
|
|
1117
1134
|
// Extension
|
|
1118
1135
|
ext: program.ext,
|
|
1119
|
-
extensionLocation: [
|
|
1136
|
+
extensionLocation: [program.extensionPath || extHeadlessUrl].flat(),
|
|
1120
1137
|
extensionPath: program.extensionPath,
|
|
1121
1138
|
|
|
1122
1139
|
// Player
|
|
@@ -1136,6 +1153,7 @@ module.exports = {
|
|
|
1136
1153
|
externalLambdatestTunnelId: program.externalLambdatestTunnelId,
|
|
1137
1154
|
externalLambdatestUseWss: program.externalLambdatestUseWss || false,
|
|
1138
1155
|
externalLambdatestDisableAutomationTunneling: Boolean(program.externalLambdatestDisableAutomationTunneling),
|
|
1156
|
+
externalLambdatestMitm: Boolean(program.externalLambdatestMitm),
|
|
1139
1157
|
|
|
1140
1158
|
// Hooks
|
|
1141
1159
|
beforeTest: program.beforeTest,
|
package/runOptionsAgentFlow.js
CHANGED
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
const { ArgError } = require('./errors');
|
|
6
6
|
const _ = require('lodash');
|
|
7
7
|
const runOptionsUtils = require('./runOptionsUtils');
|
|
8
|
+
const analytics = require('./commons/testimAnalytics');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
*
|
|
11
12
|
* @param {import("commander").CommanderStatic} program
|
|
12
13
|
*/
|
|
13
14
|
function isAgentFlow(program) {
|
|
15
|
+
if (program.start) {
|
|
16
|
+
analytics.track(null, 'cli-start-command', { downloadBrowser: Boolean(program.downloadBrowser) });
|
|
17
|
+
}
|
|
14
18
|
if (program.startV2 || program.start || program.agent) {
|
|
15
19
|
return true;
|
|
16
20
|
}
|
|
@@ -73,6 +77,7 @@ async function runAgentFlow(program) {
|
|
|
73
77
|
canary: program.canary,
|
|
74
78
|
playerPath: program.playerPath,
|
|
75
79
|
playerRequirePath: program.playerRequirePath,
|
|
80
|
+
downloadBrowser: Boolean(program.downloadBrowser),
|
|
76
81
|
};
|
|
77
82
|
}
|
|
78
83
|
|
package/runner.js
CHANGED
|
@@ -25,7 +25,6 @@ const FREE_PLAN_MINIMUM_BROWSER_TIMEOUT = 30 * 60 * 1000;
|
|
|
25
25
|
const TestPlanRunner = require('./runners/TestPlanRunner');
|
|
26
26
|
const labFeaturesService = require('./services/labFeaturesService');
|
|
27
27
|
const featureAvailabilityService = require('./commons/featureAvailabilityService');
|
|
28
|
-
const featureFlagService = require('./commons/featureFlags');
|
|
29
28
|
|
|
30
29
|
const logger = require('./commons/logger').getLogger('runner');
|
|
31
30
|
|
|
@@ -269,7 +268,7 @@ async function init(options) {
|
|
|
269
268
|
await labFeaturesService.loadLabFeatures(projectById.id, companyByProjectId.activePlan);
|
|
270
269
|
}
|
|
271
270
|
|
|
272
|
-
if (options.lightweightMode && options.lightweightMode.type === 'turboMode' && (
|
|
271
|
+
if (options.lightweightMode && options.lightweightMode.type === 'turboMode' && (featureFlags.flags.highSpeedMode.getValue() === 'disabled' || options.company.planType === 'free')) {
|
|
273
272
|
delete options.lightweightMode;
|
|
274
273
|
}
|
|
275
274
|
|