@testim/testim-cli 3.196.0 → 3.200.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 (70) hide show
  1. package/README.md +1 -1
  2. package/cli/onExit.js +12 -1
  3. package/cli.js +5 -1
  4. package/commons/constants.js +0 -25
  5. package/commons/featureFlags.js +2 -0
  6. package/commons/npmWrapper.js +46 -14
  7. package/commons/npmWrapper.test.js +182 -6
  8. package/commons/socket/testResultService.js +4 -14
  9. package/commons/testimAnalytics.js +0 -1
  10. package/commons/testimDesiredCapabilitiesBuilder.js +0 -94
  11. package/commons/testimServicesApi.js +9 -79
  12. package/executionQueue.js +7 -4
  13. package/npm-shrinkwrap.json +960 -524
  14. package/package.json +3 -1
  15. package/player/stepActions/baseJsStepAction.js +5 -1
  16. package/player/stepActions/pixelValidationStepAction.js +28 -0
  17. package/player/stepActions/salesforceAutoLoginStepAction.js +5 -3
  18. package/player/stepActions/stepActionRegistrar.js +4 -48
  19. package/player/utils/eyeSdkService.js +230 -0
  20. package/reports/consoleReporter.js +29 -44
  21. package/reports/reporter.js +0 -21
  22. package/runOptions.js +13 -89
  23. package/runner.js +3 -44
  24. package/runners/{strategies/LocalStrategy.js → ParallelWorkerManager.js} +59 -68
  25. package/runners/TestPlanRunner.js +288 -67
  26. package/runners/runnerUtils.js +73 -0
  27. package/services/analyticsService.js +94 -0
  28. package/services/gridService.js +24 -20
  29. package/services/gridService.test.js +21 -21
  30. package/stepPlayers/hybridStepPlayback.js +4 -1
  31. package/stepPlayers/tdkHybridStepPlayback.js +1 -0
  32. package/testRunHandler.js +23 -5
  33. package/testRunStatus.js +18 -27
  34. package/utils.js +5 -5
  35. package/workers/BaseWorker.js +39 -39
  36. package/workers/BaseWorker.test.js +1 -1
  37. package/workers/WorkerExtensionSingleBrowser.js +6 -3
  38. package/commons/apkUploader/apkUploader.js +0 -46
  39. package/commons/apkUploader/apkUploaderFactory.js +0 -68
  40. package/commons/apkUploader/deviceFarmApkUploader.js +0 -41
  41. package/commons/apkUploader/saucelabsApkUploader.js +0 -36
  42. package/commons/apkUploader/testObjectApkUploader.js +0 -34
  43. package/player/mobile/mobileTestPlayer.js +0 -80
  44. package/player/mobile/mobileWebDriver.js +0 -155
  45. package/player/mobile/services/frameLocatorMock.js +0 -18
  46. package/player/mobile/services/mobilePortSelector.js +0 -22
  47. package/player/mobile/services/mobileTabService.js +0 -241
  48. package/player/mobile/utils/mobileScreenshotUtils.js +0 -46
  49. package/player/mobile/utils/mobileWindowUtils.js +0 -84
  50. package/player/stepActions/mobile/android/androidLocateStepAction.js +0 -122
  51. package/player/stepActions/mobile/android/androidLongClickStepAction.js +0 -12
  52. package/player/stepActions/mobile/android/androidScrollStepAction.js +0 -134
  53. package/player/stepActions/mobile/android/androidSpecialKeyStepAction.js +0 -22
  54. package/player/stepActions/mobile/android/androidSwipeStepAction.js +0 -32
  55. package/player/stepActions/mobile/androidGlobalActionStepAction.js +0 -12
  56. package/player/stepActions/mobile/androidTapStepAction.js +0 -19
  57. package/player/stepActions/mobile/androidTextChangeStepAction.js +0 -23
  58. package/player/stepActions/mobile/ios/iosLocateStepAction.js +0 -124
  59. package/player/stepActions/mobile/ios/iosScrollStepAction.js +0 -76
  60. package/runners/AnonymousTestPlanRunner.js +0 -106
  61. package/runners/BaseRunner.js +0 -42
  62. package/runners/BaseTestPlanRunner.js +0 -194
  63. package/runners/DeviceFarmRemoteRunner.js +0 -50
  64. package/runners/SchedulerRemoteRunner.js +0 -47
  65. package/runners/strategies/BaseStrategy.js +0 -86
  66. package/runners/strategies/DeviceFarmStrategy.js +0 -195
  67. package/runners/strategies/LocalDeviceFarmStrategy.js +0 -12
  68. package/runners/strategies/LocalTestStrategy.js +0 -14
  69. package/runners/strategies/Strategy.js +0 -17
  70. package/workers/WorkerAppium.js +0 -70
