@testim/testim-cli 3.207.0 → 3.213.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testim/testim-cli",
3
- "version": "3.207.0",
3
+ "version": "3.213.0",
4
4
  "description": "Command line interface for running Testing on your CI",
5
5
  "author": "Oren Rubin",
6
6
  "contributors": [{
@@ -60,8 +60,6 @@
60
60
  "bluebird": "3.7.2",
61
61
  "bluebird-retry": "0.11.0",
62
62
  "body-parser": "1.19.0",
63
- "winston": "3.3.3",
64
- "winston-transport": "4.4.0",
65
63
  "chalk": "4.1.0",
66
64
  "chrome-launcher": "0.13.4",
67
65
  "combine-source-map": "0.8.0",
@@ -89,7 +87,6 @@
89
87
  "moment": "2.25.3",
90
88
  "ms": "2.1.2",
91
89
  "npm": "6.14.11",
92
- "npm-registry-client": "8.6.0",
93
90
  "object-hash": "2.0.3",
94
91
  "ora": "4.0.4",
95
92
  "pako": "1.0.11",
@@ -107,6 +104,8 @@
107
104
  "threads": "0.12.0",
108
105
  "ua-parser-js": "0.7.28",
109
106
  "validate-npm-package-name": "3.0.0",
107
+ "winston": "3.3.3",
108
+ "winston-transport": "4.4.0",
110
109
  "ws": "8.2.3",
111
110
  "xml2js": "0.4.23",
112
111
  "yaml": "1.10.0"
@@ -116,7 +115,6 @@
116
115
  "testim": "cli.js"
117
116
  },
118
117
  "scripts": {
119
- "postpublish": "scripts/publish-docker.js",
120
118
  "test": "IS_UNIT_TEST=1 ./node_modules/mocha/bin/_mocha --timeout 2000 --reporter spec --exit --recursive \"./src/**/*.test.js\" --exclude ./src/codim/template.js/tests/examples/**/*.test.js",
121
119
  "test:watch": "IS_UNIT_TEST=1 ./node_modules/mocha/bin/_mocha --timeout 2000 --exit --recursive \"./src/**/*.test.js\" --exclude ./src/codim/template.js/tests/examples/**/*.test.js --watch",
122
120
  "test:cov": "nyc --reporter=lcov --reporter=text yarn test",
@@ -4,7 +4,6 @@ const TabService = require('./services/tabService');
4
4
  const PortSelector = require('./services/portSelector');
5
5
  const windowCreationListener = require('./services/windowCreationListener');
6
6
  const CookieUtils = require('./utils/cookieUtils');
7
- const downloadsApiUtils = require('./utils/downloadsApiUtils');
8
7
  const FrameLocator = require('./services/frameLocator');
9
8
  const Promise = require('bluebird');
10
9
  const { isDebuggerConnected } = require('../commons/detectDebugger');
@@ -46,11 +45,21 @@ class SeleniumTestPlayer {
46
45
 
47
46
  this.tabService.createSesion(id);
48
47
 
49
- this.sessionPlayer = new player(id, this.tabService, CookieUtils(this.driver),
50
- windowCreationListener, FrameLocator(this.driver), PortSelector,
51
- null, downloadsApiUtils, stepActionUtils,
52
- this.stepActionFactory, this.playbackTimeoutCalculator, testResultService.getSocket(),
53
- automationMode);
48
+ this.sessionPlayer = new player(
49
+ id,
50
+ this.tabService,
51
+ CookieUtils(this.driver),
52
+ windowCreationListener,
53
+ FrameLocator(this.driver),
54
+ PortSelector,
55
+ null,
56
+ null /* Not in use, placeholder for the order of arguments */,
57
+ stepActionUtils,
58
+ this.stepActionFactory,
59
+ this.playbackTimeoutCalculator,
60
+ testResultService.getSocket(),
61
+ automationMode,
62
+ );
54
63
 
55
64
  if (this.sessionPlayer.setShouldMonitorPerformance) {
56
65
  this.sessionPlayer.setShouldMonitorPerformance(shouldMonitorPerformance);
@@ -350,14 +350,16 @@ class TabService {
350
350
  mainTabPromise = this.driver.executeJS('return window.__isMainTestimTab').get('value');
351
351
  }
352
352
 
353
- return Promise.props({ title: this.driver.getTitle(), url: this.driver.getUrl(), isMainTab: mainTabPromise })
354
- .catch(err => {
353
+ return Promise.all([this.driver.getTitle(), this.driver.getUrl(), mainTabPromise]).then(
354
+ ([title, url, isMainTab]) => ({ title, url, isMainTab }),
355
+ err => {
355
356
  logger.error('failed to get url or title', { err });
356
357
  return {
357
358
  title: '',
358
359
  url: '',
359
360
  };
360
- });
361
+ },
362
+ );
361
363
  })
362
364
  .catch(err => {
363
365
  logger.error('failed to switch to tab', { tabId, err });
@@ -34,7 +34,10 @@ class InputFileStepAction extends StepAction {
34
34
  top: '10px',
35
35
  };
36
36
  return this.forceInputToBeVisible(target, options)
37
- .tapCatch(err => logger.error('failed to set input file in Safari recovery', { err }));
37
+ .catch(err => {
38
+ logger.error('failed to set input file in Safari recovery', { err });
39
+ throw err;
40
+ });
38
41
  }
39
42
 
40
43
  uploadFilesAndForceVisibility(gridLocalFiles, target) {
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const _ = require('lodash');
3
4
  const StepAction = require('./stepAction');
4
5
  const { eyeSdkService } = require('../utils/eyeSdkService');
5
6
  const logger = require('../../commons/logger').getLogger('pixel-validation-step-action');
@@ -9,6 +10,7 @@ class PixelValidationStepAction extends StepAction {
9
10
  async performAction() {
10
11
  const { shouldUseVisualGrid, applitoolsSdkConfig: config, testResultId } = this.context;
11
12
  this.runContext = this.context.getRunContext(undefined);
13
+ const finalParams = (this.runContext.incomingParams && this.runContext.incomingParams.final) || {};
12
14
  const batchId = (config.batch && config.batch.id) || testResultId;
13
15
  const eyeManager = await eyeSdkService.getManager(shouldUseVisualGrid, this.context.config.applitoolsConcurrency || 5, batchId, this.runContext.applitoolsIntegrationData);
14
16
  const targetElementData = this.getTarget() || {};
@@ -16,7 +18,12 @@ class PixelValidationStepAction extends StepAction {
16
18
  try {
17
19
  const openedEye = await eyeManager.openEyes({ driver: this.driver.client, config });
18
20
  const region = (this.step.action === 'element' && targetElementData.seleniumElement) || undefined;
19
- await openedEye.check({ settings: { region, fully: this.step.action === 'stitched' } });
21
+ const settings = { region, fully: this.step.action === 'stitched' };
22
+ if (finalParams.applitoolsStepSettings && _.isPlainObject(finalParams.applitoolsStepSettings)) {
23
+ Object.assign(settings, finalParams.applitoolsStepSettings);
24
+ logger.info('Applitools SDK step executed with applitoolsStepSettings parameter', { applitoolsStepSettings: finalParams.applitoolsStepSettings });
25
+ }
26
+ await openedEye.check({ settings });
20
27
  const eyesResults = await openedEye.close();
21
28
 
22
29
  result = { isApplitoolsSdkResult: true, success: true, eyesResults };
@@ -290,7 +290,7 @@ ImageCaptureUtils.prototype = {
290
290
  }
291
291
 
292
292
  return Promise.all([windowUtil.getFullPageSize(), windowUtil.getViewportSize()])
293
- .spread((fullPageSize, viewPortSize) => createStitchImage(fullPageSize, viewPortSize));
293
+ .then(([fullPageSize, viewPortSize]) => createStitchImage(fullPageSize, viewPortSize));
294
294
  },
295
295
  };
296
296
 
@@ -1,10 +1,10 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
3
  const Promise = require('bluebird');
4
4
  const utils = require('../../utils');
5
5
 
6
6
  class ScreenshotUtils {
7
- constructor(tabId, driver, options = { takeScreenshots: true}){
7
+ constructor(tabId, driver, options = { takeScreenshots: true }) {
8
8
  this.tabId = tabId;
9
9
  this.driver = driver;
10
10
  this.options = options;
@@ -30,12 +30,12 @@ class ScreenshotUtils {
30
30
  const devicePixelRatioPromise = this.currentDevicePixelRatio ? Promise.resolve(this.currentDevicePixelRatio) : this.getDevicePixelRatio();
31
31
  const getScreenshot = () => Promise.all([devicePixelRatioPromise, this.driver.takeScreenshot()]);
32
32
  return utils.runWithRetries(getScreenshot, MAX_RETRY_COUNT, SCREENSHOT_RETRY_DELAY)
33
- .spread((devicePixelRatio, image) => {
33
+ .then(([devicePixelRatio, image]) => {
34
34
  const base64 = image ? image.value : '';
35
- const dataUrl = "data:image/png;base64," + this.base64AddPadding(base64.replace(/[\r\n]/g, ''));
35
+ const dataUrl = `data:image/png;base64,${this.base64AddPadding(base64.replace(/[\r\n]/g, ''))}`;
36
36
  return {
37
37
  image: dataUrl,
38
- devicePixelRatio: devicePixelRatio
38
+ devicePixelRatio,
39
39
  };
40
40
  });
41
41
  }
@@ -52,7 +52,7 @@ class ScreenshotUtils {
52
52
  return this.driver.executeJS(devicePixelRatioJS).then(result => Promise.resolve(result.value));
53
53
  }
54
54
 
55
- forcePixelRatio(forceRatio = 1){
55
+ forcePixelRatio(forceRatio = 1) {
56
56
  this.currentDevicePixelRatio = forceRatio;
57
57
  return Promise.resolve();
58
58
  }
@@ -74,8 +74,8 @@ class StepActionUtils {
74
74
  return this.cookieUtils.set(cookieObject).then(cookie => [cookie]);
75
75
  }
76
76
 
77
- getNextDynamicParent(frameHandler, parentId, contextMatchThreshold, nextParentId, firstChildValues) {
78
- const code = `return ${codeSnippets.getNextDynamicParent(parentId, firstChildValues, contextMatchThreshold, nextParentId)}`;
77
+ getNextDynamicParent(frameHandler, dynamicParentOptions) {
78
+ const code = `return ${codeSnippets.getNextDynamicParent(dynamicParentOptions)}`;
79
79
  return this.driver.executeJS(code).then(res => res.value);
80
80
  }
81
81
  }
package/runOptions.js CHANGED
@@ -287,6 +287,8 @@ program
287
287
  .option('--tunnel [tunnel]', 'enable tunnel')
288
288
  .option('--tunnel-port [tunnel-port]', 'tunnel port address')
289
289
  .option('--tunnel-host-header [tunnel-host-header]', 'tunnel host header')
290
+ .option('--tunnel-region [tunnel-region]', 'ngrok tunnel region')
291
+ .option('--tunnel-diagnostics', 'collect ngrok tunnel diagnostics')
290
292
  .option('--tunnel-use-http-address [tunnel-use-http-address]', 'use http:// address instead of https://', false)
291
293
  .option('--external-lambdatest-tunnel-id [tunnel-id]', 'use existing lambdatest tunnel id')
292
294
  .option('--external-lambdatest-use-wss', 'use wss instead of ssh for LT', false)
@@ -527,14 +529,9 @@ module.exports = {
527
529
  return Promise.reject(new ArgError('missing --tunnel parameter'));
528
530
  }
529
531
 
530
- if (!program.tunnel && program.tunnelPort) {
532
+ if (!program.tunnel && [program.tunnelPort, program.tunnelHostHeader, program.tunnelRegion, program.tunnelDiagnostics].some(Boolean)) {
531
533
  return Promise.reject(new ArgError('missing --tunnel parameter'));
532
534
  }
533
-
534
- if (!program.tunnel && program.tunnelHostHeader) {
535
- return Promise.reject(new ArgError('missing --tunnel parameter'));
536
- }
537
-
538
535
  if (program.chromeExtraPrefs) {
539
536
  try {
540
537
  chromeExtraPrefs = require(path.join(process.cwd(), program.chromeExtraPrefs));
@@ -1089,6 +1086,8 @@ module.exports = {
1089
1086
  tunnel: program.tunnel,
1090
1087
  tunnelPort: program.tunnelPort,
1091
1088
  tunnelHostHeader: program.tunnelHostHeader,
1089
+ tunnelRegion: program.tunnelRegion,
1090
+ tunnelDiagnostics: program.tunnelDiagnostics,
1092
1091
  tunnelUseHttpAddress: program.tunnelUseHttpAddress,
1093
1092
  externalLambdatestTunnelId: program.externalLambdatestTunnelId,
1094
1093
  externalLambdatestUseWss: program.externalLambdatestUseWss || false,
@@ -63,6 +63,7 @@ class ParallelWorkerManager {
63
63
  const source = options.source || 'cli';
64
64
  const user = options.user;
65
65
  const companyPlan = options.company && options.company.planType;
66
+ const isStartUp = options.company && options.company.isStartUp;
66
67
  const projectName = options.projectData && options.projectData.name;
67
68
  const lightweightMode = options.lightweightMode;
68
69
  const sessionType = utils.getSessionType(options);
@@ -82,6 +83,7 @@ class ParallelWorkerManager {
82
83
  source,
83
84
  user,
84
85
  lightweightMode,
86
+ isStartUp,
85
87
  });
86
88
  return testStatus.testStartAndReport(wid, executionId, resultId, isRerun, testRetryKey);
87
89
  };
@@ -146,6 +148,7 @@ class ParallelWorkerManager {
146
148
  user,
147
149
  lightweightMode,
148
150
  logger,
151
+ isStartUp,
149
152
  });
150
153
  if (stopOnError && !testResult.success) {
151
154
  reject(new StopRunOnError());
@@ -31,7 +31,7 @@ function setLightweightAnalytics(properties, lightweightMode) {
31
31
  }
32
32
 
33
33
  function analyticsTestStart({
34
- executionId, projectId, testId, resultId, companyId, companyName, projectName, companyPlan, sessionType, source, user, lightweightMode,
34
+ executionId, projectId, testId, resultId, companyId, companyName, projectName, companyPlan, sessionType, source, user, lightweightMode, isStartUp,
35
35
  }) {
36
36
  const properties = setLightweightAnalytics({
37
37
  executionId,
@@ -44,13 +44,14 @@ function analyticsTestStart({
44
44
  companyPlan,
45
45
  sessionType,
46
46
  source: calcSource(source, user),
47
+ isStartUp,
47
48
  }, lightweightMode);
48
49
  analytics.trackWithCIUser('test-run-ci', properties);
49
50
  }
50
51
 
51
52
  function analyticsTestEnd({
52
53
  executionId, projectId, testId, resultId, result, companyId, companyName, projectName, companyPlan, sessionType, source, user, lightweightMode,
53
- logger,
54
+ logger, isStartUp,
54
55
  }) {
55
56
  try {
56
57
  const properties = setLightweightAnalytics({
@@ -65,6 +66,7 @@ function analyticsTestEnd({
65
66
  sessionType,
66
67
  mockNetworkEnabled: result.wasMockNetworkActivated,
67
68
  source: calcSource(source, user),
69
+ isStartUp,
68
70
  }, lightweightMode);
69
71
 
70
72
  if (result.success) {
package/testRunHandler.js CHANGED
@@ -18,6 +18,8 @@ const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
18
18
  const { preloadTests } = require('./commons/preloadTests');
19
19
 
20
20
  const RETRIES_ON_TIMEOUT = 3;
21
+ const MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE = 50 * 1000; // max size, in characters, of stringified run data sent over URL params. Chosen arbitrarily, this value should be changed according to data.
22
+ const canSendRunDataOverUrl = (runData) => JSON.stringify(runData).length < MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE;
21
23
 
22
24
  const TestRun = function (executionId, executionName, test, options, branchToUse, testRunStatus) {
23
25
  this._executionId = executionId;
@@ -175,7 +177,13 @@ TestRun.prototype.getRunRequestParams = async function () {
175
177
 
176
178
  if (this._options.lightweightMode && this._options.lightweightMode.general) {
177
179
  runRequestParams.company = this._options.company;
178
- runRequestParams.runData = this.getRunData();
180
+ const runData = this.getRunData();
181
+ runRequestParams.lightweightMode.isRunDataSentInUrl = canSendRunDataOverUrl(runData);
182
+ if (runRequestParams.lightweightMode.isRunDataSentInUrl) {
183
+ runRequestParams.runData = runData;
184
+ } else {
185
+ logger.warn(`Run data is too big to be sent as a URL param (lightweight mode). Run data size: ${JSON.stringify(runData).length} (limit: ${MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE} characters)`);
186
+ }
179
187
  runRequestParams.isLocalRun = Boolean(this._options.useLocalChromeDriver || this._options.useChromeLauncher);
180
188
  }
181
189
 
@@ -240,7 +248,7 @@ TestRun.prototype.clearTestResult = function () {
240
248
  const mustClearPreviousStepResults = !this.isAllowReportTestResultRetries() && (this._timeoutRetryCount > 1 || this._retryCount > 1);
241
249
 
242
250
  if (this._options.lightweightMode && this._options.lightweightMode.disableResults &&
243
- !mustClearPreviousStepResults) {
251
+ !mustClearPreviousStepResults && canSendRunDataOverUrl(runData)) {
244
252
  return Promise.resolve();
245
253
  }
246
254
 
@@ -8,7 +8,7 @@ const { timeoutMessages, testRunStatus, stepResult, runnerTestStatus } = require
8
8
  const logger = require('../commons/logger').getLogger('base-worker');
9
9
  const testResultService = require('../commons/socket/testResultService');
10
10
  const remoteStepService = require('../commons/socket/remoteStepService');
11
- const { isNetworkHealthy } = require('../commons/httpRequest');
11
+ const { isNetworkHealthy, didNetworkConnectivityTestFail } = require('../commons/httpRequest');
12
12
  const testimServicesApi = require('../commons/testimServicesApi');
13
13
  const gridService = require('../services/gridService');
14
14
  const LambdatestService = require('../services/lambdatestService');
@@ -264,13 +264,14 @@ class BaseWorker {
264
264
  return undefined;
265
265
  }
266
266
  };
267
+ const getNetworkErrorMessage = () => 'Due to network connectivity issues, Testim CLI has been unable to connect to the grid.\n' +
268
+ `Please make sure the CLI has stable access to the internet. ${didNetworkConnectivityTestFail() ? '(Internal: network connectivity test failed)' : ''}`;
267
269
 
268
- const buildError = (err) => {
269
- if (!isNetworkHealthy() && featureFlags.flags.errorMessageOnBadNetwork.isEnabled()) {
270
+ const buildError = (err, wasNetworkHealthy) => {
271
+ if (!wasNetworkHealthy && featureFlags.flags.errorMessageOnBadNetwork.isEnabled()) {
270
272
  return {
271
273
  errorType: NETWORK_ERROR,
272
- reason: 'Due to network connectivity issues, Testim CLI has been unable to connect to the grid.' +
273
- 'Please make sure the CLI has stable access to the internet.',
274
+ reason: getNetworkErrorMessage(),
274
275
  };
275
276
  }
276
277
 
@@ -324,17 +325,16 @@ class BaseWorker {
324
325
  };
325
326
 
326
327
  const onRunError = async (err, testRunHandler) => {
327
- if (!isNetworkHealthy() && featureFlags.flags.warnOnBadNetwork.isEnabled()) {
328
+ const wasNetworkHealthy = await isNetworkHealthy();
329
+ if (!wasNetworkHealthy && featureFlags.flags.warnOnBadNetwork.isEnabled()) {
328
330
  // intentional, we want to log to stderr:
329
331
  // eslint-disable-next-line no-console
330
- console.warn('Due to network connectivity issues, Testim CLI has been unable to connect to the grid.');
331
- // eslint-disable-next-line no-console
332
- console.warn('Please make sure the CLI has stable access to the internet.');
332
+ console.warn(getNetworkErrorMessage());
333
333
  }
334
334
  logger.warn('error on run', { err });
335
335
 
336
336
  const projectId = this.userData && this.userData.projectId;
337
- const { errorType, reason } = buildError(err);
337
+ const { errorType, reason } = buildError(err, wasNetworkHealthy);
338
338
  testimServicesApi.updateTestResult(projectId, this.testResultId, this.testId, {
339
339
  status: testRunStatus.COMPLETED,
340
340
  success: false,
@@ -87,7 +87,10 @@ class WorkerExtension extends BaseWorker {
87
87
  .then(url => {
88
88
  reporter.onWaitToTestStart(this.id);
89
89
  return Promise.all([
90
- driver.url(url).tap(() => { startStausDetails.driverUrlFinished = true; }).tapCatch(err => logger.error('error from driver.url', { err, testResultId, executionId, testId })),
90
+ driver.url(url).tap(() => { startStausDetails.driverUrlFinished = true; }).catch(err => {
91
+ logger.error('error from driver.url', { err, testResultId, executionId, testId, url, urlLength: url.length });
92
+ throw err;
93
+ }),
91
94
  testRunHandler.onStarted(TEST_START_TIMEOUT_MS).tap(() => { startStausDetails.testRunHandlerStartedFinished = true; }),
92
95
  ])
93
96
  .timeout(TEST_START_TIMEOUT_MS, timeoutMessages.TEST_START_TIMEOUT_MSG)
@@ -110,7 +113,7 @@ class WorkerExtension extends BaseWorker {
110
113
  .tap(testResult => {
111
114
  driver.unregisterToClosedBrowser(onBrowserClosed);
112
115
  if (this.lambdatestService.isLambdatestRun()) {
113
- return driver.executeJS(`lambda-status=${!testResult.success ? 'failed' : 'passed'}`).catch(() => {});
116
+ return driver.executeJS(`lambda-status=${!testResult.success ? 'failed' : 'passed'}`).catch(() => { });
114
117
  }
115
118
  return undefined;
116
119
  })
@@ -40,7 +40,10 @@ module.exports.getBrowserWithRetries = ({ getBrowserOnce, testPlayerFactory, rel
40
40
  return getBrowserOnce(player)
41
41
  .then((getBrowserRes) => player || getBrowserRes)
42
42
  .timeout(singleGetBrowserDuration, timeoutMessages.GET_BROWSER_TIMEOUT_MSG)
43
- .tapCatch(() => reporter.onGetBrowserFailure(workerId, projectId, ++failedAttempts))
43
+ .catch(err => {
44
+ reporter.onGetBrowserFailure(workerId, projectId, ++failedAttempts);
45
+ throw err;
46
+ })
44
47
  .tap(() => reporter.onGetBrowserSuccess(workerId, projectId))
45
48
  .catch(err => waitUntilBrowserTimeout(err, startTime, singleGetBrowserDuration, projectId, workerId, player, releaseSlotOnTestFinished));
46
49
  }, maxGetBrowserAttempts).catch(err => {
@@ -1,13 +0,0 @@
1
- "use strict";
2
-
3
- const Promise = require('bluebird');
4
-
5
- // Only for extension mode
6
- class DownloadsApiUtils {
7
-
8
- removeAndErase() {
9
- return Promise.resolve();
10
- }
11
- }
12
-
13
- module.exports = new DownloadsApiUtils();