@testim/testim-cli 3.243.0 → 3.246.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.
Files changed (42) hide show
  1. package/chromiumInstaller.js +92 -0
  2. package/cli.js +89 -87
  3. package/cliAgentMode.js +39 -114
  4. package/commons/chromedriverWrapper.js +4 -3
  5. package/commons/featureFlags.js +1 -0
  6. package/commons/getSessionPlayerRequire.js +2 -1
  7. package/commons/httpRequestCounters.test.js +9 -9
  8. package/commons/npmWrapper.js +3 -3
  9. package/commons/npmWrapper.test.js +1 -1
  10. package/commons/prepareRunner.js +2 -0
  11. package/commons/socket/baseSocketServiceSocketIO.js +2 -2
  12. package/commons/testimAnalytics.js +8 -3
  13. package/commons/testimServicesApi.js +45 -39
  14. package/errors.js +2 -1
  15. package/npm-shrinkwrap.json +595 -494
  16. package/package.json +2 -2
  17. package/player/WebDriverHttpRequest.js +45 -37
  18. package/player/chromeLauncherTestPlayer.js +2 -2
  19. package/player/services/playbackTimeoutCalculator.js +5 -1
  20. package/player/stepActions/RefreshStepAction.js +2 -4
  21. package/player/stepActions/inputFileStepAction.js +6 -2
  22. package/player/stepActions/scripts/doClick.js +3 -1
  23. package/player/stepActions/scripts/html5dragAction.js +3 -1
  24. package/player/stepActions/scripts/html5dragActionV2.js +3 -1
  25. package/player/stepActions/scripts/runCode.js +23 -6
  26. package/player/stepActions/scripts/scroll.js +1 -6
  27. package/player/stepActions/scripts/setText.js +4 -1
  28. package/player/stepActions/sfdcStepAction.js +27 -0
  29. package/player/stepActions/stepActionRegistrar.js +12 -0
  30. package/player/utils/screenshotUtils.js +2 -2
  31. package/player/utils/windowUtils.js +3 -3
  32. package/player/webdriver.js +51 -52
  33. package/runOptions.js +3 -1
  34. package/runner.js +44 -41
  35. package/services/lambdatestService.js +2 -2
  36. package/services/lambdatestService.test.js +2 -1
  37. package/testRunHandler.js +7 -3
  38. package/testRunStatus.js +2 -1
  39. package/utils.js +56 -21
  40. package/utils.test.js +27 -1
  41. package/workers/BaseWorker.js +9 -7
  42. package/workers/BaseWorker.test.js +11 -12
package/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const moment = require('moment');
4
- const retry = require('bluebird-retry');
4
+ const pRetry = require('p-retry');
5
5
  const _ = require('lodash');
6
6
  const Promise = require('bluebird');
7
7
  const fs = Promise.promisifyAll(require('fs'));
