codeceptjs 3.5.9 → 3.5.11

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 (52) hide show
  1. package/README.md +14 -16
  2. package/docs/build/Appium.js +49 -49
  3. package/docs/build/Expect.js +33 -33
  4. package/docs/build/Nightmare.js +50 -50
  5. package/docs/build/Playwright.js +239 -133
  6. package/docs/build/Protractor.js +59 -59
  7. package/docs/build/Puppeteer.js +127 -107
  8. package/docs/build/TestCafe.js +48 -48
  9. package/docs/build/WebDriver.js +112 -93
  10. package/docs/helpers/Appium.md +3 -3
  11. package/docs/helpers/Expect.md +33 -33
  12. package/docs/helpers/Playwright.md +431 -325
  13. package/docs/helpers/Puppeteer.md +50 -24
  14. package/docs/helpers/WebDriver.md +41 -13
  15. package/docs/internal-api.md +1 -0
  16. package/docs/parallel.md +114 -2
  17. package/docs/plugins.md +7 -5
  18. package/docs/react.md +2 -1
  19. package/docs/vue.md +22 -0
  20. package/docs/webapi/grabWebElement.mustache +9 -0
  21. package/docs/webapi/grabWebElements.mustache +9 -0
  22. package/docs/webapi/scrollIntoView.mustache +1 -1
  23. package/lib/ai.js +12 -3
  24. package/lib/colorUtils.js +10 -0
  25. package/lib/command/run-multiple.js +1 -1
  26. package/lib/command/run-workers.js +30 -4
  27. package/lib/command/workers/runTests.js +39 -0
  28. package/lib/event.js +2 -0
  29. package/lib/helper/Appium.js +13 -13
  30. package/lib/helper/Expect.js +33 -33
  31. package/lib/helper/Playwright.js +125 -37
  32. package/lib/helper/Puppeteer.js +49 -38
  33. package/lib/helper/WebDriver.js +29 -19
  34. package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
  35. package/lib/html.js +3 -3
  36. package/lib/interfaces/gherkin.js +8 -1
  37. package/lib/interfaces/scenarioConfig.js +1 -0
  38. package/lib/locator.js +2 -2
  39. package/lib/pause.js +6 -3
  40. package/lib/plugin/autoLogin.js +4 -2
  41. package/lib/plugin/heal.js +40 -7
  42. package/lib/plugin/retryFailedStep.js +6 -1
  43. package/lib/plugin/stepByStepReport.js +2 -2
  44. package/lib/plugin/tryTo.js +5 -4
  45. package/lib/recorder.js +12 -5
  46. package/lib/ui.js +1 -0
  47. package/lib/workers.js +2 -0
  48. package/package.json +28 -25
  49. package/typings/index.d.ts +1 -1
  50. package/typings/promiseBasedTypes.d.ts +195 -76
  51. package/typings/types.d.ts +191 -145
  52. package/lib/helper/extras/PlaywrightReact.js +0 -9
@@ -869,6 +869,14 @@ class WebDriver extends Helper {
869
869
  return findFields.call(this, locator).then(res => res);
870
870
  }
871
871
 
872
+ /**
873
+ * {{> grabWebElements }}
874
+ *
875
+ */
876
+ async grabWebElements(locator) {
877
+ return this._locate(locator);
878
+ }
879
+
872
880
  /**
873
881
  * Set [WebDriver timeouts](https://webdriver.io/docs/timeouts.html) in realtime.
874
882
  *
@@ -1096,8 +1104,9 @@ class WebDriver extends Helper {
1096
1104
  }
1097
1105
 
1098
1106
  /**
1099
- * {{> attachFile }}
1100
1107
  * Appium: not tested
1108
+ *
1109
+ * {{> attachFile }}
1101
1110
  */
