codeceptjs 3.5.9-beta.2 → 3.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,7 @@ Reference: [Helpers API](https://github.com/codeceptjs/CodeceptJS/tree/master/do
24
24
  ## Supercharged E2E Testing
25
25
 
26
26
  CodeceptJS is a new testing framework for end-to-end testing with WebDriver (or others).
27
- It abstracts browser interaction to simple steps that are written from a user perspective.
27
+ It abstracts browser interaction to simple steps that are written from a user's perspective.
28
28
  A simple test that verifies the "Welcome" text is present on a main page of a site will look like:
29
29
 
30
30
  ```js
@@ -57,27 +57,27 @@ And more to come...
57
57
 
58
58
  CodeceptJS is a successor of [Codeception](http://codeception.com), a popular full-stack testing framework for PHP.
59
59
  With CodeceptJS your scenario-driven functional and acceptance tests will be as simple and clean as they can be.
60
- You don't need to worry about asynchronous nature of NodeJS or about various APIs of Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
60
+ You don't need to worry about asynchronous nature of NodeJS or about various APIs of Playwright, Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
61
61
 
62
62
 
63
63
  ## Features
64
64
 
65
- * 🪄 **AI-powered** with GPT features to assist and heal failing tests
66
- * Based on [Mocha](https://mochajs.org/) testing framework.
67
- * Designed for scenario driven acceptance testing in BDD-style
68
- * Uses ES6 natively without transpiler.
65
+ * 🪄 **AI-powered** with GPT features to assist and heal failing tests.
66
+ * Based on [Mocha](https://mochajs.org/) testing framework.
67
+ * 💼 Designed for scenario driven acceptance testing in BDD-style.
68
+ * 💻 Uses ES6 natively without transpiler.
69
69
  * Also plays nice with TypeScript.
70
- * Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
71
- * Interactive debugging shell: pause test at any point and try different commands in a browser.
70
+ * </> Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
71
+ * 🌐 Interactive debugging shell: pause test at any point and try different commands in a browser.
72
72
  * Easily create tests, pageobjects, stepobjects with CLI generators.
73
73
 
74
- ## Install
74
+ ## Installation
75
75
 
76
76
  ```sh
77
77
  npm i codeceptjs --save
78
78
  ```
79
79
 
80
- Move to directory where you'd like to have your tests (and codeceptjs config) stored, and execute
80
+ Move to directory where you'd like to have your tests (and CodeceptJS config) stored, and execute:
81
81
 
82
82
  ```sh
83
83
  npx codeceptjs init
@@ -131,8 +131,8 @@ Scenario('test some forms', ({ I }) => {
131
131
  });
132
132
  ```
133
133
 
134
- All actions are performed by I object; assertions functions start with `see` function.
135
- In this examples all methods of `I` are taken from WebDriver helper, see [reference](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) to learn how to use them.
134
+ All actions are performed by `I` object; assertions functions start with `see` function.
135
+ In these examples all methods of `I` are taken from WebDriver helper, see [reference](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) to learn how to use them.
136
136
 
137
137
  Let's execute this test with `run` command. Additional option `--steps` will show us the running process. We recommend use `--steps` or `--debug` during development.
138
138
 
@@ -198,17 +198,15 @@ In this case 'User is valid' string will be searched only inside elements locate
198
198
  ### Grabbers
199
199
 
200
200
  In case you need to return a value from a webpage and use it directly in test, you should use methods with `grab` prefix.
201
- They are expected to be used inside async/await functions, and their results will be available in test:
201
+ They are expected to be used inside `async/await` functions, and their results will be available in test:
202
202
 
203
203
  ```js
204
- const assert = require('assert');
205
-
206
204
  Feature('CodeceptJS Demonstration');
207
205
 
208
206
  Scenario('test page title', async ({ I }) => {
209
207
  I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
210
208
  const title = await I.grabTitle();
211
- assert.equal(title, 'Example application with SimpleForm and Twitter Bootstrap');
209
+ I.expectEqual(title, 'Example application with SimpleForm and Twitter Bootstrap'); // Avaiable with Expect helper. -> https://codecept.io/helpers/Expect/
212
210
  });
213
211
  ```
214
212
 
@@ -276,7 +276,7 @@ class Appium extends Webdriver {
276
276
  const _convertedCaps = {};
277
277
  for (const [key, value] of Object.entries(capabilities)) {
278
278
  if (!key.startsWith(vendorPrefix.appium)) {
279
- if (key !== 'platformName') {
279
+ if (key !== 'platformName' && key !== 'bstack:options') {
280
280
  _convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
281
281
  } else {
282
282
  _convertedCaps[`${key}`] = value;
@@ -423,8 +423,8 @@ class Appium extends Webdriver {
423
423
  async runOnIOS(caps, fn) {
424
424
  if (this.platform !== 'ios') return;
425
425
  recorder.session.start('iOS-only actions');
426
- this._runWithCaps(caps, fn);
427
- recorder.add('restore from iOS session', () => recorder.session.restore());
426
+ await this._runWithCaps(caps, fn);
427
+ await recorder.add('restore from iOS session', () => recorder.session.restore());
428
428
  return recorder.promise();
429
429
  }
430
430
 
@@ -465,8 +465,8 @@ class Appium extends Webdriver {
465
465
  async runOnAndroid(caps, fn) {
466
466
  if (this.platform !== 'android') return;
467
467
  recorder.session.start('Android-only actions');
468
- this._runWithCaps(caps, fn);
469
- recorder.add('restore from Android session', () => recorder.session.restore());
468
+ await this._runWithCaps(caps, fn);
469
+ await recorder.add('restore from Android session', () => recorder.session.restore());
470
470
  return recorder.promise();
471
471
  }
472
472
 
@@ -1424,10 +1424,10 @@ class Appium extends Webdriver {
1424
1424
  *
1425
1425
  * @return {Promise<void>}
1426
1426
  *
1427
- * Appium: support only iOS
1427
+ * Appium: support both Android and iOS
1428
1428
  */
1429
1429
  async closeApp() {
1430
- onlyForApps.call(this, 'iOS');
1430
+ onlyForApps.call(this);
1431
1431
  return this.browser.closeApp();
1432
1432
  }
1433
1433
 
@@ -362,7 +362,7 @@ class Playwright extends Helper {
362
362
  ignoreLog: ['warning', 'log'],
363
363
  uniqueScreenshotNames: false,
364
364
  manualStart: false,
365
- getPageTimeout: 0,
365
+ getPageTimeout: 30000,
366
366
  waitForNavigation: 'load',
367
367
  restart: false,
368
368
  keepCookies: false,
@@ -474,7 +474,7 @@ class Playwright extends Helper {
474
474
  async _before(test) {
475
475
  this.currentRunningTest = test;
476
476
  recorder.retry({
477
- retries: 5,
477
+ retries: process.env.FAILED_STEP_RETRIES || 3,
478
478
  when: err => {
479
479
  if (!err || typeof (err.message) !== 'string') {
480
480
  return false;
@@ -1450,6 +1450,40 @@ class Playwright extends Helper {
1450
1450
  return findFields.call(this, locator);
1451
1451
  }
1452
1452
 
1453
+ /**
1454
+ * Grab WebElements for given locator
1455
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
1456
+ *
1457
+ * ```js
1458
+ * const webElements = await I.grabWebElements('#button');
1459
+ * ```
1460
+ *
1461
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1462
+ * @returns {Promise<*>} WebElement of being used Web helper
1463
+ *
1464
+ *
1465
+ */
1466
+ async grabWebElements(locator) {
1467
+ return this._locate(locator);
1468
+ }
1469
+
1470
+ /**
1471
+ * Grab WebElement for given locator
1472
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
1473
+ *
1474
+ * ```js
1475
+ * const webElement = await I.grabWebElement('#button');
1476
+ * ```
1477
+ *
1478
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1479
+ * @returns {Promise<*>} WebElement of being used Web helper
1480
+ *
1481
+ *
1482
+ */
1483
+ async grabWebElement(locator) {
1484
+ return this._locateElement(locator);
1485
+ }
1486
+
1453
1487
  /**
1454
1488
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
1455
1489
  *
@@ -2265,8 +2299,15 @@ class Playwright extends Helper {
2265
2299
  const el = els[0];
2266
2300
 
2267
2301
  await highlightActiveElement.call(this, el);
2302
+ let optionToSelect = '';
2268
2303
 
2269
- if (!Array.isArray(option)) option = [option];
2304
+ try {
2305
+ optionToSelect = await el.locator('option', { hasText: option }).textContent();
2306
+ } catch (e) {
2307
+ optionToSelect = option;
2308
+ }
2309
+
2310
+ if (!Array.isArray(option)) option = [optionToSelect];
2270
2311
 
2271
2312
  await el.selectOption(option);
2272
2313
  return this._waitForAction();
@@ -2856,19 +2897,17 @@ class Playwright extends Helper {
2856
2897
 
2857
2898
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
2858
2899
  const elemAmount = res.length;
2859
- const commands = [];
2860
2900
  let props = [];
2861
2901
 
2862
2902
  for (const element of res) {
2863
- const cssProperties = await element.evaluate((el) => getComputedStyle(el));
2864
-
2865
- Object.keys(cssPropertiesCamelCase).forEach(prop => {
2903
+ for (const prop of Object.keys(cssProperties)) {
2904
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
2866
2905
  if (isColorProperty(prop)) {
2867
- props.push(convertColorToRGBA(cssProperties[prop]));
2906
+ props.push(convertColorToRGBA(cssProp));
2868
2907
  } else {
2869
- props.push(cssProperties[prop]);
2908
+ props.push(cssProp);
2870
2909
  }
2871
- });
2910
+ }
2872
2911
  }
2873
2912
 
2874
2913
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
@@ -2876,7 +2915,8 @@ class Playwright extends Helper {
2876
2915
  let chunked = chunkArray(props, values.length);
2877
2916
  chunked = chunked.filter((val) => {
2878
2917
  for (let i = 0; i < val.length; ++i) {
2879
- if (val[i] !== values[i]) return false;
2918
+ // eslint-disable-next-line eqeqeq
2919
+ if (val[i] != values[i]) return false;
2880
2920
  }
2881
2921
  return true;
2882
2922
  });
@@ -2914,7 +2954,7 @@ class Playwright extends Helper {
2914
2954
  let chunked = chunkArray(attrs, values.length);
2915
2955
  chunked = chunked.filter((val) => {
2916
2956
  for (let i = 0; i < val.length; ++i) {
2917
- if (val[i] !== values[i]) return false;
2957
+ if (!val[i].includes(values[i])) return false;
2918
2958
  }
2919
2959
  return true;
2920
2960
  });
@@ -3582,7 +3622,7 @@ class Playwright extends Helper {
3582
3622
  const _contextObject = this.frame ? this.frame : contextObject;
3583
3623
  let count = 0;
3584
3624
  do {
3585
- waiter = await _contextObject.locator(`:has-text('${text}')`).first().isVisible();
3625
+ waiter = await _contextObject.locator(`:has-text("${text}")`).first().isVisible();
3586
3626
  if (waiter) break;
3587
3627
  await this.wait(1);
3588
3628
  count += 1000;
@@ -297,7 +297,7 @@ class Puppeteer extends Helper {
297
297
  this.sessionPages = {};
298
298
  this.currentRunningTest = test;
299
299
  recorder.retry({
300
- retries: 3,
300
+ retries: process.env.FAILED_STEP_RETRIES || 3,
301
301
  when: err => {
302
302
  if (!err || typeof (err.message) !== 'string') {
303
303
  return false;
@@ -1063,6 +1063,23 @@ class Puppeteer extends Helper {
1063
1063
  return findFields.call(this, locator);
1064
1064
  }
1065
1065
 
1066
+ /**
1067
+ * Grab WebElements for given locator
1068
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
1069
+ *
1070
+ * ```js
1071
+ * const webElements = await I.grabWebElements('#button');
1072
+ * ```
1073
+ *
1074
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1075
+ * @returns {Promise<*>} WebElement of being used Web helper
1076
+ *
1077
+ *
1078
+ */
1079
+ async grabWebElements(locator) {
1080
+ return this._locate(locator);
1081
+ }
1082
+
1066
1083
  /**
1067
1084
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
1068
1085
  *
@@ -2589,29 +2606,26 @@ class Puppeteer extends Helper {
2589
2606
 
2590
2607
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
2591
2608
  const elemAmount = res.length;
2592
- const commands = [];
2593
- res.forEach((el) => {
2594
- Object.keys(cssPropertiesCamelCase).forEach((prop) => {
2595
- commands.push(el.executionContext()
2596
- .evaluate((el) => {
2597
- const style = window.getComputedStyle ? getComputedStyle(el) : el.currentStyle;
2598
- return JSON.parse(JSON.stringify(style));
2599
- }, el)
2600
- .then((props) => {
2601
- if (isColorProperty(prop)) {
2602
- return convertColorToRGBA(props[prop]);
2603
- }
2604
- return props[prop];
2605
- }));
2606
- });
2607
- });
2608
- let props = await Promise.all(commands);
2609
+ let props = [];
2610
+
2611
+ for (const element of res) {
2612
+ for (const prop of Object.keys(cssProperties)) {
2613
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
2614
+ if (isColorProperty(prop)) {
2615
+ props.push(convertColorToRGBA(cssProp));
2616
+ } else {
2617
+ props.push(cssProp);
2618
+ }
2619
+ }
2620
+ }
2621
+
2609
2622
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
2610
2623
  if (!Array.isArray(props)) props = [props];
2611
2624
  let chunked = chunkArray(props, values.length);
2612
2625
  chunked = chunked.filter((val) => {
2613
2626
  for (let i = 0; i < val.length; ++i) {
2614
- if (val[i] !== values[i]) return false;
2627
+ // eslint-disable-next-line eqeqeq
2628
+ if (val[i] != values[i]) return false;
2615
2629
  }
2616
2630
  return true;
2617
2631
  });
@@ -2651,7 +2665,9 @@ class Puppeteer extends Helper {
2651
2665
  let chunked = chunkArray(attrs, values.length);
2652
2666
  chunked = chunked.filter((val) => {
2653
2667
  for (let i = 0; i < val.length; ++i) {
2654
- if (val[i] !== values[i]) return false;
2668
+ const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
2669
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
2670
+ if (!_actual.includes(_expected)) return false;
2655
2671
  }
2656
2672
  return true;
2657
2673
  });
@@ -869,6 +869,23 @@ class WebDriver extends Helper {
869
869
  return findFields.call(this, locator).then(res => res);
870
870
  }
871
871
 
872
+ /**
873
+ * Grab WebElements for given locator
874
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
875
+ *
876
+ * ```js
877
+ * const webElements = await I.grabWebElements('#button');
878
+ * ```
879
+ *
880
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
881
+ * @returns {Promise<*>} WebElement of being used Web helper
882
+ *
883
+ *
884
+ */
885
+ async grabWebElements(locator) {
886
+ return this._locate(locator);
887
+ }
888
+
872
889
  /**
873
890
  * Set [WebDriver timeouts](https://webdriver.io/docs/timeouts.html) in realtime.
874
891
  *
@@ -2013,7 +2030,9 @@ class WebDriver extends Helper {
2013
2030
  let chunked = chunkArray(props, values.length);
2014
2031
  chunked = chunked.filter((val) => {
2015
2032
  for (let i = 0; i < val.length; ++i) {
2016
- if (val[i] !== values[i]) return false;
2033
+ const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
2034
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
2035
+ if (_acutal !== _expected) return false;
2017
2036
  }
2018
2037
  return true;
2019
2038
  });
@@ -2049,7 +2068,9 @@ class WebDriver extends Helper {
2049
2068
  let chunked = chunkArray(attrs, values.length);
2050
2069
  chunked = chunked.filter((val) => {
2051
2070
  for (let i = 0; i < val.length; ++i) {
2052
- if (val[i] !== values[i]) return false;
2071
+ const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
2072
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
2073
+ if (_acutal !== _expected) return false;
2053
2074
  }
2054
2075
  return true;
2055
2076
  });
@@ -871,7 +871,7 @@ Close the given application.
871
871
  I.closeApp();
872
872
  ```
873
873
 
874
- Returns **[Promise][6]&lt;void>** Appium: support only iOS
874
+ Returns **[Promise][6]&lt;void>** Appium: support both Android and iOS
875
875
 
876
876
  ### appendField
877
877
 
@@ -1389,6 +1389,36 @@ let inputs = await I.grabValueFromAll('//form/input');
1389
1389
 
1390
1390
  Returns **[Promise][22]&lt;[Array][10]&lt;[string][9]>>** attribute value
1391
1391
 
1392
+ ### grabWebElement
1393
+
1394
+ Grab WebElement for given locator
1395
+ Resumes test execution, so **should be used inside an async function with `await`** operator.
1396
+
1397
+ ```js
1398
+ const webElement = await I.grabWebElement('#button');
1399
+ ```
1400
+
1401
+ #### Parameters
1402
+
1403
+ - `locator` **([string][9] | [object][6])** element located by CSS|XPath|strict locator.
1404
+
1405
+ Returns **[Promise][22]&lt;any>** WebElement of being used Web helper
1406
+
1407
+ ### grabWebElements
1408
+
1409
+ Grab WebElements for given locator
1410
+ Resumes test execution, so **should be used inside an async function with `await`** operator.
1411
+
1412
+ ```js
1413
+ const webElements = await I.grabWebElements('#button');
1414
+ ```
1415
+
1416
+ #### Parameters
1417
+
1418
+ - `locator` **([string][9] | [object][6])** element located by CSS|XPath|strict locator.
1419
+
1420
+ Returns **[Promise][22]&lt;any>** WebElement of being used Web helper
1421
+
1392
1422
  ### grabWebSocketMessages
1393
1423
 
1394
1424
  Grab the recording WS messages
@@ -1216,6 +1216,21 @@ let inputs = await I.grabValueFromAll('//form/input');
1216
1216
 
1217
1217
  Returns **[Promise][13]&lt;[Array][15]&lt;[string][6]>>** attribute value
1218
1218
 
1219
+ ### grabWebElements
1220
+
1221
+ Grab WebElements for given locator
1222
+ Resumes test execution, so **should be used inside an async function with `await`** operator.
1223
+
1224
+ ```js
1225
+ const webElements = await I.grabWebElements('#button');
1226
+ ```
1227
+
1228
+ #### Parameters
1229
+
1230
+ - `locator` **([string][6] | [object][4])** element located by CSS|XPath|strict locator.
1231
+
1232
+ Returns **[Promise][13]&lt;any>** WebElement of being used Web helper
1233
+
1219
1234
  ### handleDownloads
1220
1235
 
1221
1236
  Sets a directory to where save files. Allows to test file downloads.
@@ -1367,6 +1367,21 @@ let inputs = await I.grabValueFromAll('//form/input');
1367
1367
 
1368
1368
  Returns **[Promise][25]&lt;[Array][28]&lt;[string][17]>>** attribute value
1369
1369
 
1370
+ ### grabWebElements
1371
+
1372
+ Grab WebElements for given locator
1373
+ Resumes test execution, so **should be used inside an async function with `await`** operator.
1374
+
1375
+ ```js
1376
+ const webElements = await I.grabWebElements('#button');
1377
+ ```
1378
+
1379
+ #### Parameters
1380
+
1381
+ - `locator` **([string][17] | [object][16])** element located by CSS|XPath|strict locator.
1382
+
1383
+ Returns **[Promise][25]&lt;any>** WebElement of being used Web helper
1384
+
1370
1385
  ### moveCursorTo
1371
1386
 
1372
1387
  Moves cursor to element matched by locator.
@@ -96,117 +96,7 @@ module.exports = function() {
96
96
  });
