@testim/testim-cli 3.261.0 → 3.263.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 (39) hide show
  1. package/cli.js +11 -5
  2. package/commons/featureFlags.js +1 -0
  3. package/commons/testimServicesApi.js +10 -7
  4. package/executionQueue.js +9 -1
  5. package/npm-shrinkwrap.json +52 -52
  6. package/package.json +1 -1
  7. package/player/stepActions/RefreshStepAction.js +7 -4
  8. package/player/stepActions/baseJsStepAction.js +45 -46
  9. package/player/stepActions/dropFileStepAction.js +11 -12
  10. package/player/stepActions/evaluateExpressionStepAction.js +32 -33
  11. package/player/stepActions/extensionOnlyStepAction.js +3 -4
  12. package/player/stepActions/extractTextStepAction.js +8 -10
  13. package/player/stepActions/hoverStepAction.js +3 -3
  14. package/player/stepActions/locateStepAction.js +39 -34
  15. package/player/stepActions/mouseStepAction.js +36 -34
  16. package/player/stepActions/navigationStepAction.js +7 -8
  17. package/player/stepActions/scrollStepAction.js +22 -22
  18. package/player/stepActions/stepAction.js +21 -21
  19. package/player/stepActions/stepActionRegistrar.js +63 -58
  20. package/player/stepActions/submitStepAction.js +2 -3
  21. package/player/stepActions/textStepAction.js +14 -14
  22. package/player/stepActions/textValidationStepAction.js +50 -38
  23. package/player/stepActions/wheelStepAction.js +5 -11
  24. package/polyfills/Array.prototype.at.js +13 -0
  25. package/polyfills/index.js +1 -0
  26. package/processHandler.js +2 -0
  27. package/reports/junitReporter.js +18 -1
  28. package/runOptions.d.ts +4 -0
  29. package/runOptions.js +8 -1
  30. package/runner.js +7 -0
  31. package/runners/TestPlanRunner.js +20 -19
  32. package/runners/runnerUtils.js +1 -2
  33. package/testRunHandler.js +18 -9
  34. package/testRunStatus.js +209 -157
  35. package/utils/index.js +9 -2
  36. package/workers/BaseWorker.js +11 -0
  37. package/workers/WorkerExtension.js +117 -91
  38. package/workers/WorkerSelenium.js +8 -3
  39. package/player/stepActions/scripts/polyfills.js +0 -393
package/testRunStatus.js CHANGED
@@ -1,41 +1,50 @@
1
1
  'use strict';
2
2
 
3
- const constants = require('./commons/constants');
4
- const { TESTIM_CONCURRENT_WORKER_COUNT } = require('./commons/config');
3
+ const _ = require('lodash');
5
4
  const utils = require('./utils');
6
- const reporter = require('./reports/reporter.js');
7
- const servicesApi = require('./commons/testimServicesApi.js');
5
+ const reporter = require('./reports/reporter');
6
+ const constants = require('./commons/constants');
8
7
  const gridService = require('./services/gridService');
9
- const logger = require('./commons/logger').getLogger('test-run-status');
10
- const { ArgError } = require('./errors');
8
+ const featureFlags = require('./commons/featureFlags');
9
+ const servicesApi = require('./commons/testimServicesApi');
11
10
  const OverrideTestDataBuilder = require('./OverrideTestDataBuilder');
12
- const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
13
- const Promise = require('bluebird');
14
- const _ = require('lodash');
11
+ const featureAvailabilityService = require('./commons/featureAvailabilityService');
12
+ const { ArgError } = require('./errors');
13
+ const { getLogger } = require('./commons/logger');
15
14
  const { registerExitHook } = require('./processHandler');
16
15
  const { calculateCoverage } = require('./coverage/jsCoverage');
17
- const featureAvailabilityService = require('./commons/featureAvailabilityService');
18
- const featureFlags = require('./commons/featureFlags');
16
+ const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
19
17
  const { mapFilesToLocalDrive } = require('./services/localRCASaver');
20
- const { promiseMap } = require('./utils');
18
+ const { TESTIM_CONCURRENT_WORKER_COUNT } = require('./commons/config');
21
19
 
20
+ const logger = getLogger('test-run-status');
22
21
  const gitBranch = utils.getEnvironmentGitBranch();
23
22
  const gitCommit = process.env.GIT_COMMIT || process.env.CIRCLE_SHA1 || process.env.TRAVIS_COMMIT;
24
23
  const gitRepoUrl = process.env.GIT_URL || process.env.CIRCLE_REPOSITORY_URL;
