codeceptjs 3.5.3 → 3.5.4

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 (62) hide show
  1. package/README.md +0 -2
  2. package/docs/build/Appium.js +8 -6
  3. package/docs/build/GraphQL.js +25 -0
  4. package/docs/build/Nightmare.js +11 -6
  5. package/docs/build/Playwright.js +425 -193
  6. package/docs/build/Protractor.js +13 -8
  7. package/docs/build/Puppeteer.js +20 -14
  8. package/docs/build/TestCafe.js +17 -10
  9. package/docs/build/WebDriver.js +41 -37
  10. package/docs/changelog.md +170 -1
  11. package/docs/community-helpers.md +8 -4
  12. package/docs/examples.md +8 -2
  13. package/docs/helpers/Appium.md +2 -2
  14. package/docs/helpers/GraphQL.md +21 -0
  15. package/docs/helpers/Nightmare.md +2 -2
  16. package/docs/helpers/Playwright.md +239 -122
  17. package/docs/helpers/Protractor.md +2 -2
  18. package/docs/helpers/Puppeteer.md +3 -3
  19. package/docs/helpers/TestCafe.md +2 -2
  20. package/docs/helpers/WebDriver.md +3 -3
  21. package/docs/playwright.md +24 -1
  22. package/docs/webapi/dontSeeInField.mustache +1 -1
  23. package/docs/webapi/seeInField.mustache +1 -1
  24. package/docs/wiki/Books-&-Posts.md +0 -0
  25. package/docs/wiki/Community-Helpers-&-Plugins.md +8 -4
  26. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +46 -14
  27. package/docs/wiki/Examples.md +8 -2
  28. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
  29. package/docs/wiki/Home.md +0 -0
  30. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
  31. package/docs/wiki/Release-Process.md +0 -0
  32. package/docs/wiki/Roadmap.md +0 -0
  33. package/docs/wiki/Tests.md +0 -0
  34. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
  35. package/docs/wiki/Videos.md +0 -0
  36. package/lib/command/definitions.js +2 -7
  37. package/lib/command/run-multiple/collection.js +17 -5
  38. package/lib/helper/Appium.js +6 -4
  39. package/lib/helper/GraphQL.js +25 -0
  40. package/lib/helper/Nightmare.js +9 -4
  41. package/lib/helper/Playwright.js +422 -190
  42. package/lib/helper/Protractor.js +11 -6
  43. package/lib/helper/Puppeteer.js +18 -12
  44. package/lib/helper/TestCafe.js +15 -8
  45. package/lib/helper/WebDriver.js +39 -35
  46. package/lib/helper/errors/ElementNotFound.js +2 -1
  47. package/lib/helper/extras/PlaywrightReact.js +9 -0
  48. package/lib/helper/scripts/highlightElement.js +1 -1
  49. package/lib/interfaces/bdd.js +1 -1
  50. package/lib/mochaFactory.js +2 -1
  51. package/lib/pause.js +5 -4
  52. package/lib/plugin/heal.js +2 -3
  53. package/lib/plugin/selenoid.js +6 -1
  54. package/lib/step.js +27 -10
  55. package/lib/utils.js +4 -0
  56. package/lib/workers.js +3 -1
  57. package/package.json +14 -14
  58. package/typings/promiseBasedTypes.d.ts +145 -126
  59. package/typings/types.d.ts +152 -133
  60. package/CHANGELOG.md +0 -2563
  61. package/docs/build/Polly.js +0 -42
  62. package/docs/build/SeleniumWebdriver.js +0 -76
