codeceptjs 3.0.3 → 3.0.7

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 (64) hide show
  1. package/CHANGELOG.md +114 -18
  2. package/bin/codecept.js +1 -0
  3. package/docs/basics.md +2 -2
  4. package/docs/bdd.md +12 -1
  5. package/docs/build/Appium.js +2 -1
  6. package/docs/build/GraphQL.js +9 -10
  7. package/docs/build/Nightmare.js +4 -5
  8. package/docs/build/Playwright.js +164 -37
  9. package/docs/build/Protractor.js +1 -1
  10. package/docs/build/Puppeteer.js +1 -1
  11. package/docs/build/REST.js +24 -4
  12. package/docs/build/TestCafe.js +1 -1
  13. package/docs/build/WebDriver.js +85 -17
  14. package/docs/changelog.md +114 -18
  15. package/docs/data.md +5 -5
  16. package/docs/detox.md +2 -2
  17. package/docs/docker.md +11 -11
  18. package/docs/email.md +8 -8
  19. package/docs/helpers/Appium.md +1 -1
  20. package/docs/helpers/Nightmare.md +4 -5
  21. package/docs/helpers/Playwright.md +94 -64
  22. package/docs/helpers/Protractor.md +1 -1
  23. package/docs/helpers/Puppeteer.md +1 -1
  24. package/docs/helpers/REST.md +9 -0
  25. package/docs/helpers/TestCafe.md +1 -1
  26. package/docs/helpers/WebDriver.md +2 -1
  27. package/docs/locators.md +29 -2
  28. package/docs/mobile-react-native-locators.md +2 -2
  29. package/docs/mobile.md +3 -3
  30. package/docs/nightmare.md +0 -5
  31. package/docs/pageobjects.md +3 -1
  32. package/docs/parallel.md +35 -10
  33. package/docs/playwright.md +55 -8
  34. package/docs/plugins.md +73 -29
  35. package/docs/reports.md +8 -7
  36. package/docs/typescript.md +47 -5
  37. package/docs/webapi/fillField.mustache +1 -1
  38. package/lib/cli.js +25 -10
  39. package/lib/codecept.js +9 -1
  40. package/lib/command/interactive.js +10 -9
  41. package/lib/command/run.js +1 -1
  42. package/lib/command/workers/runTests.js +11 -6
  43. package/lib/config.js +8 -3
  44. package/lib/event.js +2 -0
  45. package/lib/helper/Appium.js +1 -0
  46. package/lib/helper/GraphQL.js +9 -10
  47. package/lib/helper/Nightmare.js +1 -1
  48. package/lib/helper/Playwright.js +131 -38
  49. package/lib/helper/REST.js +24 -4
  50. package/lib/helper/WebDriver.js +84 -16
  51. package/lib/interfaces/gherkin.js +11 -4
  52. package/lib/output.js +7 -4
  53. package/lib/plugin/allure.js +3 -7
  54. package/lib/plugin/fakerTransform.js +51 -0
  55. package/lib/plugin/screenshotOnFail.js +6 -2
  56. package/lib/recorder.js +9 -0
  57. package/lib/step.js +2 -1
  58. package/lib/transform.js +26 -0
  59. package/lib/ui.js +6 -2
  60. package/lib/within.js +1 -1
  61. package/lib/workers.js +39 -25
  62. package/package.json +14 -9
  63. package/typings/index.d.ts +49 -21
  64. package/typings/types.d.ts +72 -26
@@ -396,6 +396,7 @@ class WebDriver extends Helper {
396
396
  this.isRunning = false;
397
397
  this.sessionWindows = {};
398
398
  this.activeSessionName = '';
399
+ this.customLocatorStrategies = config.customLocatorStrategies;
399
400
 
400
401
  this._setConfig(config);
401
402
 
@@ -503,6 +504,33 @@ class WebDriver extends Helper {
503
504
  }
504
505
  }
505
506
 