@@ -175,19 +175,19 @@ describe('gridService', () => {
175
175
 
176
176
  describe('getGridSlot', () => {
177
177
  let getGridByIdStub;
178
- let getGridByProjectStub;
178
+ let getGridByNameStub;
179
179
  let onGridSlot;
180
180
  let addItemToGridCacheStub;
181
181
 
182
182
  beforeEach(() => {
183
183
  getGridByIdStub = sinon.stub(servicesApi, 'getGridById').resolves({ grid: { gridId: 'gridId', type: 'gridId' }, status: 'success' });
184
- getGridByProjectStub = sinon.stub(servicesApi, 'getGridByProject').resolves({ grid: { gridId: 'gridId', type: 'gridName' }, status: 'success' });
184
+ getGridByNameStub = sinon.stub(servicesApi, 'getGridByName').resolves({ grid: { gridId: 'gridId', type: 'gridName' }, status: 'success' });
185
185
  addItemToGridCacheStub = sinon.stub(gridService, 'addItemToGridCache').callThrough();
186
186
  onGridSlot = sinon.stub().resolves();
187
187
  });
188
188
  afterEach(() => {
189
189
  getGridByIdStub.restore();
190
- getGridByProjectStub.restore();
190
+ getGridByNameStub.restore();
191
191
  addItemToGridCacheStub.restore();
192
192
  });
193
193
 
@@ -195,7 +195,7 @@ describe('gridService', () => {
195
195
  const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { useLocalChromeDriver: true }, 'workerId');
196
196
  expect(slot).to.eql({ mode: 'local' });
197
197
  sinon.assert.notCalled(getGridByIdStub);
198
- sinon.assert.notCalled(getGridByProjectStub);
198
+ sinon.assert.notCalled(getGridByNameStub);
199
199
  sinon.assert.notCalled(addItemToGridCacheStub);
200
200
  sinon.assert.calledOnce(onGridSlot);
201
201
  });
@@ -204,7 +204,7 @@ describe('gridService', () => {
204
204
  const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { useChromeLauncher: true }, 'workerId');
205
205
  expect(slot).to.eql({ mode: 'local' });
206
206
  sinon.assert.notCalled(getGridByIdStub);
207
- sinon.assert.notCalled(getGridByProjectStub);
207
+ sinon.assert.notCalled(getGridByNameStub);
208
208
  sinon.assert.notCalled(addItemToGridCacheStub);
209
209
  sinon.assert.calledOnce(onGridSlot);
210
210
  });
@@ -213,7 +213,7 @@ describe('gridService', () => {
213
213
  const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { host: 'localhost', port: 4444 }, 'workerId');
214
214
  expect(slot).to.shallowDeepEqual({ type: 'hostAndPort', host: 'localhost', port: 4444 });
215
215
  sinon.assert.notCalled(getGridByIdStub);
216
- sinon.assert.notCalled(getGridByProjectStub);
216
+ sinon.assert.notCalled(getGridByNameStub);
217
217
  sinon.assert.notCalled(addItemToGridCacheStub);
218
218
  sinon.assert.calledOnce(onGridSlot);
219
219
  });
@@ -222,7 +222,7 @@ describe('gridService', () => {
222
222
  const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId');
223
223
  expect(slot).to.shallowDeepEqual({ type: 'gridId', gridId: 'gridId' });
224
224
  sinon.assert.calledOnce(getGridByIdStub);
225
- sinon.assert.notCalled(getGridByProjectStub);
225
+ sinon.assert.notCalled(getGridByNameStub);
226
226
  sinon.assert.calledOnce(addItemToGridCacheStub);
227
227
  sinon.assert.calledOnce(onGridSlot);
228
228
  });
@@ -230,7 +230,7 @@ describe('gridService', () => {
230
230
  it('should get grid from server when passing grid name', async () => {
231
231
  const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { grid: 'gridName' }, 'workerId');
232
232
  expect(slot).to.shallowDeepEqual({ type: 'gridName', gridId: 'gridId' });
233
- sinon.assert.calledOnce(getGridByProjectStub);
233
+ sinon.assert.calledOnce(getGridByNameStub);
234
234
  sinon.assert.notCalled(getGridByIdStub);
235
235
  sinon.assert.calledOnce(addItemToGridCacheStub);
236
236
  sinon.assert.calledOnce(onGridSlot);
@@ -282,9 +282,9 @@ describe('gridService', () => {
282
282
  });
283
283
 
284
284
  it('should call releaseGridSlot with the correct parameters', async () => {
285
- gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
285
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
286
286
  await gridService.releaseGridSlot('workerId', 'projectId');
287
- sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId', 'gridId', 'chrome');
287
+ sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId', 'gridId', 'chrome');
288
288
  });
289
289
 
290
290
  it('should not call releaseGridSlot if the workerId is not in the cache', async () => {
@@ -293,16 +293,16 @@ describe('gridService', () => {
293
293
  });
294
294
 
295
295
  it('should not call releaseGridSlot if no slotId', async () => {
296
- gridService.addItemToGridCache('workerId', 'gridId');
296
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId');
297
297
  await gridService.releaseGridSlot('workerId', 'projectId');
298
298
  sinon.assert.notCalled(releaseGridSlotStub);
299
299
  });
300
300
 
301
301
  it('should handle releaseGridSlot request error', async () => {
302
302
  releaseGridSlotStub.rejects({ });
303
- gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
303
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
304
304
  await gridService.releaseGridSlot('workerId', 'projectId');
305
- sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId', 'gridId', 'chrome');
305
+ sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId', 'gridId', 'chrome');
306
306
  });
307
307
  });
308
308
 
@@ -322,15 +322,15 @@ describe('gridService', () => {
322
322
  });
323
323
 
324
324
  it('should send keepAlive to server on an interval', async () => {
325
- gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
325
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
326
326
  await gridService.keepAlive.start('projectId');
327
327
  clock.tick(10010);
328
- sinon.assert.calledOnceWithExactly(keepAliveStub, 'projectId', [{ gridId: 'gridId', slotId: 'slotId', browser: 'chrome' }]);
328
+ sinon.assert.calledOnceWithExactly(keepAliveStub, 'projectId', [{ gridId: 'gridId', companyId: 'companyId', slotId: 'slotId', browser: 'chrome' }]);
329
329
  await gridService.releaseGridSlot('workerId', 'projectId');
330
330
  });
331
331
 
332
332
  it('should send keepAlive to server every 10 seconds', async () => {
333
- gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
333
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
334
334
  await gridService.keepAlive.start('projectId');
335
335
  clock.tick(30010);
336
336
  sinon.assert.calledThrice(keepAliveStub);
@@ -345,7 +345,7 @@ describe('gridService', () => {
345
345
 
346
346
  it('should handle keepAlive request error', async () => {
347
347
  keepAliveStub.rejects({ });
348
- gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
348
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
349
349
  await gridService.keepAlive.start('projectId');
350
350
  clock.tick(10010);
351
351
  sinon.assert.calledOnce(keepAliveStub);
@@ -353,13 +353,13 @@ describe('gridService', () => {
353
353
  });
354
354
 
355
355
  it('should release all slots when ending', async () => {
356
- gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
357
- gridService.addItemToGridCache('workerId1', 'gridId', 'slotId1', 'firefox');
356
+ gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
357
+ gridService.addItemToGridCache('workerId1', 'companyId', 'gridId', 'slotId1', 'firefox');
358
358
  await gridService.keepAlive.start('projectId');
359
359
  await gridService.keepAlive.end('projectId');
360
360
  sinon.assert.calledTwice(releaseGridSlotStub);
361
- sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId', 'gridId', 'chrome');
362
- sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId1', 'gridId', 'firefox');
361
+ sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId', 'gridId', 'chrome');
362
+ sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId1', 'gridId', 'firefox');
363
363
  });
364
364
 
365
365
  it('should not release slots if there is no slots in use', async () => {
@@ -6,6 +6,7 @@ const perfLogger = require('../commons/performance-logger');
6
6
  const MemoryFS = require('memory-fs');
7
7
  const mfs = new MemoryFS();
8
8
  const AbortController = require("abort-controller");
9
+ const logger = require('../commons/logger').getLogger('hybrid-step-playback');
9
10
 
10
11
  /**
11
12
  * @type {Map<string, import("abort-controller")>}
@@ -119,10 +120,12 @@ module.exports.execute = async function execute(step, context, driver, loginData
119
120
  }
120
121
 
121
122
  return { success: false, shouldRetry: false, reason: 'unknown hybrid format ' + hybridFunction.type };
123
+ } catch (err) {
124
+ logger.log('error running hybrid step', { err });
122
125
  } finally {
123
126
  runningStepsAbortControllersRegistry.delete(context.stepResultId);
124
127
  }
125
- }
128
+ };
126
129
 
127
130
  module.exports.abort = function abort(stepResultId) {
128
131
  const abortController = runningStepsAbortControllersRegistry.get(stepResultId);
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const _ = require('lodash');
3
4
  const sessionPlayerInit = require('../commons/getSessionPlayerRequire');
4
5
  const perfLogger = require('../commons/performance-logger');
5
6
  const { guid } = require('../utils');
package/testRunHandler.js CHANGED
@@ -7,6 +7,7 @@ const testimServicesApi = require('./commons/testimServicesApi');
7
7
  const { timeoutMessages, CLI_MODE } = require('./commons/constants');
8
8
  const logger = require('./commons/logger').getLogger('test-run-handler');
9
9
  const perf = require('./commons/performance-logger');
10
+ const { URL } = require('url');
10
11
  const Promise = require('bluebird');
11
12
  const _ = require('lodash');
12
13
  const remoteStepPlayback = require('./stepPlayers/remoteStepPlayback');
@@ -241,7 +242,7 @@ TestRun.prototype.clearTestResult = function () {
241
242
  // make sure the execution is created by now
242
243
  await this._testRunStatus.waitForExecutionStartedFinished();
243
244
  // we probably can save this backend call by initializing the execution
244
- return testResultService.clearTestResult(this._options.project, this._testResultId, this._testId, {
245
+ return testimServicesApi.clearTestResult(this._options.project, this._testResultId, this._testId, {
245
246
  name: this._testName,
246
247
  resultId: this._testResultId,
247
248
  status: 'pending',
@@ -251,9 +252,6 @@ TestRun.prototype.clearTestResult = function () {
251
252
  testRetryKey: this.getRetryKey(),
252
253
  });
253
254
  });
254
- if (this._testRunStatus.asyncReporting) {
255
- return Promise.resolve();
256
- }
257
255
  return this.clearTestResultFinished;
258
256
  };
259
257
 
@@ -316,6 +314,26 @@ TestRun.prototype.isRetryKeyMismatch = function (testResult) {
316
314
  return testResult.testRetryKey && (testResult.testRetryKey !== this.getRetryKey());
317
315
  };
318
316
 
317
+ TestRun.prototype.validateRunConfig = function () {
318
+ const baseUrl = this.getBaseUrl();
319
+ const { browserValue } = this.getRunConfig();
320
+
321
+ if (baseUrl && browserValue === 'safari') {
322
+ let parsedUrl;
323
+ try {
324
+ parsedUrl = new URL(baseUrl);
325
+ } catch (err) {
326
+ // ignore invalid URLs (missing http:// or https:// prefix)
327
+ return;
328
+ }
329
+ const { username, password } = parsedUrl;
330
+
331
+ if (username || password) {
332
+ throw new Error('Basic authentication in URL is not supported in Safari');
333
+ }
334
+ }
335
+ };
336
+
319
337
  TestRun.prototype.onStarted = function (startTimeout) {
320
338
  return new Promise(resolve => {
321
339
  // We can't leave the test result as it may remove other listeners as well
@@ -467,7 +485,7 @@ TestRun.prototype.onCompleted = function () {
467
485
  waitForTestEnd();
468
486
  }
469
487
  } catch (err) {
470
- logger.error('failed to check is complete', {err});
488
+ logger.error('failed to check is complete', { err });
471
489
  waitForTestEnd();
472
490
  }
473
491
  }, 3000);
package/testRunStatus.js CHANGED
@@ -23,11 +23,11 @@ const gitRepoUrl = process.env.GIT_URL || process.env.CIRCLE_REPOSITORY_URL;
23
23
  const runnerVersion = utils.getRunnerVersion();
24
24
 
25
25
 
26
- function runHook(fn, object) {
26
+ function runHook(fn, ...args) {
27
27
  if (!fn || typeof fn !== 'function') {
28
28
  return Promise.resolve();
29
29
  }
30
- return Promise.try(() => fn(object) || {}).catch(err => {
30
+ return Promise.try(() => fn(...args) || {}).catch(err => {
31
31
  logger.warn('failed to run hook', { err });
32
32
  throw new ArgError(`failed to run hook promise ${err.message}`);
33
33
  });
@@ -43,7 +43,6 @@ const RunStatus = function (testInfoList, options, testPlanId, branchToUse) {
43
43
  this.exportsGlobal = {};
44
44
  this.testInfoList = testInfoList;
45
45
 
46
- this.asyncReporting = false; // whether or not we wait for result reporting
47
46
  this.executionStartedPromise = Promise.resolve();
48
47
 
49
48
  const browserNames = utils.getUniqBrowsers(options, testInfoList);
@@ -92,9 +91,6 @@ const RunStatus = function (testInfoList, options, testPlanId, branchToUse) {
92
91
  RunStatus.prototype.waitForExecutionStartedFinished = function () {
93
92
  return this.executionStartedPromise;
94
93
  };
95
- RunStatus.prototype.setAsyncReporting = function (isAsyncReportingEnabled = false) {
96
- this.asyncReporting = isAsyncReportingEnabled;
97
- };
98
94
  RunStatus.prototype.getTestResult = function (resultId) {
99
95
  return this.testRunStatus[resultId];
100
96
  };
@@ -157,7 +153,7 @@ RunStatus.prototype.updateTestStatusRunning = function (test, executionId, testR
157
153
  return this.executionStartedPromise;
158
154
  }
159
155
 
160
- const res = servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
156
+ return servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
161
157
  .catch(err => {
162
158
  logger.error('failed to upload test data artifact (runner)', { err });
163
159
  return '';
@@ -167,21 +163,15 @@ RunStatus.prototype.updateTestStatusRunning = function (test, executionId, testR
167
163
  delete testConfig.testData;
168
164
  testConfig.testDataUrl = testDataUrl;
169
165
  await this.executionStartedPromise;
170
- return servicesApi.updateTestStatus('RUNNING', executionId, test.testId, test.resultId, test.startTime, null, null, null, testConfig, projectId, remoteRunId, testRetryKey);
166
+ return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
171
167
  });
172
- if (this.asyncReporting) {
173
- // silence is golden
174
- } else {
175
- return res;
176
- }
177
- return this.executionStartedPromise.then(() => servicesApi.updateTestStatus('RUNNING', executionId, test.testId, test.resultId, test.startTime, null, null, null, test.config, projectId, remoteRunId, testRetryKey));
178
168
  };
179
169
 
180
170
  RunStatus.prototype.testStartReport = function (test, executionId, testRetryKey) {
181
171
  if (utils.isQuarantineAndNotRemoteRun(test, this.options)) {
182
172
  return Promise.resolve();
183
173
  }
184
- return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }))
174
+ return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
185
175
  .then(params => {
186
176
  this.options.runParams[test.resultId] = test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
187
177
  test.startTime = Date.now();
@@ -201,9 +191,6 @@ RunStatus.prototype.onGridSlot = function (executionId, resultId, gridInfo) {
201
191
  const test = this.getTestResult(resultId);
202
192
  test.config.gridInfo = Object.assign({}, gridInfo, { key: undefined, user: undefined });
203
193
  logger.info('on get grid info', { gridInfo: test.config.gridInfo });
204
- // we don't wait for this call since it's only for analytics/debugging sake.
205
- const p = this.updateTestStatusRunning(test, executionId);
206
- p.catch(() => {}); // suppress unhandled rejection
207
194
  };
208
195
 
209
196
  RunStatus.prototype.reportTestStatus = function (workerId, result, test, isRerun) {
@@ -277,11 +264,11 @@ RunStatus.prototype.testEnd = function (wid, result, executionId, sessionId, isR
277
264
  return test;
278
265
  };
279
266
 
280
- RunStatus.prototype.testEndReport = async function (test, executionId, result) {
267
+ RunStatus.prototype.testEndReport = async function (test, executionId, result, testResultUpdates) {
281
268
  const globalParameters = result.exportsGlobal;
282
269
  try {
283
270
  try {
284
- await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }));
271
+ await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
285
272
  } catch (err) {
286
273
  logger.error('HOOK threw an error', { test: test.testId, err });
287
274
  // eslint-disable-next-line no-console
@@ -291,16 +278,23 @@ RunStatus.prototype.testEndReport = async function (test, executionId, result) {
291
278
  return undefined;
292
279
  }
293
280
 
294
- return await servicesApi.updateTestStatus('FINISHED', executionId, test.testId, test.resultId, test.startTime, result.endTime, test.success, test.failureReason, null, this.options.project, this.options.remoteRunId, undefined, 5);
281
+ return await servicesApi.updateTestStatus(this.options.project, executionId, test.testId, test.resultId, 'FINISHED', {
282
+ startTime: test.startTime,
283
+ endTime: result.endTime,
284
+ success: test.success,
285
+ failureReason: test.failureReason,
286
+ remoteRunId: this.options.remoteRunId,
287
+ ...testResultUpdates,
288
+ }, 5);
295
289
  } catch (err) {
296
290
  logger.error('Failed to update test finished', { err });
297
291
  throw err;
298
292
  }
299
293
  };
300
294
 
301
- RunStatus.prototype.testEndAndReport = function (wid, result, executionId, sessionId, isRerun) {
295
+ RunStatus.prototype.testEndAndReport = function (wid, result, executionId, sessionId, isRerun, testResultUpdates) {
302
296
  const test = this.testEnd(wid, result, executionId, sessionId, isRerun);
303
- return this.testEndReport(test, executionId, result);
297
+ return this.testEndReport(test, executionId, result, testResultUpdates);
304
298
  };
305
299
 
306
300
  RunStatus.prototype.calcTestRunStatus = function () {
@@ -402,10 +396,7 @@ RunStatus.prototype.executionStart = function (executionId, projectId, startTime
402
396
  const ret = servicesApi.reportExecutionStarted(data);
403
397
  this.executionStartedPromise = ret;
404
398
  ret.catch(e => logger.error(e));
405
- if (!this.asyncReporting) {
406
- return ret;
407
- }
408
- return undefined;
399
+ return ret;
409
400
  });
410
401
  };
411
402
 
package/utils.js CHANGED
@@ -19,7 +19,7 @@ const OSS = [
19
19
  { osName: 'Windows 7', bs: { os: 'WINDOWS', os_version: '7' }, sl: { platform: 'Windows 7' } },
20
20
  { osName: 'Windows XP', bs: { os: 'WINDOWS', os_version: 'XP' }, sl: { platform: 'Windows XP' } },
21
21
  { osName: 'macOS Big Sur', bs: { os: 'OS X', os_version: 'Big Sur', safari_version: '14' }, sl: { platform: 'macOS 11', safari_version: '14' } },
22
- { osName: 'macOS Catalina',bs: { os: 'OS X', os_version: 'Catalina', safari_version: '13' }, sl: { platform: 'macOS 10.15', safari_version: '13' } },
22
+ { osName: 'macOS Catalina', bs: { os: 'OS X', os_version: 'Catalina', safari_version: '13' }, sl: { platform: 'macOS 10.15', safari_version: '13' } },
23
23
  { osName: 'macOS Mojave', bs: { os: 'OS X', os_version: 'Mojave', safari_version: '12' }, sl: { platform: 'macOS 10.14', safari_version: '12' } },
24
24
  { osName: 'macOS High Sierra', bs: { os: 'OS X', os_version: 'High Sierra', safari_version: '11' }, sl: { platform: 'macOS 10.13', safari_version: '11' } },
25
25
  { osName: 'macOS Sierra', bs: { os: 'OS X', os_version: 'Sierra', safari_version: '10' }, sl: { platform: 'macOS 10.12', safari_version: '10.0' } },
@@ -35,11 +35,11 @@ const OSS = [
35
35
  ];
36
36
 
37
37
  const BROWSERS = [
38
- { browserName: 'Chrome', bs: { browser: 'Chrome', browser_version: '92' }, sl: { browserName: 'chrome', version: '92.0' }, browserValue: 'chrome' },
38
+ { browserName: 'Chrome', bs: { browser: 'Chrome', browser_version: '94' }, sl: { browserName: 'chrome', version: '94.0' }, browserValue: 'chrome' },
39
39
  { browserName: 'Firefox', bs: { browser: 'Firefox', browser_version: '89' }, sl: { browserName: 'firefox', version: '89.0' }, browserValue: 'firefox' },
40
40
  { browserName: 'Safari', bs: { browser: 'Safari' }, sl: { browserName: 'safari' }, browserValue: 'safari' },
41
- { browserName: 'Edge', bs: { browser: 'Edge', browser_version: '17' }, sl: { browserName: 'MicrosoftEdge', version: '17.17134' }, browserValue: 'edge' },
42
- { browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '92' }, sl: { browserName: 'MicrosoftEdge', version: '92' }, synonyms: ['edge-chromium'], browserValue: 'edge-chromium', seleniumName: 'MicrosoftEdge' },
41
+ { browserName: 'Edge', bs: { browser: 'Edge', browser_version: '18' }, sl: { browserName: 'MicrosoftEdge', version: '18.17763' }, browserValue: 'edge' },
42
+ { browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '94' }, sl: { browserName: 'MicrosoftEdge', version: '94' }, synonyms: ['edge-chromium'], browserValue: 'edge-chromium', seleniumName: 'MicrosoftEdge' },
43
43
  { browserName: 'Internet Explorer 11', bs: { browser: 'IE', browser_version: '11' }, sl: { browserName: 'internet explorer', version: '11.0' }, synonyms: ['ie11'], browserValue: 'ie11' },
44
44
  { browserName: 'Browser', bs: {}, sl: { browserName: 'Browser' }, browserValue: 'browser' },
45
45
  { browserName: 'Android', bs: { browserName: 'android' }, sl: {}, browserValue: 'android' },
@@ -143,7 +143,7 @@ function getEnvironmentGitBranch() {
143
143
  }
144
144
 
145
145
  function getUniqBrowsers(options, testList) {
146
- if ((options.testConfigNames.length || options.testConfigIds.length || options.testPlan.length || options.testPlanIds.length || options.testObject) && !options.browser) {
146
+ if ((options.testConfigNames.length || options.testConfigIds.length || options.testPlan.length || options.testPlanIds.length) && !options.browser) {
147
147
  return _.uniq(testList.map(t => t.runConfig.browserValue));
148
148
  }
149
149
  return [options.browser.toLowerCase()];
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const Promise = require('bluebird');
3
+ const Bluebird = require('bluebird');
4
4
  const moment = require('moment');
5
5
  const ms = require('ms');
6
6
 
@@ -38,12 +38,26 @@ function buildFailureResult(testId, testName, resultId, reason) {
38
38
  }
39
39
 
40
40
  class BaseWorker {
41
- constructor(executionQueue, customExtensionLocalLocation) {
41
+ constructor(executionQueue, options, customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored, releaseSlotOnTestFinished = true) {
42
42
  this.lambdatestService = new LambdatestService();
43
+
43
44
  this.id = BaseWorker.getWorkerId();
44
45
  this.executionQueue = executionQueue;
45
46
  this.customExtensionLocalLocation = customExtensionLocalLocation;
46
- this.runTest = Promise.method(this.runTest);
47
+
48
+ this.isCodeMode = options.files && options.files.length > 0;
49
+ this.baseUrl = options.baseUrl;
50
+ this.isRegressionBaselineRun = options.isRegressionBaselineRun;
51
+ this.testRunTimeout = options.timeout;
52
+ this.onTestStarted = onTestStarted;
53
+ this.onTestCompleted = onTestCompleted;
54
+ this.onGridSlot = onGridSlot;
55
+ this.onTestIgnored = onTestIgnored;
56
+ this.releaseSlotOnTestFinished = releaseSlotOnTestFinished;
57
+
58
+ this.userData = options.userData;
59
+ this.executionId = executionId;
60
+ this.options = options;
47
61
  }
48
62
 
49
63
  static getWorkerId() {
@@ -69,7 +83,7 @@ class BaseWorker {
69
83
  throw new NotImplementedError(true);
70
84
  }
71
85
 
72
- runTestOnce(testRunHandler, player) {
86
+ async runTestOnce(testRunHandler, player) {
73
87
  testRunHandler.setSessionId(player.getSessionId());
74
88
  logger.info('Test run started', {
75
89
  testId: testRunHandler.getTestId(),
@@ -77,26 +91,9 @@ class BaseWorker {
77
91
  seleniumSession: player.getSessionId(),
78
92
  });
79
93
  if (this.options.lightweightMode && this.options.lightweightMode.disableResults) {
80
- return Promise.resolve();
94
+ return undefined;
81
95
  }
82
- return testRunHandler.clearTestResult();
83
- }
84
-
85
- start(options, onStart, onResult, executionId, onGridSlot, onTestIgnored, releaseSlotOnTestFinished) {
86
- this.isCodeMode = options.files.length > 0;
87
- this.baseUrl = options.baseUrl;
88
- this.isRegressionBaselineRun = options.isRegressionBaselineRun;
89
- this.testRunTimeout = options.timeout;
90
- this.onStart = onStart;
91
- this.onResult = onResult;
92
- this.onGridSlot = onGridSlot;
93
- this.onTestIgnored = onTestIgnored;
94
- this.releaseSlotOnTestFinished = releaseSlotOnTestFinished;
95
-
96
- this.userData = options.userData;
97
- this.executionId = executionId;
98
- this.options = options;
99
- return this.run();
96
+ return await testRunHandler.clearTestResult();
100
97
  }
101
98
 
102
99
  handleQuarantine(testRunHandler) {
@@ -124,13 +121,12 @@ class BaseWorker {
124
121
  let gridInfo = await utils.runWithRetries(async () => {
125
122
  const startTime = Date.now();
126
123
  try {
127
- return await Promise.resolve()
128
- .then(() => this.getSlotOnce(testRunHandler))
124
+ return await Bluebird.resolve(this.getSlotOnce(testRunHandler))
129
125
  .timeout(this.options.getBrowserTimeout, timeoutMessages.GET_BROWSER_TIMEOUT_MSG);
130
126
  } catch (error) {
131
127
  logger.error('error getting grid slot', { error, testId: this.testId, testResultId: this.testResultId, executionId: this.executionId });
132
128
  failedGetSlotAttempts++;
133
- await Promise.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
129
+ await Bluebird.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
134
130
  throw error;
135
131
  }
136
132
  }, this.options.getBrowserRetries);
@@ -154,7 +150,7 @@ class BaseWorker {
154
150
  this.options.gridData.host = gridInfo.host;
155
151
  this.options.gridData.failedGetBrowserAttempts = failedGetBrowserAttempts;
156
152
  const getSessionTimeout = Math.max(this.lambdatestService.getSessionTimeout, this.options.getSessionTimeout);
157
- const getBrowserRes = await Promise.resolve()
153
+ const getBrowserRes = await Bluebird.resolve()
158
154
  .log('before getBrowserOnce')
159
155
  .then(() => this.getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo))
160
156
  .log('after getBrowserOnce')
@@ -169,7 +165,7 @@ class BaseWorker {
169
165
  player.onDone();
170
166
 
171
167
  if (!(error instanceof PageNotAvailableError)) {
172
- await Promise.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
168
+ await Bluebird.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
173
169
  }
174
170
  throw error;
175
171
  }
@@ -190,14 +186,15 @@ class BaseWorker {
190
186
  }
191
187
 
192
188
  async runTest(testRunHandler, customExtensionLocalLocation, shouldRerun) {
189
+ perf.log('inside runTest');
193
190
  const projectId = this.userData && this.userData.projectId;
194
191
  const quarantineResult = this.handleQuarantine(testRunHandler);
195
192
  if (quarantineResult) {
196
193
  return quarantineResult;
197
194
  }
198
195
 
199
- perf.log('before runTest onStart');
200
- await this.onStart(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
196
+ perf.log('before runTest onTestStarted');
197
+ await this.onTestStarted(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
201
198
  const testPlayer = await this.getTestPlayer(testRunHandler, customExtensionLocalLocation);
202
199
 
203
200
  try {
@@ -207,12 +204,12 @@ class BaseWorker {
207
204
  }
208
205
  }
209
206
 
210
- runTestCleanup() {
211
- return Promise.resolve();
207
+ async runTestCleanup() {
208
+ return undefined;
212
209
  }
213
210
 
214
211
  onQueueCompleted() {
215
- return Promise.resolve();
212
+ return undefined;
216
213
  }
217
214
 
218
215
  run() {
@@ -238,9 +235,9 @@ class BaseWorker {
238
235
  try {
239
236
  const testRetryKey = testRunHandler.getRetryKey();
240
237
  testResult.testRetryKey = testRetryKey;
241
- await this.onResult(this.id, this.testId, testResult, sessionId, shouldRerun);
242
- if (!(this.options.lightweightMode && this.options.lightweightMode.general)) {
243
- await Promise.delay(DELAY_BETWEEN_TESTS);
238
+ await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
239
+ if (this.executionQueue.hasMoreTests() && !(this.options.lightweightMode && this.options.lightweightMode.general)) {
240
+ await Bluebird.delay(DELAY_BETWEEN_TESTS);
244
241
  }
245
242
  await this.runTestCleanup();
246
243
  if (shouldRerun) {
@@ -340,7 +337,7 @@ class BaseWorker {
340
337
 
341
338
  const projectId = this.userData && this.userData.projectId;
342
339
  const { errorType, reason } = buildError(err);
343
- testResultService.updateTestResult(projectId, this.testResultId, this.testId, {
340
+ testimServicesApi.updateTestResult(projectId, this.testResultId, this.testId, {
344
341
  status: testRunStatus.COMPLETED,
345
342
  success: false,
346
343
  reason,
@@ -390,10 +387,13 @@ class BaseWorker {
390
387
  !disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
391
388
  !disableResults && testResultService.joinToTestResult(this.testResultId, this.testId),
392
389
  ])
393
- .log('after join room, before runTest')
390
+ .then(() => testRunHandler.validateRunConfig())
394
391
  .then(() => this.runTest(testRunHandler, this.customExtensionLocalLocation, shouldRerun))
395
392
  .then(testResult => onRunComplete(testResult, testRunHandler))
396
- .log('After onRunComplete')
393
+ .then(result => {
394
+ perf.log('After onRunComplete');
395
+ return result;
396
+ })
397
397
  .catch(runError => recoverTestResults(runError, testRunHandler))
398
398
  .finally(() => {
399
399
  if (!disableRemoteStep) {
@@ -15,7 +15,7 @@ describe('BaseWorker', () => {
15
15
  let testPlayerMock;
16
16
 
17
17
  beforeEach(() => {
18
- worker = new BaseWorker();
18
+ worker = new BaseWorker(null, {});
19
19
  worker.userData = {};
20
20
  worker.options = { gridData: {}, browser: 'chrome', company: { companyId: 'companyId' }, getBrowserTimeout: 1000, getSessionTimeout: 100, getBrowserRetries: 10 };
21
21
  worker.testRunConfig = {};
@@ -44,9 +44,8 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
44
44
  return quarantineResult;
45
45
  }
46
46
 
47
- perf.log('before runTest onStart single browser');
48
-
49
- await this.onStart(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
47
+ perf.log('before runTest onTestStarted single browser');
48
+ await this.onTestStarted(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
50
49
  const testPlayer = await this.getTestPlayer(testRunHandler, customExtensionLocalLocation);
51
50
 
52
51
  testRunHandler.markClearBrowser();
@@ -54,6 +53,10 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
54
53
  }
55
54
 
56
55
  async runTestCleanup() {
56
+ if (!this.executionQueue.hasMoreTests()) {
57
+ await this.onQueueCompleted();
58
+ return;
59
+ }
57
60
  if (this.options.lightweightMode && this.options.lightweightMode.general) {
58
61
  await Bluebird.delay(DELAY_BETWEEN_TESTS);
59
62
  }