@@ -631,14 +631,16 @@ class Protractor extends Helper {
631
631
  * {{> seeInField }}
632
632
  */
633
633
  async seeInField(field, value) {
634
- return proceedSeeInField.call(this, 'assert', field, value);
634
+ const _value = (typeof value === 'boolean') ? value : value.toString();
635
+ return proceedSeeInField.call(this, 'assert', field, _value);
635
636
  }
636
637
 
637
638
  /**
638
639
  * {{> dontSeeInField }}
639
640
  */
640
641
  async dontSeeInField(field, value) {
641
- return proceedSeeInField.call(this, 'negate', field, value);
642
+ const _value = (typeof value === 'boolean') ? value : value.toString();
643
+ return proceedSeeInField.call(this, 'negate', field, _value);
642
644
  }
643
645
 
644
646
  /**
@@ -1053,7 +1055,7 @@ class Protractor extends Helper {
1053
1055
  const stream = fs.createWriteStream(outputFile);
1054
1056
  stream.write(Buffer.from(png, 'base64'));
1055
1057
  stream.end();
1056
- return new Promise(resolve => stream.on('finish', resolve));
1058
+ return new Promise(resolve => stream.on('finish', resolve)); // eslint-disable-line no-promise-executor-return
1057
1059
  };
1058
1060
 
1059
1061
  const res = await this._locate(locator);
@@ -1076,7 +1078,7 @@ class Protractor extends Helper {
1076
1078
  const stream = fs.createWriteStream(outputFile);
1077
1079
  stream.write(Buffer.from(png, 'base64'));
1078
1080
  stream.end();
1079
- return new Promise(resolve => stream.on('finish', resolve));
1081
+ return new Promise(resolve => stream.on('finish', resolve)); // eslint-disable-line no-promise-executor-return
1080
1082
  };
1081
1083
 
1082
1084
  if (!fullPage) {
@@ -1613,8 +1615,11 @@ class Protractor extends Helper {
1613
1615
  const body = document.body;
1614
1616
  const html = document.documentElement;
1615
1617
  window.scrollTo(0, Math.max(
1616
- body.scrollHeight, body.offsetHeight,
1617
- html.clientHeight, html.scrollHeight, html.offsetHeight
1618
+ body.scrollHeight,
1619
+ body.offsetHeight,
1620
+ html.clientHeight,
1621
+ html.scrollHeight,
1622
+ html.offsetHeight
1618
1623
  ));
1619
1624
  });
1620
1625
  /* eslint-enable */
@@ -23,7 +23,7 @@ const {
23
23
  screenshotOutputFolder,
24
24
  getNormalizedKeyAttributeValue,
25
25
  isModifierKey,
26
- requireWithFallback,
26
+ requireWithFallback, normalizeSpacesInString,
27
27
  } = require('../utils');
28
28
  const {
29
29
  isColorProperty,
@@ -69,7 +69,7 @@ const consoleLogStore = new Console();
69
69
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
70
70
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
71
71
  * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
72
- * @prop {boolean} [highlightElement] - highlight the interacting elements
72
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
73
73
  */
74
74
  const config = {};
75
75
 
@@ -231,6 +231,7 @@ class Puppeteer extends Helper {
231
231
  keepBrowserState: false,
232
232
  show: false,
233
233
  defaultPopupAction: 'accept',
234
+ highlightElement: false,
234
235
  };
235
236
 
236
237
  return Object.assign(defaults, config);
@@ -765,8 +766,11 @@ class Puppeteer extends Helper {
765
766
  const body = document.body;
766
767
  const html = document.documentElement;
767
768
  window.scrollTo(0, Math.max(
768
- body.scrollHeight, body.offsetHeight,
769
- html.clientHeight, html.scrollHeight, html.offsetHeight,
769
+ body.scrollHeight,
770
+ body.offsetHeight,
771
+ html.clientHeight,
772
+ html.scrollHeight,
773
+ html.offsetHeight,
770
774
  ));
771
775
  });
772
776
  }
@@ -1318,7 +1322,7 @@ class Puppeteer extends Helper {
1318
1322
  await this._evaluateHandeInContext(el => el.innerHTML = '', el);
1319
1323
  }
1320
1324
 
1321
- highlightActiveElement.call(this, el, this.page);
1325
+ highlightActiveElement.call(this, el, await this._getContext());
1322
1326
  await el.type(value.toString(), { delay: this.options.pressKeyDelay });
1323
1327
 
1324
1328
  return this._waitForAction();