507
+ _lookupCustomLocator(customStrategy) {
508
+ if (typeof (this.customLocatorStrategies) !== 'object') {
509
+ return null;
510
+ }
511
+ const strategy = this.customLocatorStrategies[customStrategy];
512
+ return typeof (strategy) === 'function' ? strategy : null;
513
+ }
514
+
515
+ _isCustomLocator(locator) {
516
+ const locatorObj = new Locator(locator);
517
+ if (locatorObj.isCustom()) {
518
+ const customLocator = this._lookupCustomLocator(locatorObj.type);
519
+ if (customLocator) {
520
+ return true;
521
+ }
522
+ throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".');
523
+ }
524
+ return false;
525
+ }
526
+
527
+ async _res(locator) {
528
+ const res = (this._isShadowLocator(locator) || this._isCustomLocator(locator))
529
+ ? await this._locate(locator)
530
+ : await this.$$(withStrictLocator(locator));
531
+ return res;
532
+ }
533
+
506
534
  async _startBrowser() {
507
535
  try {
508
536
  if (this.options.multiremote) {
@@ -530,9 +558,22 @@ class WebDriver extends Helper {
530
558
  await this._resizeWindowIfNeeded(this.browser, this.options.windowSize);
531
559
 
532
560
  this.$$ = this.browser.$$.bind(this.browser);
561
+
562
+ if (this._isCustomLocatorStrategyDefined()) {
563
+ Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
564
+ this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`);
565
+ const locatorFunction = this._lookupCustomLocator(customLocator);
566
+ this.browser.addLocatorStrategy(customLocator, locatorFunction);
567
+ });
568
+ }
569
+
533
570
  return this.browser;
534
571
  }
535
572
 
573
+ _isCustomLocatorStrategyDefined() {
574
+ return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length;
575
+ }
576
+
536
577
  async _stopBrowser() {
537
578
  if (this.browser && this.isRunning) await this.browser.deleteSession();
538
579
  }
@@ -718,7 +759,7 @@ class WebDriver extends Helper {
718
759
  * @param {object} locator
719
760
  */
720
761
  async _smartWait(locator) {
721
- this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${locator} in ${this.options.smartWait}`);
762
+ this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`);
722
763
  await this.defineTimeout({ implicit: this.options.smartWait });
723
764
  }
724
765
 
@@ -755,17 +796,34 @@ class WebDriver extends Helper {
755
796
  }
756
797
 
757
798
  if (!this.options.smartWait || !smartWait) {
799
+ if (this._isCustomLocator(locator)) {
800
+ const locatorObj = new Locator(locator);
801
+ return this.browser.custom$$(locatorObj.type, locatorObj.value);
802
+ }
803
+
758
804
  const els = await this.$$(withStrictLocator(locator));
759
805
  return els;
760
806
  }
761
807
 
762
808
  await this._smartWait(locator);
763
809
 
810
+ if (this._isCustomLocator(locator)) {
811
+ const locatorObj = new Locator(locator);
812
+ return this.browser.custom$$(locatorObj.type, locatorObj.value);
813
+ }
814
+
764
815
  const els = await this.$$(withStrictLocator(locator));
765
816
  await this.defineTimeout({ implicit: 0 });
766
817
  return els;
767
818
  }
768
819
 
820
+ _grabCustomLocator(locator) {
821
+ if (typeof locator === 'string') {
822
+ locator = new Locator(locator);
823
+ }
824
+ return locator.value ? locator.value : locator.custom;
825
+ }
826
+
769
827
  /**
770
828
  * Find a checkbox by providing human readable text:
771
829
  *
@@ -962,6 +1020,7 @@ class WebDriver extends Helper {
962
1020
  /**
963
1021
  * {{> fillField }}
964
1022
  * {{ react }}
1023
+ * {{ custom }}
965
1024
  *
966
1025
  */
967
1026
  async fillField(field, value) {
@@ -1335,7 +1394,7 @@ class WebDriver extends Helper {
1335
1394
  *
1336
1395
  */
1337
1396
  async seeElementInDOM(locator) {
1338
- const res = await this.$$(withStrictLocator(locator));
1397
+ const res = await this._res(locator);
1339
1398
  return empty('elements').negate(res);
1340
1399
  }
1341
1400
 
@@ -1344,7 +1403,7 @@ class WebDriver extends Helper {
1344
1403
  *
1345
1404
  */
1346
1405
  async dontSeeElementInDOM(locator) {
1347
- const res = await this.$$(withStrictLocator(locator));
1406
+ const res = await this._res(locator);
1348
1407
  return empty('elements').assert(res);
1349
1408
  }
1350
1409
 
@@ -1370,7 +1429,7 @@ class WebDriver extends Helper {
1370
1429
  */
1371
1430
  async grabBrowserLogs() {
1372
1431
  if (this.browser.isW3C) {
1373
- this.debug('Logs not awailable in W3C specification');
1432
+ this.debug('Logs not available in W3C specification');
1374
1433
  return;
1375
1434
  }
1376
1435
  return this.browser.getLogs('browser');
@@ -1991,7 +2050,7 @@ class WebDriver extends Helper {
1991
2050
  }, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
1992
2051
  }
1993
2052
  return this.browser.waitUntil(async () => {
1994
- const res = await this.$$(withStrictLocator(locator));
2053
+ const res = await this._res(locator);
1995
2054
  if (!res || res.length === 0) {
1996
2055
  return false;
1997
2056
  }
@@ -2018,7 +2077,7 @@ class WebDriver extends Helper {
2018
2077
  }, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
2019
2078
  }
2020
2079
  return this.browser.waitUntil(async () => {
2021
- const res = await this.$$(withStrictLocator(locator));
2080
+ const res = await this._res(locator);
2022
2081
  return res && res.length;
2023
2082
  }, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
2024
2083
  }
@@ -2188,9 +2247,7 @@ class WebDriver extends Helper {
2188
2247
  }, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
2189
2248
  }
2190
2249
  return this.browser.waitUntil(async () => {
2191
- const res = (this._isShadowLocator(locator))
2192
- ? await this._locate(withStrictLocator(locator))
2193
- : await this.$$(withStrictLocator(locator));
2250
+ const res = await this._res(locator);
2194
2251
  if (!res || res.length === 0) return false;
2195
2252
  const selected = await forEachAsync(res, async el => el.isDisplayed());
2196
2253
  if (Array.isArray(selected)) {
@@ -2217,7 +2274,7 @@ class WebDriver extends Helper {
2217
2274
  }, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
2218
2275
  }
2219
2276
  return this.browser.waitUntil(async () => {
2220
- const res = await this.$$(withStrictLocator(locator));
2277
+ const res = await this._res(locator);
2221
2278
  if (!res || res.length === 0) return false;
2222
2279
  let selected = await forEachAsync(res, async el => el.isDisplayed());
2223
2280
 
@@ -2241,7 +2298,7 @@ class WebDriver extends Helper {
2241
2298
  }, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
2242
2299
  }
2243
2300
  return this.browser.waitUntil(async () => {
2244
- const res = await this.$$(withStrictLocator(locator));
2301
+ const res = await this._res(locator);
2245
2302
  if (!res || res.length === 0) return true;
2246
2303
  const selected = await forEachAsync(res, async el => el.isDisplayed());
2247
2304
  return !selected.length;
@@ -2262,7 +2319,7 @@ class WebDriver extends Helper {
2262
2319
  const aSec = sec || this.options.waitForTimeout;
2263
2320
  if (isWebDriver5()) {
2264
2321
  return this.browser.waitUntil(async () => {
2265
- const res = await this.$$(withStrictLocator(locator));
2322
+ const res = await this._res(locator);
2266
2323
  if (!res || res.length === 0) {
2267
2324
  return true;
2268
2325
  }
@@ -2270,7 +2327,7 @@ class WebDriver extends Helper {
2270
2327
  }, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
2271
2328
  }
2272
2329
  return this.browser.waitUntil(async () => {
2273
- const res = await this.$$(withStrictLocator(locator));
2330
+ const res = await this._res(locator);
2274
2331
  if (!res || res.length === 0) {
2275
2332
  return true;
2276
2333
  }
@@ -2543,12 +2600,9 @@ async function proceedSee(assertType, text, context, strict = false) {
2543
2600
  }
2544
2601
 
2545
2602
  const smartWaitEnabled = assertType === 'assert';
2546
-
2547
2603
  const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
2548
2604
  assertElementExists(res, context);
2549
-
2550
2605
  const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
2551
-
2552
2606
  if (strict) {
2553
2607
  if (Array.isArray(selected) && selected.length !== 0) {
2554
2608
  return selected.map(elText => equals(description)[assertType](text, elText));
@@ -2616,6 +2670,11 @@ async function filterAsync(array, callback) {
2616
2670
 
2617
2671
  async function findClickable(locator, locateFn) {
2618
2672
  locator = new Locator(locator);
2673
+
2674
+ if (this._isCustomLocator(locator)) {
2675
+ return locateFn(locator.value);
2676
+ }
2677
+
2619
2678
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
2620
2679
  if (!locator.isFuzzy()) return locateFn(locator, true);
2621
2680
 
@@ -2637,6 +2696,10 @@ async function findClickable(locator, locateFn) {
2637
2696
  async function findFields(locator) {
2638
2697
  locator = new Locator(locator);
2639
2698
 
2699
+ if (this._isCustomLocator(locator)) {
2700
+ return this._locate(locator);
2701
+ }
2702
+
2640
2703
  if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
2641
2704
  if (!locator.isFuzzy()) return this._locate(locator, true);
2642
2705
 
@@ -2742,6 +2805,10 @@ async function findCheckable(locator, locateFn) {
2742
2805
  let els;
2743
2806
  locator = new Locator(locator);
2744
2807
 
2808
+ if (this._isCustomLocator(locator)) {
2809
+ return locateFn(locator.value);
2810
+ }
2811
+
2745
2812
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
2746
2813
  if (!locator.isFuzzy()) return locateFn(locator, true);
2747
2814
 
@@ -2787,6 +2854,7 @@ function getElementId(el) {
2787
2854
  if (el.ELEMENT) {
2788
2855
  return el.ELEMENT;
2789
2856
  }
2857
+
2790
2858
  return null;
2791
2859
  }
2792
2860
 
@@ -6,6 +6,7 @@ const event = require('../event');
6
6
  const scenario = require('../scenario');
7
7
  const Step = require('../step');
8
8
  const DataTableArgument = require('../data/dataTableArgument');
9
+ const transform = require('../transform');
9
10
 
10
11
  const parser = new Parser();
11
12
  parser.stopAtFirstError = false;
@@ -31,7 +32,13 @@ module.exports = (text) => {
31
32
  const metaStep = new Step.MetaStep(null, step.text);
32
33
  metaStep.actor = step.keyword.trim();
33
34
  const setMetaStep = (step) => {
34
- if (step.metaStep) step = step.metaStep; // assign metastep to metastep for nested steps
35
+ if (step.metaStep) {
36
+ if (step.metaStep === metaStep) {
37
+ return;
38
+ }
39
+ setMetaStep(step.metaStep);
40
+ return;
41
+ }
35
42
  step.metaStep = metaStep;
36
43
  };
37
44
  const fn = matchStep(step.text);
@@ -46,14 +53,14 @@ module.exports = (text) => {
46
53
  step.startTime = Date.now();
47
54
  step.match = fn.line;
48
55
  event.emit(event.bddStep.before, step);
49
- event.dispatcher.on(event.step.before, setMetaStep);
56
+ event.dispatcher.prependListener(event.step.before, setMetaStep);
50
57
  try {
51
58
  await fn(...fn.params);
52
59
  step.status = 'passed';
53
60
  } catch (err) {
54
61
  step.status = 'failed';
55
62
  step.err = err;
56
- return err;
63
+ throw err;
57
64
  } finally {
58
65
  step.endTime = Date.now();
59
66
  event.dispatcher.removeListener(event.step.before, setMetaStep);
@@ -75,7 +82,7 @@ module.exports = (text) => {
75
82
  const current = {};
76
83
  for (const index in example.cells) {
77
84
  const placeholder = fields[index];
78
- const value = example.cells[index].value;
85
+ const value = transform('gherkin.examples', example.cells[index].value);
79
86
  current[placeholder] = value;
80
87
  exampleSteps = exampleSteps.map((step) => {
81
88
  step = { ...step };
package/lib/output.js CHANGED
@@ -39,12 +39,12 @@ module.exports = {
39
39
  /**
40
40
  * Print information for a process
41
41
  * Used in multiple-run
42
- * @param {string} process
42
+ * @param {string | null} process
43
43
  * @returns {string}
44
44
  */
45
45
  process(process) {
46
46
  if (process === null) return outputProcess = '';
47
- if (process) outputProcess = `[${process}]`;
47
+ if (process) outputProcess = String(process).length === 1 ? `[0${process}]` : `[${process}]`;
48
48
  return outputProcess;
49
49
  },
50
50
 
@@ -102,14 +102,14 @@ module.exports = {
102
102
  if (!step) return;
103
103
  // Avoid to print non-gherkin steps, when gherkin is running for --steps mode
104
104
  if (outputLevel === 1) {
105
- if (!step.isMetaStep() && step.hasBDDAncestor()) {
105
+ if (step.hasBDDAncestor()) {
106
106
  return;
107
107
  }
108
108
  }
109
109
 
110
110
  let stepLine = step.toString();
111
111
  if (step.metaStep && outputLevel >= 1) {
112
- this.stepShift += 2;
112
+ // this.stepShift += 2;
113
113
  stepLine = colors.green(truncate(stepLine, this.spaceShift));
114
114
  }
115
115
  if (step.comment) {
@@ -190,6 +190,9 @@ module.exports = {
190
190
  * @param {string} [color]
191
191
  */
192
192
  say(message, color = 'cyan') {
193
+ if (colors[color] === undefined) {
194
+ color = 'cyan';
195
+ }
193
196
  if (outputLevel >= 1) print(` ${colors[color].bold(message)}`);
194
197
  },
195
198
 
@@ -261,13 +261,9 @@ module.exports = (config) => {
261
261
  if (isHookSteps === false) {
262
262
  startMetaStep(step.metaStep);
263
263
  if (currentStep !== step) {
264
- // The reason we need to do this cause
265
- // the step actor contains this and hence
266
- // the allure reporter could not parse and
267
- // generate the report
268
- if (step.actor.includes('\u001b[36m')) {
269
- step.actor = step.actor.replace('\u001b[36m', '').replace('\u001b[39m', '');
270
- }
264
+ // In multi-session scenarios, actors' names will be highlighted with ANSI
265
+ // escape sequences which are invalid XML values
266
+ step.actor = step.actor.replace(ansiRegExp(), '');
271
267
  reporter.startStep(step.toString());
272
268
  currentStep = step;
273
269
  }
@@ -0,0 +1,51 @@
1
+ const faker = require('faker');
2
+ const transform = require('../transform');
3
+
4
+ /**
5
+ * Use the [faker.js](https://www.npmjs.com/package/faker) package to generate fake data inside examples on your gherkin tests
6
+ *
7
+ * ![Faker.js](https://raw.githubusercontent.com/Marak/faker.js/master/logo.png)
8
+ *
9
+ * #### Usage
10
+ *
11
+ * To start please install `faker.js` package
12
+ *
13
+ * ```
14
+ * npm install -D faker
15
+ * ```
16
+ *
17
+ * ```
18
+ * yarn add -D faker
19
+ * ```
20
+ *
21
+ * Add this plugin to config file:
22
+ *
23
+ * ```js
24
+ * plugins: {
25
+ * fakerTransform: {
26
+ * enabled: true
27
+ * }
28
+ * }
29
+ * ```
30
+ *
31
+ * Add the faker API using a mustache string format inside examples tables in your gherkin scenario outline
32
+ *
33
+ * ```feature
34
+ * Scenario Outline: ...
35
+ * Given ...
36
+ * When ...
37
+ * Then ...
38
+ * Examples:
39
+ * | productName | customer | email | anythingMore |
40
+ * | {{commerce.product}} | Dr. {{name.findName}} | {{internet.email}} | staticData |
41
+ * ```
42
+ *
43
+ */
44
+ module.exports = function (config) {
45
+ transform.addTransformerBeforeAll('gherkin.examples', (value) => {
46
+ if (typeof value === 'string' && value.length > 0) {
47
+ return faker.fake(value);
48
+ }
49
+ return value;
50
+ });
51
+ };
@@ -99,8 +99,7 @@ module.exports = function (config) {
99
99
  await helper.saveScreenshot(fileName, options.fullPageScreenshots);
100
100
 
101
101
  test.artifacts.screenshot = path.join(global.output_dir, fileName);
102
-
103
- if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].attachments) {
102
+ if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
104
103
  test.attachments = [path.join(global.output_dir, fileName)];
105
104
  }
106
105
 
@@ -108,6 +107,11 @@ module.exports = function (config) {
108
107
  if (allureReporter) {
109
108
  allureReporter.addAttachment('Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), 'image/png');
110
109
  }
110
+
111
+ const cucumberReporter = Container.plugins('cucumberJsonReporter');
112
+ if (cucumberReporter) {
113
+ cucumberReporter.addScreenshot(test.artifacts.screenshot);
114
+ }
111
115
  } catch (err) {
112
116
  output.plugin(err);
113
117
  if (
package/lib/recorder.js CHANGED
@@ -321,6 +321,15 @@ module.exports = {
321
321
  return tasks.join('\n');
322
322
  },
323
323
 
324
+ /**
325
+ * Get the queue id
326
+ * @return {number}
327
+ * @inner
328
+ */
329
+ getQueueId() {
330
+ return queueId;
331
+ },
332
+
324
333
  /**
325
334
  * Get a state of current queue and tasks
326
335
  * @return {string}
package/lib/step.js CHANGED
@@ -208,9 +208,10 @@ class MetaStep extends Step {
208
208
  let result;
209
209
 
210
210
  const registerStep = (step) => {
211
+ this.metaStep = null;
211
212
  step.metaStep = this;
212
213
  };
213
- event.dispatcher.on(event.step.before, registerStep);
214
+ event.dispatcher.prependListener(event.step.before, registerStep);
214
215
  try {
215
216
  this.startTime = Date.now();
216
217
  result = fn.apply(this.context, this.args);
@@ -0,0 +1,26 @@
1
+ const transformers = {
2
+ 'gherkin.examples': [],
3
+ };
4
+
5
+ function transform(target, value) {
6
+ if (target in transformers) {
7
+ for (const transform of transformers[target]) {
8
+ value = transform(value);
9
+ }
10
+ }
11
+ return value;
12
+ }
13
+
14
+ transform.addTransformer = function (target, transformer) {
15
+ if (target in transformers) {
16
+ transformers[target].push(transformer);
17
+ }
18
+ };
19
+
20
+ transform.addTransformerBeforeAll = function (target, transformer) {
21
+ if (target in transformers) {
22
+ transformers[target].unshift(transformer);
23
+ }
24
+ };
25
+
26
+ module.exports = transform;
package/lib/ui.js CHANGED
@@ -176,8 +176,12 @@ module.exports = function (suite) {
176
176
  * @kind constant
177
177
  * @type {CodeceptJS.IScenario}
178
178
  */
179
- context.xScenario = context.Scenario.skip = function (title) {
180
- return context.Scenario(title, {});
179
+ context.xScenario = context.Scenario.skip = function (title, opts = {}, fn) {
180
+ if (typeof opts === 'function' && !fn) {
181
+ opts = {};
182
+ }
183
+
184
+ return context.Scenario(title, opts);
181
185
  };
182
186
 
183
187
  /**
package/lib/within.js CHANGED
@@ -20,7 +20,7 @@ function within(context, fn) {
20
20
  const defineMetaStep = step => step.metaStep = metaStep;
21
21
  recorder.session.start('within');
22
22
 
23
- event.dispatcher.on(event.step.before, defineMetaStep);
23
+ event.dispatcher.prependListener(event.step.before, defineMetaStep);
24
24
 
25
25
  Object.keys(helpers).forEach((helper) => {
26
26
  if (helpers[helper]._withinBegin) recorder.add(`[${helper}] start within`, () => helpers[helper]._withinBegin(context));
package/lib/workers.js CHANGED
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const mkdirp = require('mkdirp');
5
5
  const { Worker } = require('worker_threads');
6
6
  const { Suite, Test, reporters: { Base } } = require('mocha');
7
+ const ms = require('ms');
7
8
  const Codecept = require('./codecept');
8
9
  const MochaFactory = require('./mochaFactory');
9
10
  const Container = require('./container');
@@ -164,7 +165,7 @@ class Workers extends EventEmitter {
164
165
  super();
165
166
  this.setMaxListeners(50);
166
167
  this.codecept = initializeCodecept(config.testConfig, config.options);
167
- this.finishedTests = {};
168
+ this.failuresLog = [];
168
169
  this.errors = [];
169
170
  this.numberOfWorkers = 0;
170
171
  this.closedWorkers = 0;
@@ -318,29 +319,45 @@ class Workers extends EventEmitter {
318
319
  _listenWorkerEvents(worker) {
319
320
  worker.on('message', (message) => {
320
321
  output.process(message.workerIndex);
322
+
323
+ // deal with events that are not test cycle related
324
+ if (!message.event) {
325
+ return this.emit('message', message);
326
+ }
327
+
321
328
  switch (message.event) {
329
+ case event.all.failures:
330
+ this.failuresLog = this.failuresLog.concat(message.data.failuresLog);
331
+ this._appendStats(message.data.stats);
332
+ break;
333
+ case event.suite.before:
334
+ this.emit(event.suite.before, repackTest(message.data));
335
+ break;
322
336
  case event.hook.failed:
323
337
  this.emit(event.hook.failed, repackTest(message.data));
324
338
  this.errors.push(message.data.err);
325
339
  break;
340
+ case event.test.before:
341
+ this.emit(event.test.before, repackTest(message.data));
342
+ break;
343
+ case event.test.started:
344
+ this.emit(event.test.started, repackTest(message.data));
345
+ break;
326
346
  case event.test.failed:
327
- this._updateFinishedTests(repackTest(message.data));
328
347
  this.emit(event.test.failed, repackTest(message.data));
329
348
  break;
330
349
  case event.test.passed:
331
- this._updateFinishedTests(repackTest(message.data));
332
350
  this.emit(event.test.passed, repackTest(message.data));
333
351
  break;
334
352
  case event.test.skipped:
335
- this._updateFinishedTests(repackTest(message.data));
336
353
  this.emit(event.test.skipped, repackTest(message.data));
337
354
  break;
338
- case event.test.finished: this.emit(event.test.finished, repackTest(message.data)); break;
355
+ case event.test.finished:
356
+ this.emit(event.test.finished, repackTest(message.data));
357
+ break;
339
358
  case event.test.after:
340
359
  this.emit(event.test.after, repackTest(message.data));
341
360
  break;
342
- case event.all.after:
343
- this._appendStats(message.data); break;
344
361
  }
345
362
  });
346
363
 
@@ -363,7 +380,8 @@ class Workers extends EventEmitter {
363
380
  } else {
364
381
  process.exitCode = 0;
365
382
  }
366
- this.emit(event.all.result, !this.isFailed(), this.finishedTests, this.stats);
383
+ // removed this.finishedTests because in all /lib only first argument (!this.isFailed()) is used)
384
+ this.emit(event.all.result, !this.isFailed());
367
385
  this.emit('end'); // internal event
368
386
  }
369
387
 
@@ -374,30 +392,26 @@ class Workers extends EventEmitter {
374
392
  this.stats.pending += newStats.pending;
375
393
  }
376
394
 
377
- _updateFinishedTests(test) {
378
- const { id } = test;
379
- if (this.finishedTests[id]) {
380
- const stats = { passes: 0, failures: -1, tests: 0 };
381
- this._appendStats(stats);
382
- }
383
- this.finishedTests[id] = test;
384
- }
385
-
386
395
  printResults() {
387
396
  this.stats.end = new Date();
388
397
  this.stats.duration = this.stats.end - this.stats.start;
398
+
399
+ // Reset process for logs in main thread
400
+ output.process(null);
389
401
  output.print();
390
- if (this.stats.tests === 0 || (this.stats.passes && !this.errors.length)) {
391
- output.result(this.stats.passes, this.stats.failures, this.stats.pending, `${this.stats.duration || 0 / 1000}s`);
392
- }
393
- if (this.stats.failures) {
402
+
403
+ this.failuresLog = this.failuresLog
404
+ .filter(log => log.length && typeof log[1] === 'number')
405
+ // mocha/lib/reporters/base.js
406
+ .map(([format, num, title, message, stack], i) => [format, i + 1, title, message, stack]);
407
+
408
+ if (this.failuresLog.length) {
394
409
  output.print();
395
410
  output.print('-- FAILURES:');
396
- const failedList = Object.keys(this.finishedTests)
397
- .filter(key => this.finishedTests[key].err)
398
- .map(key => this.finishedTests[key]);
399
- Base.list(failedList);
411
+ this.failuresLog.forEach(log => output.print(...log));
400
412
  }
413
+
414
+ output.result(this.stats.passes, this.stats.failures, this.stats.pending, ms(this.stats.duration));
401
415
  }
402
416
  }
403
417