97
97
  }
98
98
  ```
99
- You could get test stats when running with workers
100
99
 
101
- ```js
102
- const { event } = require('codeceptjs');
103
-
104
- module.exports = function() {
105
-
106
- event.dispatcher.on(event.workers.result, function (result) {
107
-
108
- console.log(result);
109
-
110
- });
111
- }
112
-
113
- // in console log
114
- FAIL | 7 passed, 1 failed, 1 skipped // 2s
115
- {
116
- "tests": {
117
- "passed": [
118
- {
119
- "type": "test",
120
- "title": "Assert @C3",
121
- "body": "() => { }",
122
- "async": 0,
123
- "sync": true,
124
- "_timeout": 2000,
125
- "_slow": 75,
126
- "_retries": -1,
127
- "timedOut": false,
128
- "_currentRetry": 0,
129
- "pending": false,
130
- "opts": {},
131
- "tags": [
132
- "@C3"
133
- ],
134
- "uid": "xe4q1HdqpRrZG5dPe0JG+A",
135
- "workerIndex": 3,
136
- "retries": -1,
137
- "duration": 493,
138
- "err": null,
139
- "parent": {
140
- "title": "My",
141
- "ctx": {},
142
- "suites": [],
143
- "tests": [],
144
- "root": false,
145
- "pending": false,
146
- "_retries": -1,
147
- "_beforeEach": [],
148
- "_beforeAll": [],
149
- "_afterEach": [],
150
- "_afterAll": [],
151
- "_timeout": 2000,
152
- "_slow": 75,
153
- "_bail": false,
154
- "_onlyTests": [],
155
- "_onlySuites": [],
156
- "delayed": false
157
- },
158
- "steps": [
159
- {
160
- "actor": "I",
161
- "name": "amOnPage",
162
- "status": "success",
163
- "agrs": [
164
- "https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST"
165
- ],
166
- "startedAt": 1698760652610,
167
- "startTime": 1698760652611,
168
- "endTime": 1698760653098,
169
- "finishedAt": 1698760653098,
170
- "duration": 488
171
- },
172
- {
173
- "actor": "I",
174
- "name": "grabCurrentUrl",
175
- "status": "success",
176
- "agrs": [],
177
- "startedAt": 1698760653098,
178
- "startTime": 1698760653098,
179
- "endTime": 1698760653099,
180
- "finishedAt": 1698760653099,
181
- "duration": 1
182
- }
183
- ]
184
- }
185
- ],
186
- "failed": [],
187
- "skipped": []
188
- }
189
- }
190
- ```
191
-
192
- CodeceptJS also exposes the env var `process.env.RUNS_WITH_WORKERS` when running tests with `run-workers` command so that you could handle the events better in your plugins/helpers
193
-
194
- ```js
195
- const { event } = require('codeceptjs');
196
-
197
- module.exports = function() {
198
- // this event would trigger the `_publishResultsToTestrail` when running `run-workers` command
199
- event.dispatcher.on(event.workers.result, async () => {
200
- await _publishResultsToTestrail();
201
- });
202
-
203
- // this event would not trigger the `_publishResultsToTestrail` multiple times when running `run-workers` command
204
- event.dispatcher.on(event.all.result, async () => {
205
- // when running `run` command, this env var is undefined
206
- if (!process.env.RUNS_WITH_WORKERS) await _publishResultsToTestrail();
207
- });
208
- }
209
- ```
210
100
  Available events:
211
101
 
212
102
  * `event.test.before(test)` - *async* when `Before` hooks from helpers and from test is executed