@@ -1339,7 +1343,7 @@ class Puppeteer extends Helper {
1339
1343
  async appendField(field, value) {
1340
1344
  const els = await findVisibleFields.call(this, field);
1341
1345
  assertElementExists(els, field, 'Field');
1342
- highlightActiveElement.call(this, els[0], this.page);
1346
+ highlightActiveElement.call(this, els[0], await this._getContext());
1343
1347
  await els[0].press('End');
1344
1348
  await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
1345
1349
  return this._waitForAction();
@@ -1349,14 +1353,16 @@ class Puppeteer extends Helper {
1349
1353
  * {{> seeInField }}
1350
1354
  */
1351
1355
  async seeInField(field, value) {
1352
- return proceedSeeInField.call(this, 'assert', field, value);
1356
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1357
+ return proceedSeeInField.call(this, 'assert', field, _value);
1353
1358
  }
1354
1359
 
1355
1360
  /**
1356
1361
  * {{> dontSeeInField }}
1357
1362
  */
1358
1363
  async dontSeeInField(field, value) {
1359
- return proceedSeeInField.call(this, 'negate', field, value);
1364
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1365
+ return proceedSeeInField.call(this, 'negate', field, _value);
1360
1366
  }
1361
1367
 
1362
1368
  /**
@@ -1386,7 +1392,7 @@ class Puppeteer extends Helper {
1386
1392
  if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
1387
1393
  throw new Error('Element is not <select>');
1388
1394
  }
1389
- highlightActiveElement.call(this, els[0], this.page);
1395
+ highlightActiveElement.call(this, els[0], await this._getContext());
1390
1396
  if (!Array.isArray(option)) option = [option];
1391
1397
 
1392
1398
  for (const key in option) {
@@ -2359,7 +2365,7 @@ async function proceedClick(locator, context = null, options = {}) {
2359
2365
  assertElementExists(els, locator, 'Clickable element');
2360
2366
  }
2361
2367
 
2362
- highlightActiveElement.call(this, els[0], this.page);
2368
+ highlightActiveElement.call(this, els[0], await this._getContext());
2363
2369
 
2364
2370
  await els[0].click(options);
2365
2371
  const promises = [];
@@ -2419,7 +2425,7 @@ async function proceedSee(assertType, text, context, strict = false) {
2419
2425
  if (strict) {
2420
2426
  return allText.map(elText => equals(description)[assertType](text, elText));
2421
2427
  }
2422
- return stringIncludes(description)[assertType](text, allText.join(' | '));
2428
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
2423
2429
  }
2424
2430
 
2425
2431
  async function findCheckable(locator, context) {
@@ -2714,7 +2720,7 @@ function getNormalizedKey(key) {
2714
2720
  }
2715
2721
 
2716
2722
  function highlightActiveElement(element, context) {
2717
- if (!this.options.enableHighlight && !store.debugMode) return;
2723
+ if (!this.options.highlightElement && !store.debugMode) return;
2718
2724
 
2719
2725
  highlightElement(element, context);
2720
2726
  }
@@ -20,7 +20,7 @@ const { urlEquals } = require('../assert/equal');
20
20
  const { empty } = require('../assert/empty');
21
21
  const { truth } = require('../assert/truth');
22
22
  const {
23
- xpathLocator,
23
+ xpathLocator, normalizeSpacesInString,
24
24
  } = require('../utils');
25
25
  const Locator = require('../locator');
26
26
 
@@ -637,9 +637,9 @@ class TestCafe extends Helper {
637
637
  async see(text, context = null) {
638
638
  let els;
639
639
  if (context) {
640
- els = (await findElements.call(this, this.context, context)).withText(text);
640
+ els = (await findElements.call(this, this.context, context)).withText(normalizeSpacesInString(text));
641
641
  } else {
642
- els = (await findElements.call(this, this.context, '*')).withText(text);
642
+ els = (await findElements.call(this, this.context, '*')).withText(normalizeSpacesInString(text));
643
643
  }
644
644
 
645
645
  return this.t
@@ -727,13 +727,14 @@ class TestCafe extends Helper {
727
727
  * {{> seeInField }}
728
728
  */
729
729
  async seeInField(field, value) {
730
+ const _value = (typeof value === 'boolean') ? value : value.toString();
730
731
  // const expectedValue = findElements.call(this, this.context, field).value;
731
732
  const els = await findFields.call(this, field);
732
733
  assertElementExists(els, field, 'Field');
733
734
  const el = await els.nth(0);
734
735
 
735
736
  return this.t
736
- .expect(await el.value).eql(value)
737
+ .expect(await el.value).eql(_value)
737
738
  .catch(mapError);
738
739
  }
739
740
 
@@ -741,13 +742,14 @@ class TestCafe extends Helper {
741
742
  * {{> dontSeeInField }}
742
743
  */
743
744
  async dontSeeInField(field, value) {
745
+ const _value = (typeof value === 'boolean') ? value : value.toString();
744
746
  // const expectedValue = findElements.call(this, this.context, field).value;
745
747
  const els = await findFields.call(this, field);
746
748
  assertElementExists(els, field, 'Field');
747
749
  const el = await els.nth(0);
748
750
 
749
751
  return this.t
750
- .expect(el.value).notEql(value)
752
+ .expect(el.value).notEql(_value)
751
753
  .catch(mapError);
752
754
  }
753
755
 
@@ -960,8 +962,11 @@ class TestCafe extends Helper {
960
962
  const body = document.body;
961
963
  const html = document.documentElement;
962
964
  window.scrollTo(0, Math.max(
963
- body.scrollHeight, body.offsetHeight,
964
- html.clientHeight, html.scrollHeight, html.offsetHeight,
965
+ body.scrollHeight,
966
+ body.offsetHeight,
967
+ html.clientHeight,
968
+ html.scrollHeight,
969
+ html.offsetHeight,
965
970
  ));
966
971
  }).with({ boundTestRun: this.t })().catch(mapError);
967
972
  }
@@ -1220,7 +1225,9 @@ class TestCafe extends Helper {
1220
1225
  }
1221
1226
 
1222
1227
  async function waitForFunction(browserFn, waitTimeout) {
1223
- const pause = () => new Promise((done => setTimeout(done, 50)));
1228
+ const pause = () => new Promise((done => {
1229
+ setTimeout(done, 50);
1230
+ }));
1224
1231
 
1225
1232
  const start = Date.now();
1226
1233
  // eslint-disable-next-line no-constant-condition
@@ -62,7 +62,7 @@ const webRoot = 'body';
62
62
  * @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
63
63
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
64
64
  * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
65
- * @prop {boolean} [highlightElement] - highlight the interacting elements
65
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
66
66
  */
67
67
  const config = {};
68
68
 
@@ -429,6 +429,7 @@ class WebDriver extends Helper {
429
429
  keepCookies: false,
430
430
  keepBrowserState: false,
431
431
  deprecationWarnings: false,
432
+ highlightElement: false,
432
433
  };
433
434
 
434
435
  // override defaults with config
@@ -1347,7 +1348,8 @@ class WebDriver extends Helper {
1347
1348
  *
1348
1349
  */
1349
1350
  async seeInField(field, value) {
1350
- return proceedSeeField.call(this, 'assert', field, value);
1351
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1352
+ return proceedSeeField.call(this, 'assert', field, _value);
1351
1353
  }
1352
1354
 
1353
1355
  /**
@@ -1355,7 +1357,8 @@ class WebDriver extends Helper {
1355
1357
  *
1356
1358
  */
1357
1359
  async dontSeeInField(field, value) {
1358
- return proceedSeeField.call(this, 'negate', field, value);
1360
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1361
+ return proceedSeeField.call(this, 'negate', field, _value);
1359
1362
  }
1360
1363
 
1361
1364
  /**
@@ -2062,7 +2065,9 @@ class WebDriver extends Helper {
2062
2065
  * {{> wait }}
2063
2066
  */
2064
2067
  async wait(sec) {
2065
- return new Promise(resolve => setTimeout(resolve, sec * 1000));
2068
+ return new Promise(resolve => {
2069
+ setTimeout(resolve, sec * 1000);
2070
+ });
2066
2071
  }
2067
2072
 
2068
2073
  /**
@@ -2167,20 +2172,18 @@ class WebDriver extends Helper {
2167
2172
  const aSec = sec || this.options.waitForTimeoutInSeconds;
2168
2173
  const _context = context || this.root;
2169
2174
 
2170
- return this.browser.waitUntil(
2171
- async () => {
2172
- const res = await this.$$(withStrictLocator.call(this, _context));
2173
- if (!res || res.length === 0) return false;
2174
- const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
2175
- if (Array.isArray(selected)) {
2176
- return selected.filter(part => part.indexOf(text) >= 0).length > 0;
2177
- }
2178
- return selected.indexOf(text) >= 0;
2179
- }, {
2180
- timeout: aSec * 1000,
2181
- timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2182
- },
2183
- );
2175
+ return this.browser.waitUntil(async () => {
2176
+ const res = await this.$$(withStrictLocator.call(this, _context));
2177
+ if (!res || res.length === 0) return false;
2178
+ const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
2179
+ if (Array.isArray(selected)) {
2180
+ return selected.filter(part => part.indexOf(text) >= 0).length > 0;
2181
+ }
2182
+ return selected.indexOf(text) >= 0;
2183
+ }, {
2184
+ timeout: aSec * 1000,
2185
+ timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2186
+ });
2184
2187
  }
2185
2188
 
2186
2189
  /**
@@ -2190,20 +2193,18 @@ class WebDriver extends Helper {
2190
2193
  const client = this.browser;
2191
2194
  const aSec = sec || this.options.waitForTimeoutInSeconds;
2192
2195
 
2193
- return client.waitUntil(
2194
- async () => {
2195
- const res = await findFields.call(this, field);
2196
- if (!res || res.length === 0) return false;
2197
- const selected = await forEachAsync(res, async el => el.getValue());
2198
- if (Array.isArray(selected)) {
2199
- return selected.filter(part => part.indexOf(value) >= 0).length > 0;
2200
- }
2201
- return selected.indexOf(value) >= 0;
2202
- }, {
2203
- timeout: aSec * 1000,
2204
- timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2205
- },
2206
- );
2196
+ return client.waitUntil(async () => {
2197
+ const res = await findFields.call(this, field);
2198
+ if (!res || res.length === 0) return false;
2199
+ const selected = await forEachAsync(res, async el => el.getValue());
2200
+ if (Array.isArray(selected)) {
2201
+ return selected.filter(part => part.indexOf(value) >= 0).length > 0;
2202
+ }
2203
+ return selected.indexOf(value) >= 0;
2204
+ }, {
2205
+ timeout: aSec * 1000,
2206
+ timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2207
+ });
2207
2208
  }
2208
2209
 
2209
2210
  /**
@@ -2411,8 +2412,11 @@ class WebDriver extends Helper {
2411
2412
  const body = document.body;
2412
2413
  const html = document.documentElement;
2413
2414
  window.scrollTo(0, Math.max(
2414
- body.scrollHeight, body.offsetHeight,
2415
- html.clientHeight, html.scrollHeight, html.offsetHeight
2415
+ body.scrollHeight,
2416
+ body.offsetHeight,
2417
+ html.clientHeight,
2418
+ html.scrollHeight,
2419
+ html.offsetHeight
2416
2420
  ));
2417
2421
  });
2418
2422
  /* eslint-enable */
@@ -2914,7 +2918,7 @@ function isModifierKey(key) {
2914
2918
  }
2915
2919
 
2916
2920
  function highlightActiveElement(element) {
2917
- if (!this.options.enableHighlight && !store.debugMode) return;
2921
+ if (!this.options.highlightElement && !store.debugMode) return;
2918
2922
 
2919
2923
  highlightElement(element, this.browser);
2920
2924
  }
@@ -5,7 +5,8 @@ const Locator = require('../../locator');
5
5
  */
6
6
  class ElementNotFound {
7
7
  constructor(
8
- locator, prefixMessage = 'Element',
8
+ locator,
9
+ prefixMessage = 'Element',
9
10
  postfixMessage = 'was not found by text|CSS|XPath',
10
11
  ) {
11
12
  if (typeof locator === 'object') {
@@ -0,0 +1,9 @@
1
+ module.exports = async function findReact(matcher, locator) {
2
+ let _locator = `_react=${locator.react}`;
3
+
4
+ if (locator.props && locator.props.name) {
5
+ _locator += `[name = "${locator.props.name}"]`;
6
+ }
7
+
8
+ return matcher.locator(_locator).all();
9
+ };
@@ -8,7 +8,7 @@ module.exports.highlightElement = (element, context) => {
8
8
 
9
9
  try {
10
10
  // Playwright, Puppeteer
11
- context.evaluate(clientSideHighlightFn, element);
11
+ context.evaluate(clientSideHighlightFn, element).catch(err => console.error(err));
12
12
  } catch (e) {
13
13
  // WebDriver
14
14
  try {
@@ -30,7 +30,7 @@ const parameterTypeRegistry = new ParameterTypeRegistry();
30
30
  const matchStep = (step) => {
31
31
  for (const stepName in steps) {
32
32
  if (stepName.indexOf('/') === 0) {
33
- const regExpArr = stepName.match(new RegExp('^/(.*?)/([gimy]*)$')) || [];
33
+ const regExpArr = stepName.match(/^\/(.*?)\/([gimy]*)$/) || [];
34
34
  const res = step.match(new RegExp(regExpArr[1], regExpArr[2]));
35
35
  if (res) {
36
36
  const fn = steps[stepName];
@@ -97,7 +97,8 @@ class MochaFactory {
97
97
  const attributes = Object.getOwnPropertyDescriptor(reporterOptions, 'codeceptjs-cli-reporter');
98
98
  if (reporterOptions['codeceptjs-cli-reporter'] && attributes) {
99
99
  Object.defineProperty(
100
- reporterOptions, 'codeceptjs/lib/cli',
100
+ reporterOptions,
101
+ 'codeceptjs/lib/cli',
101
102
  attributes,
102
103
  );
103
104
  delete reporterOptions['codeceptjs-cli-reporter'];
package/lib/pause.js CHANGED
@@ -67,6 +67,7 @@ function pauseSession(passedObject = {}) {
67
67
  });
68
68
  return new Promise(((resolve) => {
69
69
  finish = resolve;
70
+ // eslint-disable-next-line
70
71
  return askForStep();
71
72
  }));
72
73
  }
@@ -114,13 +115,13 @@ async function parseInput(cmd) {
114
115
  isAiCommand = true;
115
116
  executeCommand = executeCommand.then(async () => {
116
117
  try {
117
- const html = await res;
118
+ const html = await res;
118
119
  aiAssistant.setHtmlContext(html);
119
120
  } catch (err) {
120
121
  output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
121
122
  return;
122
123
  } finally {
123
- output.level(currentOutputLevel);
124
+ output.level(currentOutputLevel);
124
125
  }
125
126
  // aiAssistant.mockResponse("```js\nI.click('Sign in');\n```");
126
127
  const spinner = ora("Processing OpenAI request...").start();
@@ -148,12 +149,12 @@ async function parseInput(cmd) {
148
149
  })
149
150
 
150
151
  const val = await executeCommand;
151
-
152
+
152
153
  if (isCustomCommand) {
153
154
  if (val !== undefined) console.log('Result', '$res=', val); // eslint-disable-line
154
155
  $res = val;
155
156
  }
156
-
157
+
157
158
  if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) {
158
159
  output.print(output.styles.success(' OK '), cmd);
159
160
  }
@@ -147,11 +147,10 @@ module.exports = function (config = {}) {
147
147
  async function tryToHeal(failedStep, err) {
148
148
  output.debug(`Running OpenAI to heal ${failedStep.toCode()} step`);
149
149
 
150
- const codeSnippets = await aiAssistant.healFailedStep(
151
- failedStep, err, currentTest,
152
- );
150
+ const codeSnippets = await aiAssistant.healFailedStep(failedStep, err, currentTest);
153
151
 
154
152
  output.debug(`Received ${codeSnippets.length} suggestions from OpenAI`);
153
+ const I = Container.support('I'); // eslint-disable-line
155
154
 
156
155
  for (const codeSnippet of codeSnippets) {
157
156
  try {
@@ -58,7 +58,12 @@ let seleniumUrl = 'http://localhost:$port$';
58
58
  const supportedHelpers = ['WebDriver'];
59
59
  const SELENOID_START_TIMEOUT = 2000;
60
60
  const SELENOID_STOP_TIMEOUT = 10000;
61
- const wait = time => new Promise((res) => setTimeout(() => res(), time));
61
+ const wait = time => new Promise((res) => {
62
+ setTimeout(() => {
63
+ // @ts-ignore
64
+ res();
65
+ }, time);
66
+ });
62
67
 
63
68
  /**
64
69
  * [Selenoid](https://aerokube.com/selenoid/) plugin automatically starts browsers and video recording.
package/lib/step.js CHANGED
@@ -138,13 +138,7 @@ class Step {
138
138
 
139
139
  /** @return {string} */
140
140
  humanize() {
141
- return this.name
142
- // insert a space before all caps
143
- .replace(/([A-Z])/g, ' $1')
144
- // _ chars to spaces
145
- .replace('_', ' ')
146
- // uppercase the first character
147
- .replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase());
141
+ return humanizeString(this.name);
148
142
  }
149
143
 
150
144
  /** @return {string} */
@@ -247,12 +241,21 @@ class MetaStep extends Step {
247
241
  }
248
242
 
249
243
  toString() {
250
- const actorText = !this.isBDD() && !this.isWithin() ? `${this.actor}:` : this.actor;
251
- return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`;
244
+ const actorText = this.actor;
245
+
246
+ if (this.isBDD() || this.isWithin()) {
247
+ return `${this.prefix}${actorText} ${this.name} "${this.humanizeArgs()}${this.suffix}"`;
248
+ }
249
+
250
+ if (actorText === 'I') {
251
+ return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`;
252
+ }
253
+
254
+ return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`;
252
255
  }
253
256
 
254
257
  humanize() {
255
- return this.name;
258
+ return humanizeString(this.name);
256
259
  }
257
260
 
258
261
  setTrace() {
@@ -316,3 +319,17 @@ function dryRunResolver() {
316
319
  },
317
320
  };
318
321
  }
322
+
323
+ function humanizeString(string) {
324
+ // split strings by words, then make them all lowercase
325
+ const _result = string.replace(/([a-z](?=[A-Z]))/g, '$1 ')
326
+ .split(' ')
327
+ .map(word => word.toLowerCase());
328
+
329
+ _result[0] = _result[0] === 'i' ? capitalizeFLetter(_result[0]) : _result[0];
330
+ return _result.join(' ').trim();
331
+ }
332
+
333
+ function capitalizeFLetter(string) {
334
+ return (string[0].toUpperCase() + string.slice(1));
335
+ }
package/lib/utils.js CHANGED
@@ -472,3 +472,7 @@ module.exports.printObjectProperties = (obj) => {
472
472
 
473
473
  return `{${result}}`;
474
474
  };
475
+
476
+ module.exports.normalizeSpacesInString = (string) => {
477
+ return string.replace(/\s+/g, ' ');
478
+ };
package/lib/workers.js CHANGED
@@ -344,7 +344,9 @@ class Workers extends EventEmitter {
344
344
  this._listenWorkerEvents(workerThread);
345
345
  }
346
346
  });
347
- return new Promise(resolve => this.on('end', resolve));
347
+ return new Promise(resolve => {
348
+ this.on('end', resolve);
349
+ });
348
350
  }
349
351
 
350
352
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.5.3",
3
+ "version": "3.5.4",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -40,7 +40,7 @@
40
40
  "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.js",
41
41
  "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix",
42
42
  "docs": "./runok.js docs",
43
- "test:unit": "mocha test/unit --recursive",
43
+ "test:unit": "mocha test/unit --recursive --timeout 5000",
44
44
  "test:runner": "mocha test/runner --recursive --timeout 5000",
45
45
  "test": "npm run test:unit && npm run test:runner",
46
46
  "test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick'",
@@ -54,17 +54,17 @@
54
54
  "def": "./runok.js def",
55
55
  "dev:graphql": "node test/data/graphql/index.js",
56
56
  "publish:site": "./runok.js publish:site",
57
- "update-contributor-faces": "contributor-faces .",
57
+ "update-contributor-faces": "./runok.js contributor:faces",
58
58
  "dtslint": "dtslint typings --localTs './node_modules/typescript/lib'",
59
59
  "prepare": "husky install"
60
60
  },
61
61
  "dependencies": {
62
62
  "@codeceptjs/configure": "^0.10.0",
63
- "@codeceptjs/helper": "^1.0.2",
63
+ "@codeceptjs/helper": "^2.0.1",
64
64
  "@cucumber/cucumber-expressions": "^16",
65
65
  "@cucumber/gherkin": "^26",
66
- "@cucumber/messages": "^21.0.1",
67
- "@xmldom/xmldom": "^0.7.9",
66
+ "@cucumber/messages": "^22.0.0",
67
+ "@xmldom/xmldom": "^0.8.10",
68
68
  "acorn": "^8.10.0",
69
69
  "arrify": "^2.0.1",
70
70
  "axios": "^1.3.3",
@@ -104,8 +104,8 @@
104
104
  "@faker-js/faker": "^7.6.0",
105
105
  "@pollyjs/adapter-puppeteer": "^6.0.5",
106
106
  "@pollyjs/core": "^5.1.0",
107
- "@types/inquirer": "^0.0.35",
108
- "@types/node": "^8.10.66",
107
+ "@types/inquirer": "^9.0.3",
108
+ "@types/node": "^20.4.4",
109
109
  "@wdio/sauce-service": "^8.3.8",
110
110
  "@wdio/selenium-standalone-service": "^8.3.2",
111
111
  "@wdio/utils": "^8.3.0",
@@ -115,12 +115,12 @@
115
115
  "contributor-faces": "^1.0.3",
116
116
  "documentation": "^12.3.0",
117
117
  "dtslint": "^4.1.6",
118
- "electron": "^25.2.0",
118
+ "electron": "^26.1.0",
119
119
  "eslint": "^8.45.0",
120
120
  "eslint-config-airbnb-base": "^15.0.0",
121
121
  "eslint-plugin-import": "^2.25.4",
122
122
  "eslint-plugin-mocha": "^6.3.0",
123
- "expect": "^26.6.2",
123
+ "expect": "^29.6.2",
124
124
  "express": "^4.17.2",
125
125
  "graphql": "^14.6.0",
126
126
  "husky": "^8.0.1",
@@ -135,18 +135,18 @@
135
135
  "runok": "^0.9.2",
136
136
  "sinon": "^15.2.0",
137
137
  "sinon-chai": "^3.7.0",
138
- "testcafe": "^2.1.0",
138
+ "testcafe": "^3.0.1",
139
139
  "ts-morph": "^3.1.3",
140
140
  "ts-node": "^10.9.1",
141
141
  "tsd-jsdoc": "^2.5.0",
142
- "typedoc": "^0.23.10",
142
+ "typedoc": "^0.24.8",
143
143
  "typedoc-plugin-markdown": "^3.13.4",
144
- "typescript": "^4.8.4",
144
+ "typescript": "^5.1.6",
145
145
  "wdio-docker-service": "^1.5.0",
146
146
  "webdriverio": "^8.3.8",
147
147
  "xml2js": "^0.6.0",
148
148
  "xmldom": "^0.6.0",
149
- "xpath": "0.0.27"
149
+ "xpath": "0.0.33"
150
150
  },
151
151
  "engines": {
152
152
  "node": ">=16.0",