@@ -10,6 +10,10 @@ const { sessionType, testStatus: testStatusConst } = require('./commons/constant
10
10
  const path = require('path');
11
11
  const httpRequest = require('./commons/httpRequest');
12
12
  const decompress = require('decompress');
13
+ const os = require('os');
14
+
15
+ const HOMEDIR = os.homedir();
16
+ const TESTIM_BROWSER_DIR = path.join(HOMEDIR, '.testim-browser-profile');
13
17
 
14
18
  const OSS = [
15
19
  { osName: 'Linux', bs: { os: 'LINUX' }, sl: { platform: 'Linux' } },
@@ -71,20 +75,16 @@ function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
71
75
  return _.merge(selectedBrowser, selectedOS);
72
76
  }
73
77
 
74
- function handleAngularURIComponent(part) {
75
- return part.replace(/(~|\/)/g, m => ({ '~': '~~', '/': '~2F' }[m]));
76
- }
77
-
78
78
  function getTestUrl(editorUrl, projectId, testId, resultId, branch) {
79
79
  let testUrl = '';
80
- branch = branch ? handleAngularURIComponent(branch) : 'master';
80
+ branch = branch ? encodeURIComponent(branch) : 'master';
81
81
  if (projectId && testId) {
82
82
  testUrl = `${editorUrl}/#/project/${projectId}/branch/${branch}/test/${testId}`;
83
83
  if (resultId) {
84
84
  testUrl += `?result-id=${resultId}`;
85
85
  }
86
86
  }
87
- return encodeURI(testUrl);
87
+ return testUrl;
88
88
  }
89
89
 
90
90
  function isPromise(obj) {
@@ -100,17 +100,6 @@ function getDurationSec(ms) {
100
100
  return moment.duration(ms).asSeconds();
101
101
  }
102
102
 
103
- function runWithRetries(task, times, interval, predicate) {
104
- times = times || 3;
105
- interval = interval || 3000;
106
-
107
- const opt = { max_tries: times, interval, throw_original: true };
108
- if (predicate) {
109
- opt.predicate = predicate;
110
- }
111
- return retry(task, opt);
112
- }
113
-
114
103
  function getRunnerVersion() {
115
104
  try {
116
105
  const pack = require(`${__dirname}/package.json`);
@@ -198,7 +187,7 @@ function extractElementId(element) {
198
187
  }
199
188
 
200
189
  function isURL(path) {
201
- const legacyPattern = new RegExp('^(https?:\\/\\/)', 'i');
190
+ const legacyPattern = /^(https?:\/\/)/i;
202
191
 
203
192
  // https://gist.github.com/dperini/729294 (validator.js based on).
204
193
  const pattern = new RegExp(
@@ -247,7 +236,7 @@ function isURL(path) {
247
236
  }
248
237
 
249
238
  const DOWNLOAD_RETRY = 3;
250
- const download = async (url) => runWithRetries(() => httpRequest.download(url), DOWNLOAD_RETRY);
239
+ const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
251
240
 
252
241
  const downloadAndSave = async (url, saveToLocation) => {
253
242
  const res = await download(url);
@@ -315,6 +304,16 @@ function getPlanType(plan) {
315
304
  return 'free';
316
305
  }
317
306
 
307
+ /**
308
+ * @param time {number} in ms
309
+ * @returns {Promise}
310
+ */
311
+ function delay(time) {
312
+ return new Promise(((resolve) => {
313
+ setTimeout(resolve, time);
314
+ }));
315
+ }
316
+
318
317
  const calcPercentile = (arr, percentile) => {
319
318
  if (arr.length === 0) return 0;
320
319
  if (typeof percentile !== 'number') throw new TypeError('p must be a number');
@@ -361,12 +360,45 @@ function groupTestsByRetries(testResults = []) { // NOTE: This duplicates a func
361
360
  .value();
362
361
  }
363
362
 
363
+ async function getCdpAddressForHost(browserInstanceHost, timeout) {
364
+ try {
365
+ /**
366
+ Example response:
367
+ {
368
+ "Browser": "Chrome/81.0.4044.138",
369
+ "Protocol-Version": "1.3",
370
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
371
+ "V8-Version": "8.1.307.32",
372
+ "WebKit-Version": "537.36 (@8c6c7ba89cc9453625af54f11fd83179e23450fa)",
373
+ "webSocketDebuggerUrl": "ws://localhost:58938/devtools/browser/d4290379-ec08-4d03-a41a-ab9d9d4c36ac"
374
+ }
375
+ */
376
+ const debuggerAddress = await httpRequest.get(`http://${browserInstanceHost}/json/version`, undefined, undefined, timeout);
377
+ return debuggerAddress.webSocketDebuggerUrl;
378
+ } catch (e) {
379
+ throw new Error('unable to connect to devtools server');
380
+ }
381
+ }
382
+
383
+ function getArgsOnRemoteRunFailure() {
384
+ const { argv: args } = process;
385
+ if (!args.includes('--remoteRunId')) {
386
+ return undefined;
387
+ }
388
+ return {
389
+ remoteRunId: args[args.indexOf('--remoteRunId') + 1],
390
+ projectId: args[args.indexOf('--project') + 1],
391
+ token: args[args.indexOf('--token') + 1],
392
+ };
393
+ }
394
+
395
+
364
396
  module.exports = {
397
+ TESTIM_BROWSER_DIR,
365
398
  removePropertyFromObject,
366
399
  getTestUrl,
367
400
  getDuration,
368
401
  getDurationSec,
369
- runWithRetries,
370
402
  getRunnerVersion,
371
403
  getEnginesVersion,
372
404
  getEnginesVersionAsync,
@@ -395,4 +427,7 @@ module.exports = {
395
427
  isQuarantineAndNotRemoteRun,
396
428
  groupTestsByRetries,
397
429
  getPlanType,
430
+ delay,
431
+ getCdpAddressForHost,
432
+ getArgsOnRemoteRunFailure,
398
433
  };
package/utils.test.js CHANGED
@@ -32,11 +32,37 @@ describe('utils', () => {
32
32
  expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'normal-branch-name'))
33
33
  .to.equal('http://localhost:8080/#/project/project/branch/normal-branch-name/test/test?result-id=result');
34
34
  expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'branch/with/slashes'))
35
- .to.equal('http://localhost:8080/#/project/project/branch/branch~2Fwith~2Fslashes/test/test?result-id=result');
35
+ .to.equal('http://localhost:8080/#/project/project/branch/branch%2Fwith%2Fslashes/test/test?result-id=result');
36
36
  expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'branch with spaces'))
37
37
  .to.equal('http://localhost:8080/#/project/project/branch/branch%20with%20spaces/test/test?result-id=result');
38
38
  expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'encoded%20branch'))
39
39
  .to.equal('http://localhost:8080/#/project/project/branch/encoded%2520branch/test/test?result-id=result');
40
40
  });
41
41
  });