1102
1111
  async attachFile(locator, pathToFile) {
1103
1112
  let file = path.join(global.codecept_dir, pathToFile);
@@ -1124,8 +1133,8 @@ class WebDriver extends Helper {
1124
1133
  }
1125
1134
 
1126
1135
  /**
1127
- * {{> checkOption }}
1128
1136
  * Appium: not tested
1137
+ * {{> checkOption }}
1129
1138
  */
1130
1139
  async checkOption(field, context = null) {
1131
1140
  const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
@@ -1144,8 +1153,8 @@ class WebDriver extends Helper {
1144
1153
  }
1145
1154
 
1146
1155
  /**
1147
- * {{> uncheckOption }}
1148
1156
  * Appium: not tested
1157
+ * {{> uncheckOption }}
1149
1158
  */
1150
1159
  async uncheckOption(field, context = null) {
1151
1160
  const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
@@ -1362,16 +1371,16 @@ class WebDriver extends Helper {
1362
1371
  }
1363
1372
 
1364
1373
  /**
1365
- * {{> seeCheckboxIsChecked }}
1366
1374
  * Appium: not tested
1375
+ * {{> seeCheckboxIsChecked }}
1367
1376
  */
1368
1377
  async seeCheckboxIsChecked(field) {
1369
1378
  return proceedSeeCheckbox.call(this, 'assert', field);
1370
1379
  }
1371
1380
 
1372
1381
  /**
1373
- * {{> dontSeeCheckboxIsChecked }}
1374
1382
  * Appium: not tested
1383
+ * {{> dontSeeCheckboxIsChecked }}
1375
1384
  */
1376
1385
  async dontSeeCheckboxIsChecked(field) {
1377
1386
  return proceedSeeCheckbox.call(this, 'negate', field);
@@ -1508,7 +1517,9 @@ class WebDriver extends Helper {
1508
1517
  let chunked = chunkArray(props, values.length);
1509
1518
  chunked = chunked.filter((val) => {
1510
1519
  for (let i = 0; i < val.length; ++i) {
1511
- if (val[i] !== values[i]) return false;
1520
+ const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1521
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1522
+ if (_acutal !== _expected) return false;
1512
1523
  }
1513
1524
  return true;
1514
1525
  });
@@ -1535,7 +1546,9 @@ class WebDriver extends Helper {
1535
1546
  let chunked = chunkArray(attrs, values.length);
1536
1547
  chunked = chunked.filter((val) => {
1537
1548
  for (let i = 0; i < val.length; ++i) {
1538
- if (val[i] !== values[i]) return false;
1549
+ const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1550
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1551
+ if (_acutal !== _expected) return false;
1539
1552
  }
1540
1553
  return true;
1541
1554
  });
@@ -1594,10 +1607,9 @@ class WebDriver extends Helper {
1594
1607
  }
1595
1608
 
1596
1609
  /**
1597
- * {{> executeScript }}
1598
- *
1599
- *
1600
1610
  * Wraps [execute](http://webdriver.io/api/protocol/execute.html) command.
1611
+ *
1612
+ * {{> executeScript }}
1601
1613
  */
1602
1614
  executeScript(...args) {
1603
1615
  return this.browser.execute.apply(this.browser, args);
@@ -1719,11 +1731,8 @@ class WebDriver extends Helper {
1719
1731
  }
1720
1732
 
1721
1733
  /**
1734
+ * Uses Selenium's JSON [cookie format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
1722
1735
  * {{> setCookie }}
1723
- *
1724
- *
1725
- * Uses Selenium's JSON [cookie
1726
- * format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
1727
1736
  */
1728
1737
  async setCookie(cookie) {
1729
1738
  return this.browser.setCookies(cookie);
@@ -1776,7 +1785,7 @@ class WebDriver extends Helper {
1776
1785
  }
1777
1786
 
1778
1787
  /**
1779
- * Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt.
1788
+ * Dismisses the active JavaScript popup, as created by `window.alert|window.confirm|window.prompt`.
1780
1789
  *
1781
1790
  */
1782
1791
  async cancelPopup() {
@@ -1850,9 +1859,9 @@ class WebDriver extends Helper {
1850
1859
  }
1851
1860
 
1852
1861
  /**
1853
- * {{> pressKeyWithKeyNormalization }}
1854
- *
1855
1862
  * _Note:_ In case a text field or textarea is focused be aware that some browsers do not respect active modifier when combining modifier keys with other keys.
1863
+ *
1864
+ * {{> pressKeyWithKeyNormalization }}
1856
1865
  */
1857
1866
  async pressKey(key) {
1858
1867
  const modifiers = [];
@@ -1911,8 +1920,9 @@ class WebDriver extends Helper {
1911
1920
  }
1912
1921
 
1913
1922
  /**
1914
- * {{> resizeWindow }}
1915
1923
  * Appium: not tested in web, in apps doesn't work
1924
+ *
1925
+ * {{> resizeWindow }}
1916
1926
  */
1917
1927
  async resizeWindow(width, height) {
1918
1928
  return this._resizeBrowserWindow(this.browser, width, height);
@@ -1964,8 +1974,8 @@ class WebDriver extends Helper {
1964
1974
  }
1965
1975
 
1966
1976
  /**
1967
- * {{> dragAndDrop }}
1968
1977
  * Appium: not tested
1978
+ * {{> dragAndDrop }}
1969
1979
  */
1970
1980
  async dragAndDrop(srcElement, destElement) {
1971
1981
  let sourceEl = await this._locate(srcElement);
@@ -0,0 +1,38 @@
1
+ async function findReact(matcher, locator) {
2
+ let _locator = `_react=${locator.react}`;
3
+ let props = '';
4
+
5
+ if (locator.props) {
6
+ props += propBuilder(locator.props);
7
+ _locator += props;
8
+ }
9
+ return matcher.locator(_locator).all();
10
+ }
11
+
12
+ async function findVue(matcher, locator) {
13
+ let _locator = `_vue=${locator.vue}`;
14
+ let props = '';
15
+
16
+ if (locator.props) {
17
+ props += propBuilder(locator.props);
18
+ _locator += props;
19
+ }
20
+ return matcher.locator(_locator).all();
21
+ }
22
+
23
+ function propBuilder(props) {
24
+ let _props = '';
25
+
26
+ for (const [key, value] of Object.entries(props)) {
27
+ if (typeof value === 'object') {
28
+ for (const [k, v] of Object.entries(value)) {
29
+ _props += `[${key}.${k} = "${v}"]`;
30
+ }
31
+ } else {
32
+ _props += `[${key} = "${value}"]`;
33
+ }
34
+ }
35
+ return _props;
36
+ }
37
+
38
+ module.exports = { findReact, findVue };
package/lib/html.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { parse, serialize } = require('parse5');
2
- const { minify } = require('html-minifier');
2
+ const { minify } = require('html-minifier-terser');
3
3
 
4
- function minifyHtml(html) {
4
+ async function minifyHtml(html) {
5
5
  return minify(html, {
6
6
  collapseWhitespace: true,
7
7
  removeComments: true,
@@ -11,7 +11,7 @@ function minifyHtml(html) {
11
11
  removeStyleLinkTypeAttributes: true,
12
12
  collapseBooleanAttributes: true,
13
13
  useShortDoctype: true,
14
- }).toString();
14
+ });
15
15
  }
16
16
 
17
17
  const defaultHtmlOpts = {
@@ -119,7 +119,14 @@ module.exports = (text, file) => {
119
119
  });
120
120
  }
121
121
  const tags = child.scenario.tags.map(t => t.name).concat(examples.tags.map(t => t.name));
122
- const title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
122
+ let title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
123
+
124
+ for (const [key, value] of Object.entries(current)) {
125
+ if (title.includes(`<${key}>`)) {
126
+ title = title.replace(JSON.stringify(current), '').replace(`<${key}>`, value);
127
+ }
128
+ }
129
+
123
130
  const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)));
124
131
  test.tags = suite.tags.concat(tags);
125
132
  test.file = file;
@@ -35,6 +35,7 @@ class ScenarioConfig {
35
35
  * @returns {this}
36
36
  */
37
37
  retry(retries) {
38
+ if (process.env.SCENARIO_ONLY) retries = -retries;
38
39
  this.test.retries(retries);
39
40
  return this;
40
41
  }
package/lib/locator.js CHANGED
@@ -1,4 +1,4 @@
1
- const cssToXPath = require('css-to-xpath');
1
+ const cssToXPath = require('convert-cssxpath');
2
2
  const { sprintf } = require('sprintf-js');
3
3
 
4
4
  const { xpathLocator } = require('./utils');
@@ -162,7 +162,7 @@ class Locator {
162
162
  */
163
163
  toXPath() {
164
164
  if (this.isXPath()) return this.value;
165
- if (this.isCSS()) return cssToXPath(this.value);
165
+ if (this.isCSS()) return cssToXPath.convert(this.value);
166
166
 
167
167
  throw new Error('Can\'t be converted to XPath');
168
168
  }
package/lib/pause.js CHANGED
@@ -18,8 +18,7 @@ let nextStep;
18
18
  let finish;
19
19
  let next;
20
20
  let registeredVariables = {};
21
- const aiAssistant = new AiAssistant();
22
-
21
+ let aiAssistant;
23
22
  /**
24
23
  * Pauses test execution and starts interactive shell
25
24
  * @param {Object<string, *>} [passedObject]
@@ -45,6 +44,8 @@ function pauseSession(passedObject = {}) {
45
44
  let vars = Object.keys(registeredVariables).join(', ');
46
45
  if (vars) vars = `(vars: ${vars})`;
47
46
 
47
+ aiAssistant = AiAssistant.getInstance();
48
+
48
49
  output.print(colors.yellow(' Interactive shell started'));
49
50
  output.print(colors.yellow(' Use JavaScript syntax to try steps in action'));
50
51
  output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`));
@@ -102,7 +103,9 @@ async function parseInput(cmd) {
102
103
  let isAiCommand = false;
103
104
  let $res;
104
105
  try {
106
+ // eslint-disable-next-line
105
107
  const locate = global.locate; // enable locate in this context
108
+ // eslint-disable-next-line
106
109
  const I = container.support('I');
107
110
  if (cmd.trim().startsWith('=>')) {
108
111
  isCustomCommand = true;
@@ -115,7 +118,7 @@ async function parseInput(cmd) {
115
118
  executeCommand = executeCommand.then(async () => {
116
119
  try {
117
120
  const html = await res;
118
- aiAssistant.setHtmlContext(html);
121
+ await aiAssistant.setHtmlContext(html);
119
122
  } catch (err) {
120
123
  output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
121
124
  return;
@@ -38,12 +38,14 @@ const defaultConfig = {
38
38
  * ```js
39
39
  * // inside a test file
40
40
  * // use login to inject auto-login function
41
+ * Feature('Login');
42
+ *
41
43
  * Before(({ login }) => {
42
44
  * login('user'); // login using user session
43
45
  * });
44
46
  *
45
- * // Alternatively log in for one scenario
46
- * Scenario('log me in', ( {I, login} ) => {
47
+ * // Alternatively log in for one scenario.
48
+ * Scenario('log me in', ( { I, login } ) => {
47
49
  * login('admin');
48
50
  * I.see('I am logged in');
49
51
  * });
@@ -8,6 +8,7 @@ const output = require('../output');
8
8
  const supportedHelpers = require('./standardActingHelpers');
9
9
 
10
10
  const defaultConfig = {
11
+ healTries: 1,
11
12
  healLimit: 2,
12
13
  healSteps: [
13
14
  'click',
@@ -54,11 +55,14 @@ const defaultConfig = {
54
55
  *
55
56
  */
56
57
  module.exports = function (config = {}) {
57
- const aiAssistant = new AiAssistant();
58
+ const aiAssistant = AiAssistant.getInstance();
58
59
 
59
60
  let currentTest = null;
60
61
  let currentStep = null;
61
62
  let healedSteps = 0;
63
+ let caughtError;
64
+ let healTries = 0;
65
+ let isHealing = false;
62
66
 
63
67
  const healSuggestions = [];
64
68
 
@@ -67,20 +71,35 @@ module.exports = function (config = {}) {
67
71
  event.dispatcher.on(event.test.before, (test) => {
68
72
  currentTest = test;
69
73
  healedSteps = 0;
74
+ caughtError = null;
70
75
  });
71
76
 
72
77
  event.dispatcher.on(event.step.started, step => currentStep = step);
73
78
 
74
- event.dispatcher.on(event.step.before, () => {
79
+ event.dispatcher.on(event.step.after, (step) => {
80
+ if (isHealing) return;
75
81
  const store = require('../store');
76
82
  if (store.debugMode) return;
77
-
78
83
  recorder.catchWithoutStop(async (err) => {
79
- if (!aiAssistant.isEnabled) throw err;
84
+ isHealing = true;
85
+ if (caughtError === err) throw err; // avoid double handling
86
+ caughtError = err;
87
+ if (!aiAssistant.isEnabled) {
88
+ output.print(colors.yellow('Heal plugin can\'t operate, AI assistant is disabled. Please set OPENAI_API_KEY env variable to enable it.'));
89
+ throw err;
90
+ }
80
91
  if (!currentStep) throw err;
81
92
  if (!config.healSteps.includes(currentStep.name)) throw err;
82
93
  const test = currentTest;
83
94
 
95
+ if (healTries >= config.healTries) {
96
+ output.print(colors.bold.red(`Healing failed for ${config.healTries} time(s)`));
97
+ output.print('AI couldn\'t identify the correct solution');
98
+ output.print('Probably the entire flow has changed and the test should be updated');
99
+
100
+ throw err;
101
+ }
102
+
84
103
  if (healedSteps >= config.healLimit) {
85
104
  output.print(colors.bold.red(`Can't heal more than ${config.healLimit} step(s) in a test`));
86
105
  output.print('Entire flow can be broken, please check it manually');
@@ -111,9 +130,17 @@ module.exports = function (config = {}) {
111
130
 
112
131
  if (!html) throw err;
113
132
 
114
- aiAssistant.setHtmlContext(html);
133
+ healTries++;
134
+ await aiAssistant.setHtmlContext(html);
115
135
  await tryToHeal(step, err);
116
- recorder.session.restore();
136
+
137
+ recorder.add('close healing session', () => {
138
+ recorder.session.restore('heal');
139
+ recorder.ignoreErr(err);
140
+ });
141
+ await recorder.promise();
142
+
143
+ isHealing = false;
117
144
  });
118
145
  });
119
146
 
@@ -155,6 +182,9 @@ module.exports = function (config = {}) {
155
182
  for (const codeSnippet of codeSnippets) {
156
183
  try {
157
184
  debug('Executing', codeSnippet);
185
+ recorder.catch((e) => {
186
+ console.log(e);
187
+ });
158
188
  await eval(codeSnippet); // eslint-disable-line
159
189
 
160
190
  healSuggestions.push({
@@ -163,14 +193,17 @@ module.exports = function (config = {}) {
163
193
  snippet: codeSnippet,
164
194
  });
165
195
 
166
- output.print(colors.bold.green(' Code healed successfully'));
196
+ recorder.add('healed', () => output.print(colors.bold.green(' Code healed successfully')));
167
197
  healedSteps++;
168
198
  return;
169
199
  } catch (err) {
170
200
  debug('Failed to execute code', err);
201
+ recorder.ignoreErr(err); // healing ded not help
202
+ // recorder.catch(() => output.print(colors.bold.red(' Failed healing code')));
171
203
  }
172
204
  }
173
205
 
174
206
  output.debug(`Couldn't heal the code for ${failedStep.toCode()}`);
175
207
  }
208
+ return recorder.promise();
176
209
  };
@@ -1,5 +1,6 @@
1
1
  const event = require('../event');
2
2
  const recorder = require('../recorder');
3
+ const container = require('../container');
3
4
 
4
5
  const defaultConfig = {
5
6
  retries: 3,
@@ -42,7 +43,7 @@ const defaultConfig = {
42
43
  * * `factor` - The exponential factor to use. Default is 1.5.
43
44
  * * `minTimeout` - The number of milliseconds before starting the first retry. Default is 1000.
44
45
  * * `maxTimeout` - The maximum number of milliseconds between two retries. Default is Infinity.
45
- * * `randomize` - Randomizes the timeouts by multiplying with a factor between 1 to 2. Default is false.
46
+ * * `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.
46
47
  * * `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:
47
48
  * * `amOnPage`
48
49
  * * `wait*`
@@ -98,6 +99,8 @@ module.exports = (config) => {
98
99
  config.when = when;
99
100
 
100
101
  event.dispatcher.on(event.step.started, (step) => {
102
+ if (process.env.TRY_TO) return;
103
+
101
104
  // if a step is ignored - return
102
105
  for (const ignored of config.ignoredSteps) {
103
106
  if (step.name === ignored) return;
@@ -114,6 +117,8 @@ module.exports = (config) => {
114
117
 
115
118
  event.dispatcher.on(event.test.before, (test) => {
116
119
  if (test && test.disableRetryFailedStep) return; // disable retry when a test is not active
120
+ // this env var is used to set the retries inside _before() block of helpers
121
+ process.env.FAILED_STEP_RETRIES = config.retries;
117
122
  recorder.retry(config);
118
123
  });
119
124
  };
@@ -89,8 +89,8 @@ module.exports = function (config) {
89
89
  const reportDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output;
90
90
 
91
91
  event.dispatcher.on(event.test.before, (test) => {
92
- const md5hash = crypto.createHash('md5').update(test.file + test.title).digest('hex');
93
- dir = path.join(reportDir, `record_${md5hash}`);
92
+ const sha256hash = crypto.createHash('sha256').update(test.file + test.title).digest('hex');
93
+ dir = path.join(reportDir, `record_${sha256hash}`);
94
94
  mkdirp.sync(dir);
95
95
  stepNum = 0;
96
96
  error = null;
@@ -1,5 +1,4 @@
1
1
  const recorder = require('../recorder');
2
- const store = require('../store');
3
2
  const { debug } = require('../output');
4
3
 
5
4
  const defaultConfig = {
@@ -9,7 +8,7 @@ const defaultConfig = {
9
8
  /**
10
9
  *
11
10
  *
12
- * Adds global `tryTo` function inside of which all failed steps won't fail a test but will return true/false.
11
+ * Adds global `tryTo` function in which all failed steps won't fail a test but will return true/false.
13
12
  *
14
13
  * Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
15
14
  *
@@ -47,7 +46,7 @@ const defaultConfig = {
47
46
  * ```js
48
47
  * const assert = require('assert');
49
48
  * ```
50
- * Then use the assert:
49
+ * Then use the assertion:
51
50
  * const result1 = await tryTo(() => I.see('Hello, user'));
52
51
  * const result2 = await tryTo(() => I.seeElement('.welcome'));
53
52
  * assert.ok(result1 && result2, 'Assertions were not succesful');
@@ -70,7 +69,7 @@ const defaultConfig = {
70
69
  * const tryTo = codeceptjs.container.plugins('tryTo');
71
70
  * ```
72
71
  *
73
- */
72
+ */
74
73
  module.exports = function (config) {
75
74
  config = Object.assign(defaultConfig, config);
76
75
 
@@ -84,6 +83,7 @@ function tryTo(callback) {
84
83
  let result = false;
85
84
  return recorder.add('tryTo', () => {
86
85
  recorder.session.start('tryTo');
86
+ process.env.TRY_TO = 'true';
87
87
  callback();
88
88
  recorder.add(() => {
89
89
  result = true;
@@ -98,6 +98,7 @@ function tryTo(callback) {
98
98
  return result;
99
99
  });
100
100
  return recorder.add('result', () => {
101
+ process.env.TRY_TO = undefined;
101
102
  return result;
102
103
  }, true, false);
103
104
  }, false, false);
package/lib/recorder.js CHANGED
@@ -11,6 +11,7 @@ let errFn;
11
11
  let queueId = 0;
12
12
  let sessionId = null;
13
13
  let asyncErr = null;
14
+ let ignoredErrs = [];
14
15
 
15
16
  let tasks = [];
16
17
  let oldPromises = [];
@@ -93,6 +94,7 @@ module.exports = {
93
94
  promise = Promise.resolve();
94
95
  oldPromises = [];
95
96
  tasks = [];
97
+ ignoredErrs = [];
96
98
  this.session.running = false;
97
99
  // reset this retries makes the retryFailedStep plugin won't work if there is Before/BeforeSuit block due to retries is undefined on Scenario
98
100
  // this.retries = [];
@@ -226,9 +228,10 @@ module.exports = {
226
228
  * @inner
227
229
  */
228
230
  catch(customErrFn) {
229
- debug(`${currentQueue()}Queued | catch with error handler`);
231
+ const fnDescription = customErrFn?.toString()?.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ')?.slice(0, 50);
232
+ debug(`${currentQueue()}Queued | catch with error handler ${fnDescription || ''}`);
230
233
  return promise = promise.catch((err) => {
231
- log(`${currentQueue()}Error | ${err}`);
234
+ log(`${currentQueue()}Error | ${err} ${fnDescription}...`);
232
235
  if (!(err instanceof Error)) { // strange things may happen
233
236
  err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`); // we should be prepared for them
234
237
  }
@@ -247,15 +250,15 @@ module.exports = {
247
250
  * @inner
248
251
  */
249
252
  catchWithoutStop(customErrFn) {
253
+ const fnDescription = customErrFn?.toString()?.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ')?.slice(0, 50);
250
254
  return promise = promise.catch((err) => {
251
- log(`${currentQueue()}Error | ${err}`);
255
+ if (ignoredErrs.includes(err)) return; // already caught
256
+ log(`${currentQueue()}Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`);
252
257
  if (!(err instanceof Error)) { // strange things may happen
253
258
  err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`); // we should be prepared for them
254
259
  }
255
260
  if (customErrFn) {
256
261
  return customErrFn(err);
257
- } if (errFn) {
258
- return errFn(err);
259
262
  }
260
263
  });
261
264
  },
@@ -274,6 +277,10 @@ module.exports = {
274
277
  });
275
278
  },
276
279
 
280
+ ignoreErr(err) {
281
+ ignoredErrs.push(err);
282
+ },
283
+
277
284
  /**
278
285
  * @param {*} err
279
286
  * @inner
package/lib/ui.js CHANGED
@@ -168,6 +168,7 @@ module.exports = function (suite) {
168
168
  context.Scenario.only = function (title, opts, fn) {
169
169
  const reString = `^${escapeRe(`${suites[0].title}: ${title}`.replace(/( \| {.+})?$/g, ''))}`;
170
170
  mocha.grep(new RegExp(reString));
171
+ process.env.SCENARIO_ONLY = true;
171
172
  return addScenario(title, opts, fn);
172
173
  };
173
174
 
package/lib/workers.js CHANGED
@@ -359,6 +359,7 @@ class Workers extends EventEmitter {
359
359
  this.stats.start = new Date();
360
360
  recorder.startUnlessRunning();
361
361
  event.dispatcher.emit(event.workers.before);
362
+ process.env.RUNS_WITH_WORKERS = 'true';
362
363
  recorder.add('starting workers', () => {
363
364
  for (const worker of this.workers) {
364
365
  const workerThread = createWorker(worker);
@@ -492,6 +493,7 @@ class Workers extends EventEmitter {
492
493
  }
493
494
 
494
495
  output.result(this.stats.passes, this.stats.failures, this.stats.pending, ms(this.stats.duration));
496
+ process.env.RUNS_WITH_WORKERS = 'false';
495
497
  }
496
498
  }
497
499