@testim/testim-cli 3.266.0 → 3.267.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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@testim/testim-cli",
3
- "version": "3.266.0",
3
+ "version": "3.267.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@testim/testim-cli",
9
- "version": "3.266.0",
9
+ "version": "3.267.0",
10
10
  "license": "Proprietary",
11
11
  "dependencies": {
12
12
  "@applitools/eyes-sdk-core": "13.11.21",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testim/testim-cli",
3
- "version": "3.266.0",
3
+ "version": "3.267.0",
4
4
  "description": "Command line interface for running Testing on your CI",
5
5
  "author": "Oren Rubin",
6
6
  "contributors": [{
@@ -14,11 +14,18 @@ const { StopRunOnError } = require('../errors');
14
14
 
15
15
  require('../player/webdriver'); // preload
16
16
 
17
+ /**
18
+ * @template {unknown[]} T
19
+ * @typedef {T extends [infer H, ...infer R] ? R : T} RemoveFirst
20
+ */
21
+
17
22
  class ParallelWorkerManager {
23
+ /** @param {string=} customExtensionLocalLocation */
18
24
  constructor(customExtensionLocalLocation) {
19
25
  this.customExtensionLocalLocation = customExtensionLocalLocation;
20
26
  }
21
27
 
28
+ /** @param {typeof CLI_MODE[keyof typeof CLI_MODE]} mode */
22
29
  getWorkerType(mode) {
23
30
  switch (mode) {
24
31
  case CLI_MODE.SELENIUM:
@@ -33,6 +40,12 @@ class ParallelWorkerManager {
33
40
  }
34
41
  }
35
42
 
43
+ /**
44
+ * @param {number} count
45
+ * @param {ExecutionQueue} queue
46
+ * @param {typeof CLI_MODE[keyof typeof CLI_MODE]} mode
47
+ * @param {...RemoveFirst<ConstructorParameters<typeof import('../workers/BaseWorker')>>} args
48
+ */
36
49
  createWorkers(count, queue, mode, ...args) {
37
50
  const Worker = this.getWorkerType(mode);
38
51
  const createWorker = () => {
@@ -47,6 +60,17 @@ class ParallelWorkerManager {
47
60
  return Array.from(new Array(count), createWorker);
48
61
  }
49
62
 
63
+ /**
64
+ * @param {import('./TestPlanRunner').ExecutionList} testList
65
+ * @param {import('../testRunStatus')} testStatus
66
+ * @param {string} executionId
67
+ * @param {string} executionName
68
+ * @param {import('../runOptions').RunnerOptions} options
69
+ * @param {string} branchToUse
70
+ * @param {ReturnType<typeof testimCustomToken['getTokenV3UserData']>} authData
71
+ * @param {number} workerCount
72
+ * @param {boolean} stopOnError
73
+ */
50
74
  async runTests(testList, testStatus, executionId, executionName, options, branchToUse, authData, workerCount, stopOnError) {
51
75
  if (testList && testList.length === 0) {
52
76
  return undefined;
@@ -72,6 +96,13 @@ class ParallelWorkerManager {
72
96
  const lightweightMode = options.lightweightMode;
73
97
  const sessionType = utils.getSessionType(options);
74
98
 
99
+ /**
100
+ * @param {number} wid
101
+ * @param {string} testId
102
+ * @param {string} resultId
103
+ * @param {boolean | undefined} isRerun
104
+ * @param {`${number}:${number}`} testRetryKey
105
+ */
75
106
  const onTestStarted = (wid, testId, resultId, isRerun, testRetryKey) => {
76
107
  runningTests++;
77
108
  analyticsService.analyticsTestStart({
@@ -93,6 +124,13 @@ class ParallelWorkerManager {
93
124
  return testStatus.testStartAndReport(wid, executionId, resultId, isRerun, testRetryKey);
94
125
  };
95
126
 
127
+ /**
128
+ * @param {number} wid
129
+ * @param {string} testId
130
+ * @param {*} testResult
131
+ * @param {string} sessionId
132
+ * @param {boolean | undefined} isRerun
133
+ */
96
134
  const onTestCompleted = async (wid, testId, testResult, sessionId, isRerun) => {
97
135
  runningTests--;
98
136
  const update = {};
@@ -135,7 +173,7 @@ class ParallelWorkerManager {
135
173
  .catch(err => logger.error('testEndAndReport threw an error', { err }));
136
174
 
137
175
  if (isRerun) {
138
- return undefined;
176
+ return;
139
177
  }
140
178
  combinedTestResults[testResult.resultId] = testResult;
141
179
  analyticsService.analyticsTestEnd({
@@ -163,11 +201,13 @@ class ParallelWorkerManager {
163
201
  const completedTests = Object.keys(combinedTestResults).length;
164
202
  if (completedTests === testCount || (stoppedOnError && runningTests === 0)) {
165
203
  resolve(combinedTestResults);
166
- return undefined;
167
204
  }
168
- return undefined;
169
205
  };
170
206
 
207
+ /**
208
+ * @param {number} wid
209
+ * @param {{ name: string; testId: string; resultId: string; runnerStatus: 'SKIPPED'; testStatus: 'quarantine' }} testResult
210
+ */
171
211
  const onTestIgnored = (wid, testResult) => {
172
212
  combinedTestResults[testResult.resultId] = testResult;
173
213
  testStatus.onTestIgnored(wid, testResult.resultId);
@@ -178,7 +218,7 @@ class ParallelWorkerManager {
178
218
  }
179
219
  };
180
220
 
181
- const onGridSlot = (_executionId, resultId, gridInfo) => testStatus.onGridSlot(_executionId, resultId, gridInfo);
221
+ const onGridSlot = (resultId, gridInfo) => testStatus.onGridSlot(resultId, gridInfo);
182
222
 
183
223
  options.userData = {
184
224
  loginData: Object.assign({}, testimCustomToken.getTokenV3UserData(), {
@@ -191,14 +231,24 @@ class ParallelWorkerManager {
191
231
  servicesUrl: config.SERVICES_HOST,
192
232
  };
193
233
  perf.log('in localStrategy before createWorker');
194
- this.createWorkers(workerCount, executionQueue, options.mode, options, this.customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored)
195
- .forEach((worker, index) => {
196
- perf.log('before schedule worker.run after createWorkers');
197
- schedule(() => {
198
- perf.log('right before worker.run');
199
- worker.run();
200
- }, index);
201
- });
234
+ this.createWorkers(
235
+ workerCount,
236
+ executionQueue,
237
+ options.mode,
238
+ options,
239
+ this.customExtensionLocalLocation,
240
+ executionId,
241
+ onTestStarted,
242
+ onTestCompleted,
243
+ onGridSlot,
244
+ onTestIgnored,
245
+ ).forEach((worker, index) => {
246
+ perf.log('before schedule worker.run after createWorkers');
247
+ schedule(() => {
248
+ perf.log('right before worker.run');
249
+ worker.run();
250
+ }, index);
251
+ });
202
252
  });
203
253
 
204
254
  try {
@@ -215,6 +265,10 @@ class ParallelWorkerManager {
215
265
  }
216
266
  }
217
267
 
268
+ /**
269
+ * @param {Function} fn
270
+ * @param {number} index
271
+ */
218
272
  function schedule(fn, index) {
219
273
  if (index === 0) {
220
274
  fn();
@@ -26,9 +26,7 @@ const TDK_CHILD_RESULTS_TIMEOUT = 1000 * 60 * 5;
26
26
  /** @typedef {Awaited<ReturnType<typeof getSuite>>['tests'][number]} ExecutionList */
27
27
 
28
28
  class TestPlanRunner {
29
- /**
30
- * @param {string=} customExtensionLocalLocation
31
- */
29
+ /** @param {string=} customExtensionLocalLocation */
32
30
  constructor(customExtensionLocalLocation) {
33
31
  this.workerManager = new ParallelWorkerManager(customExtensionLocalLocation);
34
32
  this.startTime = Date.now();
@@ -115,7 +113,7 @@ class TestPlanRunner {
115
113
  }
116
114
  const { runKey: apiKey, url: serverUrl } = applitoolsIntegrationData;
117
115
  const tmpSDK = require('@applitools/eyes-sdk-core').makeSDK({ name: 'Testim.io', version: '4.0.0', spec: {} });
118
- await tmpSDK.closeBatch({ settings: { batchId: executionId, serverUrl, apiKey } });
116
+ await tmpSDK.closeBatches({ settings: { batchIds: [executionId], serverUrl, apiKey } });
119
117
  } catch (err) {
120
118
  // If a batch with this name did not exist, do not log an error.
121
119
  if (err.message && (err.message.startsWith('Request failed with status code 404') || err.message.startsWith('no batchIds were set'))) {
@@ -59,38 +59,47 @@ function getSerializableObject(grid) {
59
59
  };
60
60
  }
61
61
 
62
- function handleGetGridResponse(projectId, companyId, workerId, browser, getFun) {
63
- return getFun()
64
- .catch(err => {
65
- logger.error('failed to get grid', { projectId, companyId, err });
66
- throw new Error(gridMessages.UNKNOWN);
67
- })
68
- .then(async (res) => {
69
- logger.info('get grid info', Object.assign({}, res, { projectId, companyId }));
70
- const isSuccess = () => res.status === 'success';
71
- const isError = () => res.status === 'error' && res.code;
72
- if (!res || (!isError() && !isSuccess())) {
73
- logger.error('invalid response - get grid', { res });
74
- throw new Error(gridMessages.UNKNOWN);
75
- }
76
-
77
- if (isSuccess()) {
78
- const serGrid = getSerializableObject(res.grid);
79
- module.exports.addItemToGridCache(workerId, companyId, serGrid.gridId, serGrid.slotId, browser);
80
- return serGrid;
81
- }
82
-
83
- if (isError() && res.code === 'not-found') {
84
- throw new GridError(gridMessages.NOT_FOUND);
85
- }
86
-
87
- if (isError() && res.code === 'no-available-slot') {
88
- throw new GridError(`Failed to run test on ${browser} - concurrency limit reached`);
89
- }
90
-
91
- logger.error('invalid code error response - get grid', { res });
92
- throw new GridError(gridMessages.UNKNOWN);
93
- });
62
+ /**
63
+ * @template {{ status: string; code?: string; grid: * }} T
64
+ * @param {string} projectId
65
+ * @param {string} companyId
66
+ * @param {number} workerId
67
+ * @param {string} browser
68
+ * @param {() => Promise<T>} getFun
69
+ */
70
+ async function handleGetGridResponse(projectId, companyId, workerId, browser, getFun) {
71
+ /** @type {T} */
72
+ let res;
73
+ try {
74
+ res = await getFun();
75
+ } catch (err) {
76
+ logger.error('failed to get grid', { projectId, companyId, err });
77
+ throw new Error(gridMessages.UNKNOWN);
78
+ }
79
+ logger.info('get grid info', Object.assign({}, res, { projectId, companyId }));
80
+ const isSuccess = () => res.status === 'success';
81
+ const isError = () => res.status === 'error' && res.code;
82
+ if (!res || (!isError() && !isSuccess())) {
83
+ logger.error('invalid response - get grid', { res });
84
+ throw new Error(gridMessages.UNKNOWN);
85
+ }
86
+
87
+ if (isSuccess()) {
88
+ const serGrid = getSerializableObject(res.grid);
89
+ module.exports.addItemToGridCache(workerId, companyId, serGrid.gridId, serGrid.slotId, browser);
90
+ return serGrid;
91
+ }
92
+
93
+ if (isError() && res.code === 'not-found') {
94
+ throw new GridError(gridMessages.NOT_FOUND);
95
+ }
96
+
97
+ if (isError() && res.code === 'no-available-slot') {
98
+ throw new GridError(`Failed to run test on ${browser} - concurrency limit reached`);
99
+ }
100
+
101
+ logger.error('invalid code error response - get grid', { res });
102
+ throw new GridError(gridMessages.UNKNOWN);
94
103
  }
95
104
 
96
105
  function addItemToGridCache(workerId, companyId, gridId, slotId, browser) {
@@ -264,7 +273,13 @@ async function getGridData(options) {
264
273
  throw new GridError('Missing host or grid configuration');
265
274
  }
266
275
 
267
- async function getGridSlot(browser, executionId, testResultId, onGridSlot, options, workerId) {
276
+ /**
277
+ * @param {string} browser
278
+ * @param {string} executionId
279
+ * @param {import('../runOptions')} options
280
+ * @param {number} workerId
281
+ */
282
+ async function getGridSlot(browser, executionId, options, workerId) {
268
283
  const getGridDataFromServer = async () => {
269
284
  const { host, project, grid, gridId, useLocalChromeDriver, useChromeLauncher, company = {} } = options;
270
285
  const companyId = company.companyId;
@@ -284,9 +299,6 @@ async function getGridSlot(browser, executionId, testResultId, onGridSlot, optio
284
299
  };
285
300
 
286
301
  const gridInfo = await getGridDataFromServer();
287
-
288
- await onGridSlot(executionId, testResultId, gridInfo);
289
-
290
302
  return gridInfo;
291
303
  }
292
304
 
@@ -169,14 +169,12 @@ describe('gridService', () => {
169
169
  describe('getGridSlot', () => {
170
170
  let getGridByIdStub;
171
171
  let getGridByNameStub;
172
- let onGridSlot;
173
172
  let addItemToGridCacheStub;
174
173
 
175
174
  beforeEach(() => {
176
175
  getGridByIdStub = sinon.stub(servicesApi, 'getGridById').resolves({ grid: { gridId: 'gridId', type: 'gridId' }, status: 'success' });
177
176
  getGridByNameStub = sinon.stub(servicesApi, 'getGridByName').resolves({ grid: { gridId: 'gridId', type: 'gridName' }, status: 'success' });
178
177
  addItemToGridCacheStub = sinon.stub(gridService, 'addItemToGridCache').callThrough();
179
- onGridSlot = sinon.stub().resolves();
180
178
  });
181
179
  afterEach(() => {
182
180
  getGridByIdStub.restore();
@@ -185,82 +183,77 @@ describe('gridService', () => {
185
183
  });
186
184
 
187
185
  it('should not access server when using useLocalChromeDriver flag', async () => {
188
- const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { useLocalChromeDriver: true }, 'workerId');
186
+ const slot = await gridService.getGridSlot('browser', 'executionId', { useLocalChromeDriver: true }, 'workerId');
189
187
  expect(slot).to.eql({ mode: 'local' });
190
188
  sinon.assert.notCalled(getGridByIdStub);
191
189
  sinon.assert.notCalled(getGridByNameStub);
192
190
  sinon.assert.notCalled(addItemToGridCacheStub);
193
- sinon.assert.calledOnce(onGridSlot);
194
191
  });
195
192
 
196
193
  it('should not access server when using useChromeLauncher flag', async () => {
197
- const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { useChromeLauncher: true }, 'workerId');
194
+ const slot = await gridService.getGridSlot('browser', 'executionId', { useChromeLauncher: true }, 'workerId');
198
195
  expect(slot).to.eql({ mode: 'local' });
199
196
  sinon.assert.notCalled(getGridByIdStub);
200
197
  sinon.assert.notCalled(getGridByNameStub);
201
198
  sinon.assert.notCalled(addItemToGridCacheStub);
202
- sinon.assert.calledOnce(onGridSlot);
203
199
  });
204
200
 
205
201
  it('should return fixed grid when passing host and port', async () => {
206
- const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { host: 'localhost', port: 4444 }, 'workerId');
202
+ const slot = await gridService.getGridSlot('browser', 'executionId', { host: 'localhost', port: 4444 }, 'workerId');
207
203
  expect(slot).to.shallowDeepEqual({ type: 'hostAndPort', host: 'localhost', port: 4444 });
208
204
  sinon.assert.notCalled(getGridByIdStub);
209
205
  sinon.assert.notCalled(getGridByNameStub);
210
206
  sinon.assert.notCalled(addItemToGridCacheStub);
211
- sinon.assert.calledOnce(onGridSlot);
212
207
  });
213
208
 
214
209
  it('should get grid from server when passing grid id', async () => {
215
- const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId');
210
+ const slot = await gridService.getGridSlot('browser', 'executionId', { gridId: 'gridId' }, 'workerId');
216
211
  expect(slot).to.shallowDeepEqual({ type: 'gridId', gridId: 'gridId' });
217
212
  sinon.assert.calledOnce(getGridByIdStub);
218
213
  sinon.assert.notCalled(getGridByNameStub);
219
214
  sinon.assert.calledOnce(addItemToGridCacheStub);
220
- sinon.assert.calledOnce(onGridSlot);
221
215
  });
222
216
 
223
217
  it('should get grid from server when passing grid name', async () => {
224
- const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { grid: 'gridName' }, 'workerId');
218
+ const slot = await gridService.getGridSlot('browser', 'executionId', { grid: 'gridName' }, 'workerId');
225
219
  expect(slot).to.shallowDeepEqual({ type: 'gridName', gridId: 'gridId' });
226
220
  sinon.assert.calledOnce(getGridByNameStub);
227
221
  sinon.assert.notCalled(getGridByIdStub);
228
222
  sinon.assert.calledOnce(addItemToGridCacheStub);
229
- sinon.assert.calledOnce(onGridSlot);
230
223
  });
231
224
 
232
225
  it('should handle grid not found error', async () => {
233
226
  getGridByIdStub.resolves({ status: 'error', code: 'not-found' });
234
- await expect(gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId'))
227
+ await expect(gridService.getGridSlot('browser', 'executionId', { gridId: 'gridId' }, 'workerId'))
235
228
  .to.eventually.be.rejectedWith('The specified grid is not available');
236
229
  });
237
230
 
238
231
  it('should handle no available slot error', async () => {
239
232
  getGridByIdStub.resolves({ status: 'error', code: 'no-available-slot' });
240
- await expect(gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId'))
233
+ await expect(gridService.getGridSlot('browser', 'executionId', { gridId: 'gridId' }, 'workerId'))
241
234
  .to.eventually.be.rejectedWith('Failed to run test on browser - concurrency limit reached');
242
235
  });
243
236
 
244
237
  it('should handle getGridSlot request error', async () => {
245
238
  getGridByIdStub.rejects({ status: 'error', code: 'not-found' });
246
- await expect(gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId'))
239
+ await expect(gridService.getGridSlot('browser', 'executionId', { gridId: 'gridId' }, 'workerId'))
247
240
  .to.eventually.be.rejectedWith('Test couldn\'t get browser - unknown error');
248
241
  });
249
242
 
250
243
  it('should throw when no grid selected', async () => {
251
- await expect(gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, {}, 'workerId'))
244
+ await expect(gridService.getGridSlot('browser', 'executionId', {}, 'workerId'))
252
245
  .to.eventually.be.rejectedWith('Missing host or grid configuration');
253
246
  });
254
247
 
255
248
  it('should handle unkonwn errors', async () => {
256
249
  getGridByIdStub.resolves({ status: 'error', code: 'bla bla' });
257
- await expect(gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId'))
250
+ await expect(gridService.getGridSlot('browser', 'executionId', { gridId: 'gridId' }, 'workerId'))
258
251
  .to.eventually.be.rejectedWith('Test couldn\'t get browser - unknown error');
259
252
  });
260
253
 
261
254
  it('should handle no status', async () => {
262
255
  getGridByIdStub.resolves({ code: 'bla bla' });
263
- await expect(gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId'))
256
+ await expect(gridService.getGridSlot('browser', 'executionId', { gridId: 'gridId' }, 'workerId'))
264
257
  .to.eventually.be.rejectedWith('Test couldn\'t get browser - unknown error');
265
258
  });
266
259
  });
package/testRunStatus.js CHANGED
@@ -233,7 +233,11 @@ class RunStatus {
233
233
  return this.testStartReport(test, executionId, testRetryKey);
234
234
  }
235
235
 
236
- onGridSlot(executionId, resultId, gridInfo) {
236
+ /**
237
+ * @param {string} resultId
238
+ * @param {Awaited<ReturnType<typeof import('./services/gridService')['getGridSlot']>>} gridInfo
239
+ */
240
+ onGridSlot(resultId, gridInfo) {
237
241
  const test = this.getTestResult(resultId);
238
242
  test.config.gridInfo = Object.assign({}, gridInfo, { key: undefined, user: undefined });
239
243
  logger.info('on get grid info', { gridInfo: test.config.gridInfo });
@@ -264,6 +268,10 @@ class RunStatus {
264
268
  return result.success ? constants.runnerTestStatus.PASSED : constants.runnerTestStatus.FAILED;
265
269
  }
266
270
 
271
+ /**
272
+ * @param {number} wid
273
+ * @param {Parameters<ConstructorParameters<typeof import('./workers/BaseWorker')>[7]>[1]} resultId
274
+ */
267
275
  onTestIgnored(wid, resultId) {
268
276
  const test = this.getTestResult(resultId);
269
277
  reporter.onTestIgnored(wid, test, `test in ${constants.testStatus.QUARANTINE}`);
@@ -27,6 +27,12 @@ const { SETUP_TIMEOUT, NETWORK_ERROR, GRID_ERROR, BROWSER_CLOSED, SELENIUM_ERROR
27
27
  const DELAY_BETWEEN_TESTS = ms('1s');
28
28
  let ordinal = 1;
29
29
 
30
+ /**
31
+ * @param {string} testId
32
+ * @param {string} testName
33
+ * @param {string} resultId
34
+ * @param {string} reason
35
+ */
30
36
  function buildFailureResult(testId, testName, resultId, reason) {
31
37
  return {
32
38
  testId,
@@ -43,10 +49,10 @@ class BaseWorker {
43
49
  * @param {import('../runOptions').RunnerOptions} options
44
50
  * @param {string=} customExtensionLocalLocation
45
51
  * @param {string} executionId
46
- * @param {Function} onTestStarted
47
- * @param {Function} onTestCompleted
48
- * @param {Function} onGridSlot
49
- * @param {Function} onTestIgnored
52
+ * @param {(workerId: number, testId: string, resultId: string, isRerun: boolean | undefined, testRetryKey: `${number}:${number}` ) => Promise<any>} onTestStarted
53
+ * @param {(workerId: number, testId: string, testResult: any, sessionId: string, shouldRerun: boolean | undefined) => Promise<void>} onTestCompleted
54
+ * @param {import('../testRunStatus')['onGridSlot']} onGridSlot
55
+ * @param {(workerId: number, testResult: { name: string; testId: string; resultId: string; runnerStatus: 'SKIPPED'; testStatus: 'quarantine' }) => void} onTestIgnored
50
56
  * @param {boolean=} releaseSlotOnTestFinished
51
57
  */
52
58
  constructor(executionQueue, options, customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored, releaseSlotOnTestFinished = true) {
@@ -75,10 +81,17 @@ class BaseWorker {
75
81
  return ordinal++;
76
82
  }
77
83
 
78
- getGridSlot(browser, testRunHandler) {
79
- return gridService.getGridSlot(browser, testRunHandler.getExecutionId(), testRunHandler.getTestResultId(), this.onGridSlot, this.options, this.id);
84
+ /**
85
+ * @param {string} browser
86
+ * @param {import('../testRunHandler')} testRunHandler
87
+ */
88
+ async getGridSlot(browser, testRunHandler) {
89
+ const slot = await gridService.getGridSlot(browser, testRunHandler.getExecutionId(), this.options, this.id);
90
+ this.onGridSlot(testRunHandler.getTestResultId(), slot);
91
+ return slot;
80
92
  }
81
93
 
94
+ /** @param {import('../testRunHandler')} testRunHandler */
82
95
  async getSlotOnce(testRunHandler) {
83
96
  const { browserValue } = this.testRunConfig;
84
97
  reporter.onGetSlot(this.id, browserValue || 'chrome');
@@ -86,6 +99,10 @@ class BaseWorker {
86
99
  return gridInfo;
87
100
  }
88
101
 
102
+ /**
103
+ * @abstract
104
+ * @returns {import('../player/appiumTestPlayer') | import('../player/seleniumTestPlayer') | import('../player/extensionTestPlayer') | import('../player/chromeLauncherTestPlayer')}
105
+ */
89
106
  initPlayer() {
90
107
  throw new NotImplementedError(true);
91
108
  }
@@ -94,6 +111,10 @@ class BaseWorker {
94
111
  throw new NotImplementedError(true);
95
112
  }
96
113
 
114
+ /**
115
+ * @param {import('../testRunHandler')} testRunHandler
116
+ * @param {ReturnType<typeof this['initPlayer']>} player
117
+ */
97
118
  async runTestOnce(testRunHandler, player) {
98
119
  testRunHandler.setSessionId(player.getSessionId());
99
120
  logger.info('Test run started', {
@@ -105,19 +126,20 @@ class BaseWorker {
105
126
  return await testRunHandler.clearTestResult();
106
127
  }
107
128
 
129
+ /** @param {import('../testRunHandler')} testRunHandler */
108
130
  handleQuarantine(testRunHandler) {
109
- if (utils.isQuarantineAndNotRemoteRun({ testStatus: testRunHandler.getTestStatus() }, this.options)) {
110
- const testResult = {
111
- name: testRunHandler.getTestName(),
112
- testId: testRunHandler.getTestId(),
113
- resultId: testRunHandler.getTestResultId(),
114
- runnerStatus: runnerTestStatus.SKIPPED,
115
- testStatus: testRunHandler.getTestStatus(),
116
- };
117
- this.onTestIgnored(this.id, testResult);
118
- return testResult;
131
+ if (!utils.isQuarantineAndNotRemoteRun({ testStatus: testRunHandler.getTestStatus() }, this.options)) {
132
+ return undefined;
119
133
  }
120
- return undefined;
134
+ const testResult = {
135
+ name: testRunHandler.getTestName(),
136
+ testId: testRunHandler.getTestId(),
137
+ resultId: testRunHandler.getTestResultId(),
138
+ runnerStatus: runnerTestStatus.SKIPPED,
139
+ testStatus: testRunHandler.getTestStatus(),
140
+ };
141
+ this.onTestIgnored(this.id, testResult);
142
+ return testResult;
121
143
  }
122
144
 
123
145
  setSessionTimeout() {
@@ -127,6 +149,10 @@ class BaseWorker {
127
149
  return Math.max(this.lambdatestService.getSessionTimeout, this.options.getSessionTimeout);
128
150
  }
129
151
 
152
+ /**
153
+ * @param {import('../testRunHandler')} testRunHandler
154
+ * @param {string=} customExtensionLocalLocation
155
+ */
130
156
  async getTestPlayer(testRunHandler, customExtensionLocalLocation) {
131
157
  const projectId = this.userData?.projectId;
132
158
  let testPlayer;
@@ -202,6 +228,11 @@ class BaseWorker {
202
228
  return testPlayer;
203
229
  }
204
230
 
231
+ /**
232
+ * @param {import('../testRunHandler')} testRunHandler
233
+ * @param {string=} customExtensionLocalLocation
234
+ * @param {boolean=} shouldRerun
235
+ */
205
236
  async runTest(testRunHandler, customExtensionLocalLocation, shouldRerun) {
206
237
  perf.log('inside runTest');
207
238
  const projectId = this.userData?.projectId;
@@ -233,6 +264,11 @@ class BaseWorker {
233
264
  run() {
234
265
  const runNextTest = () => process.nextTick(() => this.run());
235
266
 
267
+ /**
268
+ * @param {*} testResult
269
+ * @param {import('../testRunHandler')} testRunHandler
270
+ * @param {Error=} err
271
+ */
236
272
  const onRunComplete = async (testResult, testRunHandler, err) => {
237
273
  if (utils.isQuarantineAndNotRemoteRun(testResult, this.options)) {
238
274
  return runNextTest();
@@ -287,6 +323,10 @@ class BaseWorker {
287
323
  const getNetworkErrorMessage = () => 'Due to network connectivity issues, Testim CLI has been unable to connect to the grid.\n' +
288
324
  `Please make sure the CLI has stable access to the internet. ${didNetworkConnectivityTestFail() ? '(Internal: network connectivity test failed)' : ''}`;
289
325
 
326
+ /**
327
+ * @param {Error} err
328
+ * @param {boolean} wasNetworkHealthy
329
+ */
290
330
  const buildError = (err, wasNetworkHealthy) => {
291
331
  if (!wasNetworkHealthy && featureFlags.flags.errorMessageOnBadNetwork.isEnabled()) {
292
332
  return {
@@ -344,6 +384,10 @@ class BaseWorker {
344
384
  return { errorType: UNKNOWN_ERROR, reason: msg };
345
385
  };
346
386
 
387
+ /**
388
+ * @param {Error} err
389
+ * @param {import('../testRunHandler')} testRunHandler
390
+ */
347
391
  const onRunError = async (err, testRunHandler) => {
348
392
  const wasNetworkHealthy = await isNetworkHealthy();
349
393
  if (!wasNetworkHealthy && featureFlags.flags.warnOnBadNetwork.isEnabled()) {
@@ -366,6 +410,10 @@ class BaseWorker {
366
410
  await onRunComplete(buildFailureResult(this.testId, this.testName, this.testResultId, reason), testRunHandler, err);
367
411
  };
368
412
 
413
+ /**
414
+ * @param {Error} runError
415
+ * @param {import('../testRunHandler')} testRunHandler
416
+ */
369
417
  const recoverTestResults = async (runError, testRunHandler) => {
370
418
  const testId = this.testId;
371
419
  const resultId = this.testResultId;
@@ -401,23 +449,29 @@ class BaseWorker {
401
449
  const disableResults = this.options.disableSockets || (this.options.lightweightMode?.disableResults && (this.options.useChromeLauncher || this.options.mode !== 'extension'));
402
450
  const disableRemoteStep = this.options.disableSockets || (this.options.lightweightMode?.disableRemoteStep);
403
451
 
404
- const runTestAndCalcResult = (testRunHandler, shouldRerun) => Promise.all([
405
- !disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
406
- !disableResults && testResultService.joinToTestResult(this.testResultId, this.testId),
407
- ])
408
- .then(() => testRunHandler.validateRunConfig())
409
- .then(() => this.runTest(testRunHandler, this.customExtensionLocalLocation, shouldRerun))
410
- .then(testResult => onRunComplete(testResult, testRunHandler))
411
- .then(result => {
452
+ /**
453
+ * @param {import('../testRunHandler')} testRunHandler
454
+ * @param {boolean=} shouldRerun
455
+ */
456
+ const runTestAndCalcResult = async (testRunHandler, shouldRerun) => {
457
+ try {
458
+ await Promise.all([
459
+ !disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
460
+ !disableResults && testResultService.joinToTestResult(this.testResultId, this.testId),
461
+ ]);
462
+ testRunHandler.validateRunConfig();
463
+ const testResult = await this.runTest(testRunHandler, this.customExtensionLocalLocation, shouldRerun);
464
+ const result = await onRunComplete(testResult, testRunHandler);
412
465
  perf.log('After onRunComplete');
413
466
  return result;
414
- })
415
- .catch(runError => recoverTestResults(runError, testRunHandler))
416
- .finally(() => {
467
+ } catch (runError) {
468
+ return recoverTestResults(runError, testRunHandler);
469
+ } finally {
417
470
  if (!disableRemoteStep) {
418
471
  remoteStepService.unlistenToRemoteStep(this.testResultId);
419
472
  }
420
- });
473
+ }
474
+ };
421
475
 
422
476
  const testRunHandler = this.executionQueue.getNext();
423
477
 
@@ -25,7 +25,7 @@ describe('BaseWorker', () => {
25
25
  },
26
26
  });
27
27
 
28
- worker = new BaseWorker(null, {}, null, null, onTestStartedStub);
28
+ worker = new BaseWorker(null, {}, null, null, onTestStartedStub, null, () => { /* noop */ });
29
29
  worker.userData = {};
30
30
  worker.options = { gridData: {}, browser: 'chrome', company: { companyId: 'companyId' }, getBrowserTimeout: 1000, getSessionTimeout: 100, getBrowserRetries: 10 };
31
31
  worker.testRunConfig = {};
@@ -10,11 +10,6 @@ const AppiumApi = require('../commons/getSessionPlayerRequire').AppiumApi;
10
10
  const desiredCapabilitiesBuilder = require('../commons/testimDesiredCapabilitiesBuilder');
11
11
 
12
12
  class WorkerAppium extends BaseWorker {
13
- constructor(...args) {
14
- super(...args);
15
- this.getBrowserOnce = Promise.method(this.getBrowserOnce);
16
- }
17
-
18
13
  initPlayer(testRunHandler) {
19
14
  return new AppiumTestPlayer(this.id,
20
15
  testRunHandler.getRunParams(),
@@ -27,6 +27,11 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
27
27
  return this._getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo);
28
28
  }
29
29
 
30
+ /**
31
+ * @override
32
+ * @param {import('../testRunHandler')} testRunHandler
33
+ * @param {string=} customExtensionLocalLocation
34
+ */
30
35
  async getTestPlayer(testRunHandler, customExtensionLocalLocation) {
31
36
  if (this.testPlayer && !this.testPlayer.driver.isAlive()) {
32
37
  logger.warn('WorkerExtensionSingleBrowser is releasing a dead player', { workerId: this.id });
@@ -17,21 +17,29 @@ const { preloadTests } = require('../commons/preloadTests');
17
17
  const NO_NAVIGATION_TIME_LIMIT = 1e9;
18
18
 
19
19
  class WorkerSelenium extends BaseWorker {
20
- constructor(...args) {
21
- super(...args);
22
- this.getBrowserOnce = Promise.method(this.getBrowserOnce);
23
- }
24
-
20
+ /**
21
+ * @override
22
+ * @param {import('../testRunHandler')} testRunHandler
23
+ */
25
24
  initPlayer(testRunHandler) {
26
- return new SeleniumTestPlayer(this.id,
25
+ return new SeleniumTestPlayer(
26
+ this.id,
27
27
  testRunHandler.getRunParams(),
28
28
  this.options.shouldMonitorPerformance,
29
29
  testRunHandler.getAutomationMode(),
30
30
  undefined,
31
31
  testRunHandler.getRetryCount(),
32
- testRunHandler.getPreviousTestResultId());
32
+ testRunHandler.getPreviousTestResultId(),
33
+ );
33
34
  }
34
35
 
36
+ /**
37
+ * @override
38
+ * @param {import('../testRunHandler')} testRunHandler
39
+ * @param {string=} customExtensionLocalLocation
40
+ * @param {SeleniumTestPlayer} seleniumTestPlayer
41
+ * @param {Awaited<ReturnType<import('../services/gridService')['handleHybridOrVendorIfNeeded']>>} gridInfo
42
+ */
35
43
  async getBrowserOnce(testRunHandler, customExtensionLocalLocation, seleniumTestPlayer, gridInfo) {
36
44
  perf.log('in WorkerSelenium getBrowserOnce');
37
45
  reporter.onGetSession(this.id, this.testName, testRunHandler.getRunMode());
@@ -81,6 +89,7 @@ class WorkerSelenium extends BaseWorker {
81
89
  }
82
90
 
83
91
  /**
92
+ * @override
84
93
  * @param {import('../testRunHandler')} testRunHandler
85
94
  * @param {import('../player/seleniumTestPlayer')} seleniumTestPlayer
86
95
  */