42
+
43
+ describe('getArgsOnRemoteRunFailure', () => {
44
+ let originalArgv;
45
+
46
+ beforeEach(() => {
47
+ originalArgv = process.argv;
48
+ });
49
+
50
+ afterEach(() => {
51
+ process.argv = originalArgv;
52
+ });
53
+
54
+ it('should return undefined if no remote run is current', () => {
55
+ process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id'];
56
+ expect(utils.getArgsOnRemoteRunFailure()).to.be.undefined;
57
+ });
58
+
59
+ it('should return details if remote run is current', () => {
60
+ process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id', '--remoteRunId', 'remote-run-id'];
61
+ expect(utils.getArgsOnRemoteRunFailure()).to.eql({
62
+ remoteRunId: 'remote-run-id',
63
+ projectId: 'project-id',
64
+ token: 'token',
65
+ });
66
+ });
67
+ });
42
68
  });
@@ -2,6 +2,7 @@
2
2
 
3
3
  const Bluebird = require('bluebird');
4
4
  const moment = require('moment');
5
+ const pRetry = require('p-retry');
5
6
  const ms = require('ms');
6
7
 
7
8
  const { timeoutMessages, testRunStatus, stepResult, runnerTestStatus } = require('../commons/constants');
@@ -116,7 +117,8 @@ class BaseWorker {
116
117
  try {
117
118
  perf.log('before getSlotOnce retries');
118
119
  let failedGetSlotAttempts = 0;
119
- let gridInfo = await utils.runWithRetries(async () => {
120
+
121
+ let gridInfo = await pRetry(async () => {
120
122
  const startTime = Date.now();
121
123
  try {
122
124
  return await Bluebird.resolve(this.getSlotOnce(testRunHandler))
@@ -124,10 +126,10 @@ class BaseWorker {
124
126
  } catch (error) {
125
127
  logger.error('error getting grid slot', { error, testId: this.testId, testResultId: this.testResultId, executionId: this.executionId });
126
128
  failedGetSlotAttempts++;
127
- await Bluebird.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
129
+ await utils.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
128
130
  throw error;
129
131
  }
130
- }, this.options.getBrowserRetries);
132
+ }, { retries: this.options.getBrowserRetries - 1, minTimeout: 0 });
131
133
  perf.log('after getSlotOnce retries');
132
134
 
133
135
  perf.log('before getBrowserOnce retries');
@@ -136,7 +138,7 @@ class BaseWorker {
136
138
  throw new Error('No free browser slots in desired grid');
137
139
  }
138
140
  let failedGetBrowserAttempts = 0;
139
- testPlayer = await utils.runWithRetries(async () => {
141
+ testPlayer = await pRetry(async () => {
140
142
  const startTime = Date.now();
141
143
  const player = this.initPlayer(testRunHandler);
142
144
  try {
@@ -162,11 +164,11 @@ class BaseWorker {
162
164
  player.onDone();
163
165
 
164
166
  if (!(error instanceof PageNotAvailableError)) {
165
- await Bluebird.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
167
+ await utils.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
166
168
  }
167
169
  throw error;
168
170
  }
169
- }, getBrowserRetriesNumber, undefined, (err) => !(err instanceof PageNotAvailableError));
171
+ }, { retries: getBrowserRetriesNumber - 1, minTimeout: 0 });
170
172
  perf.log('after getBrowserOnce retries');
171
173
  } catch (err) {
172
174
  await releasePlayer(this.id, this.releaseSlotOnTestFinished, projectId, testPlayer);
@@ -235,7 +237,7 @@ class BaseWorker {
235
237
  testResult.testRetryKey = testRetryKey;
236
238
  await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
237
239
  if (this.executionQueue.hasMoreTests() && !(this.options.lightweightMode && this.options.lightweightMode.general)) {
238
- await Bluebird.delay(DELAY_BETWEEN_TESTS);
240
+ await utils.delay(DELAY_BETWEEN_TESTS);
239
241
  }
240
242
  await this.runTestCleanup();
241
243
  if (shouldRerun) {
@@ -3,10 +3,9 @@ const proxyquire = require('proxyquire');
3
3
  const { expect, sinon } = require('../../test/utils/testUtils');
4
4
  const gridService = require('../services/gridService');
5
5
  const reporter = require('../reports/reporter');
6
- const Bluebird = require('bluebird');
7
6
  const { PageNotAvailableError, GridError, GetBrowserError } = require('../errors');
8
7
  const servicesApi = require('../commons/testimServicesApi');
9
- const { releasePlayer } = require('./workerUtils');
8
+ const utils = require('../utils');
10
9
 
11
10
 
12
11
  describe('BaseWorker', () => {
@@ -41,7 +40,7 @@ describe('BaseWorker', () => {
41
40
  getGridSlotStub = sinon.stub(gridService, 'getGridSlot').resolves({});
42
41
 
43
42
 
44
- sandbox.stub(Bluebird, 'delay').returns(Promise.resolve());
43
+ sandbox.stub(utils, 'delay').returns(Promise.resolve());
45
44
  sandbox.stub(reporter);
46
45
  });
47
46
 
@@ -57,7 +56,7 @@ describe('BaseWorker', () => {
57
56
  it('should get grid slot from server', async () => {
58
57
  await worker.getTestPlayer(testRunHandlerMock);
59
58
  sinon.assert.calledOnce(getGridSlotStub);
60
- sinon.assert.notCalled(Bluebird.delay);
59
+ sinon.assert.notCalled(utils.delay);
61
60
  });
62
61
  it('should retry getting slot until it succeeds', async () => {
63
62
  getGridSlotStub.onFirstCall().rejects();
@@ -65,7 +64,7 @@ describe('BaseWorker', () => {
65
64
 
66
65
  await worker.getTestPlayer(testRunHandlerMock);
67
66
  sinon.assert.calledTwice(getGridSlotStub);
68
- sinon.assert.calledTwice(Bluebird.delay);
67
+ sinon.assert.calledOnce(utils.delay);
69
68
  });
70
69
  it('should not proceed to getting browser if all retries used for getting a slot', async () => {
71
70
  getGridSlotStub.resolves({});
@@ -74,7 +73,7 @@ describe('BaseWorker', () => {
74
73
  sinon.assert.calledOnce(getGridSlotStub);
75
74
  sinon.assert.notCalled(worker.getBrowserOnce);
76
75
  sinon.assert.notCalled(worker.initPlayer);
77
- sinon.assert.notCalled(Bluebird.delay);
76
+ sinon.assert.notCalled(utils.delay);
78
77
  });
79
78
  });
80
79
 
@@ -84,7 +83,7 @@ describe('BaseWorker', () => {
84
83
  sinon.assert.calledOnce(getGridSlotStub);
85
84
  sinon.assert.calledOnce(worker.getBrowserOnce);
86
85
  sinon.assert.calledOnce(worker.initPlayer);
87
- sinon.assert.notCalled(Bluebird.delay);
86
+ sinon.assert.notCalled(utils.delay);
88
87
  });
89
88
  it('should retry getting browser until it succeeds', async () => {
90
89
  worker.getBrowserOnce.onFirstCall().rejects();
@@ -95,16 +94,16 @@ describe('BaseWorker', () => {
95
94
  sinon.assert.calledTwice(worker.getBrowserOnce);
96
95
  sinon.assert.calledTwice(worker.initPlayer);
97
96
  sinon.assert.calledOnce(testPlayerMock.onDone);
98
- sinon.assert.calledTwice(Bluebird.delay);
97
+ sinon.assert.calledOnce(utils.delay);
99
98
  });
100
99
  it('should not retry if page is not available', async () => {
101
100
  worker.getBrowserOnce.throws(() => new PageNotAvailableError());
102
101
 
103
- await expect(worker.getTestPlayer(testRunHandlerMock)).to.eventually.be.rejectedWith(PageNotAvailableError);
102
+ await expect(worker.getTestPlayer(testRunHandlerMock)).to.eventually.be.rejectedWith(Error);
104
103
  sinon.assert.calledOnce(getGridSlotStub);
105
104
  sinon.assert.calledOnce(worker.getBrowserOnce);
106
105
  sinon.assert.calledOnce(testPlayerMock.onDone);
107
- sinon.assert.notCalled(Bluebird.delay);
106
+ sinon.assert.notCalled(utils.delay);
108
107
  });
109
108
  it('should handle get grid error', async () => {
110
109
  worker.getBrowserOnce.throws(() => new GridError());
@@ -153,8 +152,8 @@ describe('BaseWorker', () => {
153
152
  getHybridGridProviderStub.onFirstCall().resolves({ provider: 'loacker', connectionDetails: { host: 'localhost', external: { user: 'user', key: 'password' } } });
154
153
  getHybridGridProviderStub.onSecondCall().resolves({ provider: 'nitzan', connectionDetails: { host: 'google.com', port: 443 } });
155
154
  getHybridGridProviderStub.onThirdCall().resolves({ provider: 'a', connectionDetails: { host: 'google.com', port: 4444 } });
156
- worker.getBrowserOnce.onFirstCall().rejects();
157
- worker.getBrowserOnce.onSecondCall().rejects();
155
+ worker.getBrowserOnce.onFirstCall().rejects(new Error());
156
+ worker.getBrowserOnce.onSecondCall().rejects(new Error());
158
157
  worker.getBrowserOnce.onThirdCall().resolves({});
159
158
 
160
159
  await worker.getTestPlayer(testRunHandlerMock);