25
24
  const runnerVersion = utils.getRunnerVersion();
26
25
 
27
26
 
28
- function runHook(fn, ...args) {
27
+ async function runHook(fn, ...args) {
29
28
  if (!fn || typeof fn !== 'function') {
30
- return Promise.resolve();
29
+ return undefined;
31
30
  }
32
- return Promise.try(() => fn(...args) || {}).catch(err => {
31
+
32
+ try {
33
+ const res = await fn(...args);
34
+ return res || {};
35
+ } catch (err) {
33
36
  logger.warn('failed to run hook', { err });
34
37
  throw new ArgError(`failed to run hook promise ${err.message}`);
35
- });
38
+ }
36
39
  }
37
40
 
38
41
  class RunStatus {
42
+ /**
43
+ * @param {any[]} testInfoList
44
+ * @param {import('./runOptions').RunnerOptions} options
45
+ * @param {string} testPlanId
46
+ * @param {string} branchToUse
47
+ */
39
48
  constructor(testInfoList, options, testPlanId, branchToUse) {
40
49
  this.options = options;
41
50
  this.options.runParams = this.options.runParams || {};
@@ -65,7 +74,7 @@ class RunStatus {
65
74
  testPlanId,
66
75
  testPlans: options.testPlan,
67
76
  testLabels: options.label,
68
- testSuites: _.uniq(testInfoList.flatMap(test => test.testSuites)),
77
+ testSuites: [...new Set(testInfoList.flatMap(test => test.testSuites))],
69
78
  testNames: options.name,
70
79
  testIds: options.testId,
71
80
  testConfigs: options.testConfigNames,
@@ -104,7 +113,18 @@ class RunStatus {
104
113
  }) {
105
114
  const orgTestResult = this.testRunStatus[originalTestResultId] || {};
106
115
  const {
107
- config, isTestsContainer, testId, name, testStatus,
116
+ config,
117
+ isTestsContainer,
118
+ testId,
119
+ name,
120
+ testStatus,
121
+ testCreatorName,
122
+ testCreatorEmail,
123
+ testOwnerName,
124
+ testOwnerEmail,
125
+ testLabels,
126
+ testSuites,
127
+ allLabels,
108
128
  } = orgTestResult;
109
129
 
110
130
  const newTestResult = {
@@ -120,7 +140,15 @@ class RunStatus {
120
140
  testStatus,
121
141
  };
122
142
 
123
- this.testRunStatus[newResultId] = newTestResult;
143
+ this.testRunStatus[newResultId] = Object.assign({}, newTestResult, {
144
+ testCreatorName,
145
+ testCreatorEmail,
146
+ testOwnerName,
147
+ testOwnerEmail,
148
+ testLabels,
149
+ testSuites,
150
+ allLabels,
151
+ });
124
152
 
125
153
  return servicesApi.addTestRetry({
126
154
  projectId,
@@ -146,55 +174,58 @@ class RunStatus {
146
174
  return test;
147
175
  }
148
176
 
149
- updateTestStatusRunning(test, executionId, testRetryKey) {
177
+ async updateTestStatusRunning(test, executionId, testRetryKey) {
150
178
  const { project: projectId, remoteRunId, projectData } = this.options;
151
179
  if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
152
180
  return this.executionStartedPromise;
153
181
  }
154
182
 
155
- return servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
156
- .catch(err => {
157
- logger.error('failed to upload test data artifact (runner)', { err });
158
- return '';
159
- })
160
- .then(async (testDataUrl) => {
161
- const testConfig = _.cloneDeep(test.config);
162
- delete testConfig.testData;
163
- testConfig.testDataUrl = testDataUrl;
164
- await this.executionStartedPromise;
165
- return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
166
- });
183
+ let testDataUrl = '';
184
+
185
+ try {
186
+ testDataUrl = await servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults);
187
+ } catch (err) {
188
+ logger.error('failed to upload test data artifact (runner)', { err });
189
+ }
190
+ const testConfig = _.cloneDeep(test.config);
191
+ delete testConfig.testData;
192
+ testConfig.testDataUrl = testDataUrl;
193
+ await this.executionStartedPromise;
194
+ return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
167
195
  }
168
196
 
169
- testStartReport(test, executionId, testRetryKey) {
197
+ async testStartReport(test, executionId, testRetryKey) {
170
198
  if (utils.isQuarantineAndNotRemoteRun(test, this.options)) {
171
- return Promise.resolve();
199
+ return undefined;
200
+ }
201
+ const globalParameters = this.exportsGlobal;
202
+ try {
203
+ const params = await runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: globalParameters, globalParameters }), this.options.userData.loginData.token);
204
+
205
+ // Temporary Sapiens log (SUP-3192)
206
+ if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
207
+ logger.info('testRunStatus - testStartReport', {
208
+ 'test.config.testData': test.config.testData,
209
+ 'this.exportsGlobal': this.exportsGlobal,
210
+ 'this.fileUserParamsData': this.fileUserParamsData,
211
+ 'this.beforeSuiteParams': this.beforeSuiteParams,
212
+ params,
213
+ executionId,
214
+ 'test.testId': test.testId,
215
+ 'test.resultId': test.resultId,
216
+ });
217
+ }
218
+
219
+ test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
220
+ this.options.runParams[test.resultId] = test.config.testData;
221
+ test.startTime = Date.now();
222
+ await this.updateTestStatusRunning(test, executionId, testRetryKey);
223
+
224
+ return test;
225
+ } catch (err) {
226
+ logger.error('Failed to start test', { err });
227
+ throw err;
172
228
  }
173
- return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
174
- .then(async (params) => {
175
- // Temporary Sapiens log (SUP-3192)
176
- if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
177
- logger.info('testRunStatus - testStartReport', {
178
- 'test.config.testData': test.config.testData,
179
- 'this.exportsGlobal': this.exportsGlobal,
180
- 'this.fileUserParamsData': this.fileUserParamsData,
181
- 'this.beforeSuiteParams': this.beforeSuiteParams,
182
- params,
183
- executionId,
184
- 'test.testId': test.testId,
185
- 'test.resultId': test.resultId,
186
- });
187
- }
188
- test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
189
- this.options.runParams[test.resultId] = test.config.testData;
190
- test.startTime = Date.now();
191
- await this.updateTestStatusRunning(test, executionId, testRetryKey);
192
-
193
- return test;
194
- }).catch(err => {
195
- logger.error('Failed to start test', { err });
196
- throw err;
197
- });
198
229
  }
199
230
 
200
231
  testStartAndReport(wid, executionId, resultId, isRerun, testRetryKey) {
@@ -219,16 +250,14 @@ class RunStatus {
219
250
  reporter.onTestPassed(name);
220
251
  return;
221
252
  }
222
- reporter.onTestFailed(test,
253
+ reporter.onTestFailed(
254
+ test,
223
255
  test.failureReason,
224
- utils.getTestUrl(this.options.editorUrl,
225
- this.options.project,
226
- testId,
227
- resultId,
228
- this.branchToUse),
256
+ utils.getTestUrl(this.options.editorUrl, this.options.project, testId, resultId, this.branchToUse),
229
257
  testId,
230
258
  isRerun,
231
- resultId);
259
+ resultId,
260
+ );
232
261
  }
233
262
 
234
263
  calcResultText(result) {
@@ -298,7 +327,7 @@ class RunStatus {
298
327
  const globalParameters = result.exportsGlobal;
299
328
  try {
300
329
  try {
301
- await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
330
+ await runHook(this.options.afterTest, Object.assign({}, test, { exportsGlobal: globalParameters, globalParameters }), this.options.userData.loginData.token);
302
331
  } catch (err) {
303
332
  logger.error('HOOK threw an error', { test: test.testId, err });
304
333
  // eslint-disable-next-line no-console
@@ -349,6 +378,10 @@ class RunStatus {
349
378
 
350
379
  const runConfig = options.browser ? utils.getRunConfigByBrowserName(options.browser, options.saucelabs, options.browserstack) : testInfo.runConfig;
351
380
 
381
+ if (runConfig && featureFlags.flags.dec2022eolBrowsers.isEnabled() && utils.getBrowserInfo(runConfig.browserValue)?.eol) {
382
+ throw new ArgError(`Unsupported Browser: ${runConfig.browserName}`);
383
+ }
384
+
352
385
  resultStatus[testInfo.resultId].config = Object.assign({}, this.execConfig, {
353
386
  companyId,
354
387
  testData: testInfo.testData?.value ? testInfo.testData.value : null,
@@ -364,7 +397,7 @@ class RunStatus {
364
397
  }, {});
365
398
  }
366
399
 
367
- executionStart(executionId, projectId, startTime, testPlanName, testNames) {
400
+ async executionStart(executionId, projectId, startTime, testPlanName, testNames) {
368
401
  logger.info('execution started', { executionId });
369
402
  const { options } = this;
370
403
  const { remoteRunId, projectData } = options;
@@ -383,66 +416,74 @@ class RunStatus {
383
416
  ]));
384
417
 
385
418
  this.startTime = startTime || Date.now();
386
- const runHooksProps = { projectId, executionId };
387
- if (featureFlags.flags.testNamesToBeforeSuiteHook.isEnabled()) {
388
- runHooksProps.testNames = testNames;
419
+ const runHooksProps = {
420
+ projectId,
421
+ executionId,
422
+ ...(featureFlags.flags.testNamesToBeforeSuiteHook.isEnabled() && { testNames }),
423
+ };
424
+ const params = await runHook(options.beforeSuite, runHooksProps);
425
+ const overrideTestDataBuilder = new OverrideTestDataBuilder(params, _.cloneDeep(this.testInfoList), projectId);
426
+ this.testInfoList = overrideTestDataBuilder.overrideTestData();
427
+ this.calcTestRunStatus();
428
+ this.beforeSuiteParams = params;
429
+
430
+ const { testInfoList } = this;
431
+ const beforeTests = [];
432
+ const tests = [];
433
+ const afterTests = [];
434
+ for (const test of testInfoList) {
435
+ if (test.isBeforeTestPlan) {
436
+ beforeTests.push(test);
437
+ continue;
438
+ }
439
+ if (test.isAfterTestPlan) {
440
+ afterTests.push(test);
441
+ continue;
442
+ }
443
+ tests.push(test);
389
444
  }
390
- return runHook(options.beforeSuite, runHooksProps)
391
- .then(params => {
392
- const overrideTestDataBuilder = new OverrideTestDataBuilder(params, _.cloneDeep(this.testInfoList), projectId);
393
- this.testInfoList = overrideTestDataBuilder.overrideTestData();
394
- this.calcTestRunStatus();
395
- this.beforeSuiteParams = params;
396
-
397
- const { testInfoList } = this;
398
- const beforeTests = testInfoList.filter(test => test.isBeforeTestPlan);
399
- const tests = testInfoList.filter(test => !test.isBeforeTestPlan && !test.isAfterTestPlan);
400
- const afterTests = testInfoList.filter(test => test.isAfterTestPlan);
401
-
402
- const reportExecutionStarted = () => {
403
- const testResults = _.cloneDeep(this.testRunStatus);
404
- return promiseMap(Object.keys(testResults), testResultId => {
405
- const test = testResults[testResultId];
406
- const testData = test.config?.testData;
407
- const testId = test.testId;
408
- return servicesApi.updateTestDataArtifact(projectId, testId, testResultId, testData, projectData.defaults)
409
- .then((testDataUrl) => {
410
- if (!testDataUrl) {
411
- return;
412
- }
413
- delete test.config.testData;
414
- test.config.testDataUrl = testDataUrl;
415
- });
416
- }).then(() => {
417
- const isLocalRun = Boolean(options.useLocalChromeDriver || options.useChromeLauncher);
418
- const data = {
419
- executionId,
420
- projectId,
421
- labels: testPlanName || [],
422
- startTime,
423
- executions: testResults,
424
- config: this.execConfig,
425
- resultLabels: options.resultLabels,
426
- remoteRunId: options.remoteRunId,
427
- localRunUserId: options.user,
428
- isLocalRun,
429
- intersections: options.intersections,
430
- };
431
- const ret = servicesApi.reportExecutionStarted(data);
432
- this.executionStartedPromise = ret;
433
- ret.catch(e => logger.error(e));
434
- return ret;
435
- });
436
- };
437
-
438
- return reportExecutionStarted()
439
- .catch(err => {
440
- logger.error('Failed to start suite', { err });
441
- // eslint-disable-next-line no-console
442
- console.error('Failed to start test run. Please contact support@testim.io');
443
- })
444
- .then(() => ({ beforeTests, tests, afterTests }));
445
+
446
+ const reportExecutionStarted = async () => {
447
+ const testResults = _.cloneDeep(this.testRunStatus);
448
+ await utils.promiseMap(Object.keys(testResults), async testResultId => {
449
+ const test = testResults[testResultId];
450
+ const testData = test.config?.testData;
451
+ const testId = test.testId;
452
+ const testDataUrl = await servicesApi.updateTestDataArtifact(projectId, testId, testResultId, testData, projectData.defaults);
453
+ if (!testDataUrl) {
454
+ return;
455
+ }
456
+ delete test.config.testData;
457
+ test.config.testDataUrl = testDataUrl;
445
458
  });
459
+ const isLocalRun = Boolean(options.useLocalChromeDriver || options.useChromeLauncher);
460
+ const data = {
461
+ executionId,
462
+ projectId,
463
+ labels: testPlanName || [],
464
+ startTime,
465
+ executions: testResults,
466
+ config: this.execConfig,
467
+ resultLabels: options.resultLabels,
468
+ remoteRunId: options.remoteRunId,
469
+ localRunUserId: options.user,
470
+ isLocalRun,
471
+ intersections: options.intersections,
472
+ };
473
+ const ret = servicesApi.reportExecutionStarted(data);
474
+ this.executionStartedPromise = ret;
475
+ ret.catch(e => logger.error(e));
476
+ return ret;
477
+ };
478
+
479
+ try {
480
+ await reportExecutionStarted();
481
+ } catch (err) {
482
+ logger.error('Failed to start suite', { err });
483
+ // eslint-disable-next-line no-console
484
+ console.error('Failed to start test run. Please contact support@testim.io');
485
+ }
486
+ return { beforeTests, tests, afterTests };
446
487
  }
447
488
 
448
489
  concatSeleniumPerfMarks(marks) {
@@ -456,47 +497,58 @@ class RunStatus {
456
497
  .value();
457
498
  }
458
499
 
459
- executionEnd(executionId) {
500
+ async executionEnd(executionId) {
460
501
  const tests = utils.groupTestsByRetries(this.testRunStatus);
461
502
  const total = tests.length;
462
- const passed = tests.filter(({ status }) => status === constants.runnerTestStatus.PASSED).length;
463
- const skipped = tests.filter(({ status }) => status === constants.runnerTestStatus.SKIPPED).length;
464
- const failedInEvaluatingStatus = tests.filter(({ status, testStatus }) => status === constants.runnerTestStatus.FAILED && testStatus === constants.testStatus.EVALUATING).length;
465
503
 
466
- const resultExtraData = { ...this.seleniumPerfStats.getStats() };
467
- delete resultExtraData.seleniumPerfMarks;
504
+ let passed = 0;
505
+ let skipped = 0;
506
+ let failedInEvaluatingStatus = 0;
507
+ for (const { status, testStatus } of tests) {
508
+ if (status === constants.runnerTestStatus.PASSED) {
509
+ passed++;
510
+ }
511
+ if (status === constants.runnerTestStatus.SKIPPED) {
512
+ skipped++;
513
+ }
514
+ if (status === constants.runnerTestStatus.FAILED && testStatus === constants.testStatus.EVALUATING) {
515
+ failedInEvaluatingStatus++;
516
+ }
517
+ }
518
+
519
+ const { seleniumPerfMarks, ...resultExtraData } = this.seleniumPerfStats.getStats();
468
520
 
469
- return runHook(this.options.afterSuite, {
521
+ await runHook(this.options.afterSuite, {
470
522
  exportsGlobal: this.exportsGlobal,
471
523
  tests,
472
524
  total,
473
525
  passed,
474
526
  skipped,
475
- })
476
- .then(() => calculateCoverage(this.options, this.branchToUse, total, executionId))
477
- .then((coverageSummary) => {
478
- resultExtraData.coverageSummary = coverageSummary;
527
+ });
528
+ const coverageSummary = await calculateCoverage(this.options, this.branchToUse, total, executionId);
529
+ resultExtraData.coverageSummary = coverageSummary;
479
530
 
480
- if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
481
- return undefined;
482
- }
483
- return servicesApi.reportExecutionFinished(
484
- 'FINISHED',
485
- executionId,
486
- this.options.project,
487
- total === (passed + skipped + failedInEvaluatingStatus),
488
- {
489
- tmsSuppressReporting: this.options.tmsSuppressReporting,
490
- tmsRunId: this.options.tmsRunId,
491
- tmsCustomFields: this.options.tmsCustomFields,
492
- },
493
- this.options.remoteRunId,
494
- resultExtraData
495
- ).catch(err => {
496
- logger.error('Failed to update suite finished', { err });
497
- throw err;
498
- });
499
- });
531
+ if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
532
+ return undefined;
533
+ }
534
+ try {
535
+ return await servicesApi.reportExecutionFinished(
536
+ 'FINISHED',
537
+ executionId,
538
+ this.options.project,
539
+ total === (passed + skipped + failedInEvaluatingStatus),
540
+ {
541
+ tmsSuppressReporting: this.options.tmsSuppressReporting,
542
+ tmsRunId: this.options.tmsRunId,
543
+ tmsCustomFields: this.options.tmsCustomFields,
544
+ },
545
+ this.options.remoteRunId,
546
+ resultExtraData
547
+ );
548
+ } catch (err) {
549
+ logger.error('Failed to update suite finished', { err });
550
+ throw err;
551
+ }
500
552
  }
501
553
 
502
554
  async markAllQueuedTests(executionId, status, failureReason, success) {
package/utils/index.js CHANGED
@@ -43,16 +43,22 @@ const BROWSERS = [
43
43
  { browserName: 'Chrome', bs: { browser: 'Chrome', browser_version: '94' }, sl: { browserName: 'chrome', version: '94.0' }, browserValue: 'chrome' },
44
44
  { browserName: 'Firefox', bs: { browser: 'Firefox', browser_version: '89' }, sl: { browserName: 'firefox', version: '89.0' }, browserValue: 'firefox' },
45
45
  { browserName: 'Safari', bs: { browser: 'Safari' }, sl: { browserName: 'safari' }, browserValue: 'safari' },
46
- { browserName: 'Edge', bs: { browser: 'Edge', browser_version: '18' }, sl: { browserName: 'MicrosoftEdge', version: '18.17763' }, browserValue: 'edge' },
46
+ { browserName: 'Edge', bs: { browser: 'Edge', browser_version: '18' }, sl: { browserName: 'MicrosoftEdge', version: '18.17763' }, browserValue: 'edge', eol: true },
47
47
  // eslint-disable-next-line max-len
48
48
  { browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '94' }, sl: { browserName: 'MicrosoftEdge', version: '94' }, synonyms: ['edge-chromium'], browserValue: 'edge-chromium', seleniumName: 'MicrosoftEdge' },
49
- { browserName: 'Internet Explorer 11', bs: { browser: 'IE', browser_version: '11' }, sl: { browserName: 'internet explorer', version: '11.0' }, synonyms: ['ie11'], browserValue: 'ie11' },
49
+ // eslint-disable-next-line max-len
50
+ { browserName: 'Internet Explorer 11', bs: { browser: 'IE', browser_version: '11' }, sl: { browserName: 'internet explorer', version: '11.0' }, synonyms: ['ie11'], browserValue: 'ie11', eol: true },
50
51
  { browserName: 'Browser', bs: {}, sl: { browserName: 'Browser' }, browserValue: 'browser' },
51
52
  { browserName: 'Android', bs: { browserName: 'android' }, sl: {}, browserValue: 'android' },
52
53
  { browserName: 'iPad', bs: { browserName: 'iPad' }, sl: {}, browserValue: 'ipad' },
53
54
  { browserName: 'iPhone', bs: { browserName: 'iPhone' }, sl: {}, browserValue: 'iphone' },
54
55
  ];
55
56
 
57
+ function getBrowserInfo(browserValue) {
58
+ browserValue = browserValue.toLowerCase();
59
+ return BROWSERS.find((b) => b.browserValue === browserValue);
60
+ }
61
+
56
62
  function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
57
63
  browser = browser.toLowerCase();
58
64
  const selectedBrowser = BROWSERS.find(b => b.browserName.toLowerCase() === browser || browser.includes(b.synonyms)) || BROWSERS[0];
@@ -182,6 +188,7 @@ module.exports = {
182
188
  groupTestsByRetries,
183
189
  getPlanType,
184
190
  getCdpAddressForHost,
191
+ getBrowserInfo,
185
192
  ...fsUtils,
186
193
  ...argsUtils,
187
194
  ...timeUtils,
@@ -38,6 +38,17 @@ function buildFailureResult(testId, testName, resultId, reason) {
38
38
  }
39
39
 
40
40
  class BaseWorker {
41
+ /**
42
+ * @param {import('../executionQueue')} executionQueue
43
+ * @param {import('../runOptions').RunnerOptions} options
44
+ * @param {string=} customExtensionLocalLocation
45
+ * @param {string} executionId
46
+ * @param {Function} onTestStarted
47
+ * @param {Function} onTestCompleted
48
+ * @param {Function} onGridSlot
49
+ * @param {Function} onTestIgnored
50
+ * @param {boolean=} releaseSlotOnTestFinished
51
+ */
41
52
  constructor(executionQueue, options, customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored, releaseSlotOnTestFinished = true) {
42
53
  this.lambdatestService = new LambdatestService();
43
54