codeceptjs 3.5.4-beta.1 → 3.5.5

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 (68) hide show
  1. package/CHANGELOG.md +368 -0
  2. package/README.md +0 -2
  3. package/docs/build/Appium.js +48 -7
  4. package/docs/build/GraphQL.js +25 -0
  5. package/docs/build/Nightmare.js +15 -6
  6. package/docs/build/Playwright.js +436 -197
  7. package/docs/build/Protractor.js +17 -8
  8. package/docs/build/Puppeteer.js +37 -20
  9. package/docs/build/TestCafe.js +19 -10
  10. package/docs/build/WebDriver.js +45 -37
  11. package/docs/changelog.md +375 -0
  12. package/docs/community-helpers.md +8 -4
  13. package/docs/examples.md +8 -2
  14. package/docs/helpers/Appium.md +39 -2
  15. package/docs/helpers/GraphQL.md +21 -0
  16. package/docs/helpers/Nightmare.md +1260 -0
  17. package/docs/helpers/Playwright.md +223 -119
  18. package/docs/helpers/Protractor.md +1711 -0
  19. package/docs/helpers/Puppeteer.md +31 -29
  20. package/docs/helpers/TestCafe.md +18 -17
  21. package/docs/helpers/WebDriver.md +34 -32
  22. package/docs/playwright.md +24 -1
  23. package/docs/webapi/dontSeeInField.mustache +1 -1
  24. package/docs/webapi/executeAsyncScript.mustache +2 -0
  25. package/docs/webapi/executeScript.mustache +2 -0
  26. package/docs/webapi/seeInField.mustache +1 -1
  27. package/docs/wiki/Books-&-Posts.md +0 -0
  28. package/docs/wiki/Community-Helpers-&-Plugins.md +8 -4
  29. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +46 -14
  30. package/docs/wiki/Examples.md +8 -2
  31. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
  32. package/docs/wiki/Home.md +0 -0
  33. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
  34. package/docs/wiki/Release-Process.md +0 -0
  35. package/docs/wiki/Roadmap.md +0 -0
  36. package/docs/wiki/Tests.md +0 -0
  37. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
  38. package/docs/wiki/Videos.md +0 -0
  39. package/lib/codecept.js +1 -0
  40. package/lib/command/definitions.js +2 -7
  41. package/lib/command/init.js +40 -4
  42. package/lib/command/run-multiple/collection.js +17 -5
  43. package/lib/command/run-workers.js +4 -0
  44. package/lib/command/run.js +6 -0
  45. package/lib/helper/Appium.js +46 -5
  46. package/lib/helper/GraphQL.js +25 -0
  47. package/lib/helper/Nightmare.js +1415 -0
  48. package/lib/helper/Playwright.js +336 -62
  49. package/lib/helper/Protractor.js +1837 -0
  50. package/lib/helper/Puppeteer.js +31 -18
  51. package/lib/helper/TestCafe.js +15 -8
  52. package/lib/helper/WebDriver.js +39 -35
  53. package/lib/helper/clientscripts/nightmare.js +213 -0
  54. package/lib/helper/errors/ElementNotFound.js +2 -1
  55. package/lib/helper/scripts/highlightElement.js +1 -1
  56. package/lib/interfaces/bdd.js +1 -1
  57. package/lib/mochaFactory.js +2 -1
  58. package/lib/pause.js +6 -4
  59. package/lib/plugin/heal.js +2 -3
  60. package/lib/plugin/selenoid.js +6 -1
  61. package/lib/step.js +27 -10
  62. package/lib/utils.js +4 -0
  63. package/lib/workers.js +3 -1
  64. package/package.json +87 -87
  65. package/typings/promiseBasedTypes.d.ts +163 -126
  66. package/typings/types.d.ts +183 -144
  67. package/docs/build/Polly.js +0 -42
  68. package/docs/build/SeleniumWebdriver.js +0 -76
@@ -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);
@@ -603,8 +604,8 @@ class Puppeteer extends Helper {
603
604
  return this.switchTo(null)
604
605
  .then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()));
605
606
  }
606
- await this.switchTo(locator);
607
- this.withinLocator = new Locator(locator);
607
+ await this.switchTo(frame);
608
+ this.withinLocator = new Locator(frame);
608
609
  return;
609
610
  }
610
611
 
@@ -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) {
@@ -2005,7 +2011,7 @@ class Puppeteer extends Helper {
2005
2011
  assertElementExists(els, locator);
2006
2012
 
2007
2013
  return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
2008
- if (/failed: timeout/i.test(e.message)) {
2014
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2009
2015
  throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`);
2010
2016
  } else {
2011
2017
  throw e;
@@ -2109,7 +2115,7 @@ class Puppeteer extends Helper {
2109
2115
  return currUrl.indexOf(urlPart) > -1;
2110
2116
  }, { timeout: waitTimeout }, urlPart).catch(async (e) => {
2111
2117
  const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
2112
- if (/failed: timeout/i.test(e.message)) {
2118
+ if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2113
2119
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
2114
2120
  } else {
2115
2121
  throw e;
@@ -2133,7 +2139,7 @@ class Puppeteer extends Helper {
2133
2139
  return currUrl.indexOf(urlPart) > -1;
2134
2140
  }, { timeout: waitTimeout }, urlPart).catch(async (e) => {
2135
2141
  const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
2136
- if (/failed: timeout/i.test(e.message)) {
2142
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2137
2143
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
2138
2144
  } else {
2139
2145
  throw e;
@@ -2342,6 +2348,10 @@ async function findElements(matcher, locator) {
2342
2348
  if (locator.react) return findReact(matcher.executionContext(), locator);
2343
2349
  locator = new Locator(locator, 'css');
2344
2350
  if (!locator.isXPath()) return matcher.$$(locator.simplify());
2351
+ // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2352
+ if (puppeteer.default?.defaultBrowserRevision) {
2353
+ return matcher.$$(`xpath/${locator.value}`);
2354
+ }
2345
2355
  return matcher.$x(locator.value);
2346
2356
  }
2347
2357
 
@@ -2359,7 +2369,7 @@ async function proceedClick(locator, context = null, options = {}) {
2359
2369
  assertElementExists(els, locator, 'Clickable element');
2360
2370
  }
2361
2371
 
2362
- highlightActiveElement.call(this, els[0], this.page);
2372
+ highlightActiveElement.call(this, els[0], await this._getContext());
2363
2373
 
2364
2374
  await els[0].click(options);
2365
2375
  const promises = [];
@@ -2419,7 +2429,7 @@ async function proceedSee(assertType, text, context, strict = false) {
2419
2429
  if (strict) {
2420
2430
  return allText.map(elText => equals(description)[assertType](text, elText));
2421
2431
  }
2422
- return stringIncludes(description)[assertType](text, allText.join(' | '));
2432
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
2423
2433
  }
2424
2434
 
2425
2435
  async function findCheckable(locator, context) {
@@ -2596,7 +2606,10 @@ async function elementSelected(element) {
2596
2606
 
2597
2607
  function isFrameLocator(locator) {
2598
2608
  locator = new Locator(locator);
2599
- if (locator.isFrame()) return locator.value;
2609
+ if (locator.isFrame()) {
2610
+ const _locator = new Locator(locator);
2611
+ return _locator.value;
2612
+ }
2600
2613
  return false;
2601
2614
  }
2602
2615
 
@@ -2714,7 +2727,7 @@ function getNormalizedKey(key) {
2714
2727
  }
2715
2728
 
2716
2729
  function highlightActiveElement(element, context) {
2717
- if (!this.options.enableHighlight && !store.debugMode) return;
2730
+ if (!this.options.highlightElement && !store.debugMode) return;
2718
2731
 
2719
2732
  highlightElement(element, context);
2720
2733
  }
@@ -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
  }
@@ -0,0 +1,213 @@
1
+ if (!window.codeceptjs) {
2
+ /**
3
+ * @alias CodeceptJS.browserCodecept
4
+ * @namespace
5
+ */
6
+ const codeceptjs = {};
7
+
8
+ /**
9
+ * all found elements are stored here for reuse
10
+ * @inner
11
+ * @type {Node[]}
12
+ */
13
+ codeceptjs.elements = [];
14
+
15
+ /**
16
+ * global context changer
17
+ * @inner
18
+ * @type {?Node}
19
+ */
20
+ codeceptjs.within = null;
21
+
22
+ // save
23
+ const storeElement = function (el) {
24
+ if (!el) return;
25
+ return codeceptjs.elements.push(el) - 1; // return index
26
+ };
27
+
28
+ const storeElements = function (els) {
29
+ return els.map(el => storeElement(el));
30
+ };
31
+
32
+ /**
33
+ * finders
34
+ * @param {number} id
35
+ * @return {Node}
36
+ */
37
+ codeceptjs.fetchElement = function (id) {
38
+ if (!this.elements[id]) throw new Error(`Element (${id}) is not accessible`);
39
+ return this.elements[id];
40
+ };
41
+
42
+ /**
43
+ * @param {string} by
44
+ * @param {CodeceptJS.ILocator} locator
45
+ * @param {*} [contextEl]
46
+ * @return {number[]}
47
+ */
48
+ codeceptjs.findAndStoreElements = function (by, locator, contextEl) {
49
+ return storeElements(this.findElements(by, locator, contextEl));
50
+ };
51
+
52
+ /**
53
+ * @param {string} by
54
+ * @param {CodeceptJS.ILocator} locator
55
+ * @param {*} [contextEl]
56
+ * @return {number | undefined}
57
+ */
58
+ codeceptjs.findAndStoreElement = function (by, locator, contextEl) {
59
+ return storeElement(this.findElement(by, locator, contextEl));
60
+ };
61
+
62
+ /**
63
+ * @param {string} by
64
+ * @param {CodeceptJS.ILocator} locator
65
+ */
66
+ codeceptjs.setWithin = function (by, locator) {
67
+ this.within = this.findElement(by, locator);
68
+ };
69
+
70
+ /**
71
+ * @param {string} by
72
+ * @param {CodeceptJS.ILocator} locator
73
+ * @param {*} [contextEl]
74
+ * @return {Node[]}
75
+ */
76
+ codeceptjs.findElements = function (by, locator, contextEl) {
77
+ let context;
78
+ if (typeof contextEl !== 'number') {
79
+ context = this.within || document;
80
+ } else {
81
+ context = this.fetchElement(contextEl);
82
+ }
83
+
84
+ if (by === 'name') {
85
+ by = 'css';
86
+ locator = `*[name="${locator}"]`;
87
+ }
88
+ if (by === 'id') {
89
+ by = 'css';
90
+ locator = `#${locator}`;
91
+ }
92
+
93
+ if (by === 'css') {
94
+ const found = context.querySelectorAll(locator);
95
+ const res = [];
96
+ for (let i = 0; i < found.length; i++) {
97
+ res.push(found[i]);
98
+ }
99
+ return res;
100
+ }
101
+
102
+ if (by === 'xpath') {
103
+ const found = document.evaluate(locator, context, null, 5, null);
104
+ const res = [];
105
+ let current = null;
106
+ while (current = found.iterateNext()) {
107
+ res.push(current);
108
+ }
109
+ return res;
110
+ }
111
+
112
+ if (by === 'frame') {
113
+ if (!Array.isArray(locator)) locator = [locator];
114
+ return [locator.reduce((parent, child) => parent.querySelector(child).contentDocument, window.document).querySelector('body')];
115
+ }
116
+ return [];
117
+ };
118
+
119
+ /**
120
+ * @param {string} by
121
+ * @param {CodeceptJS.ILocator} locator
122
+ * @param {*} [context]
123
+ * @return {?Node}
124
+ */
125
+ codeceptjs.findElement = function (by, locator, context) {
126
+ return this.findElements(by, locator, context)[0] || null;
127
+ };
128
+
129
+ // actions
130
+ /**
131
+ * @param {number} el
132
+ * @return {boolean}
133
+ */
134
+ codeceptjs.clickEl = function (el) {
135
+ if (document.activeElement instanceof HTMLElement) {
136
+ document.activeElement.blur();
137
+ }
138
+ const event = document.createEvent('MouseEvent');
139
+ event.initEvent('click', true, true);
140
+ return this.fetchElement(el).dispatchEvent(event);
141
+ };
142
+
143
+ /** @param {number} el */
144
+ codeceptjs.doubleClickEl = function (el) {
145
+ if (document.activeElement instanceof HTMLElement) {
146
+ document.activeElement.blur();
147
+ }
148
+ const event = document.createEvent('MouseEvent');
149
+ event.initEvent('dblclick', true, true);
150
+ this.fetchElement(el).dispatchEvent(event);
151
+ };
152
+
153
+ /**
154
+ * @param {number} el
155
+ * @param {number | undefined} x
156
+ * @param {number | undefined} y
157
+ */
158
+ codeceptjs.hoverEl = function (el, x, y) {
159
+ if (document.activeElement instanceof HTMLElement) {
160
+ document.activeElement.blur();
161
+ }
162
+
163
+ const event = new MouseEvent('mouseover', {
164
+ bubbles: true,
165
+ cancelable: true,
166
+ screenX: x,
167
+ screenY: y,
168
+ clientX: x,
169
+ clientY: y,
170
+ });
171
+
172
+ this.fetchElement(el).dispatchEvent(event);
173
+ };
174
+
175
+ /** @param {number} el */
176
+ codeceptjs.rightClickEl = function (el) {
177
+ const event = new MouseEvent('contextmenu', {
178
+ bubbles: true,
179
+ cancelable: true,
180
+ view: window,
181
+ buttons: 2,
182
+ });
183
+
184
+ this.fetchElement(el).dispatchEvent(event);
185
+ };
186
+
187
+ /**
188
+ * @param {number} el
189
+ * @return {boolean | undefined}
190
+ */
191
+ codeceptjs.checkEl = function (el) {
192
+ const element = this.fetchElement(el);
193
+ const event = document.createEvent('HTMLEvents');
194
+ if (element.checked) return;
195
+ element.checked = true;
196
+ event.initEvent('change', true, true);
197
+ return element.dispatchEvent(event);
198
+ };
199
+
200
+ /**
201
+ * @param {number} el
202
+ * @return {boolean}
203
+ */
204
+ codeceptjs.unCheckEl = function (el) {
205
+ const element = this.fetchElement(el);
206
+ const event = document.createEvent('HTMLEvents');
207
+ element.checked = false;
208
+ event.initEvent('change', true, true);
209
+ return element.dispatchEvent(event);
210
+ };
211
+
212
+ window.codeceptjs = codeceptjs;
213
+ }
@@ -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') {
@@ -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
@@ -22,6 +22,7 @@ const aiAssistant = new AiAssistant();
22
22
 
23
23
  /**
24
24
  * Pauses test execution and starts interactive shell
25
+ * @param {Object<string, *>} [passedObject]
25
26
  */
26
27
  const pause = function (passedObject = {}) {
27
28
  if (store.dryRun) return;
@@ -67,6 +68,7 @@ function pauseSession(passedObject = {}) {
67
68
  });
68
69
  return new Promise(((resolve) => {
69
70
  finish = resolve;
71
+ // eslint-disable-next-line
70
72
  return askForStep();
71
73
  }));
72
74
  }
@@ -114,13 +116,13 @@ async function parseInput(cmd) {
114
116
  isAiCommand = true;
115
117
  executeCommand = executeCommand.then(async () => {
116
118
  try {
117
- const html = await res;
119
+ const html = await res;
118
120
  aiAssistant.setHtmlContext(html);
119
121
  } catch (err) {
120
122
  output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
121
123
  return;
122
124
  } finally {
123
- output.level(currentOutputLevel);
125
+ output.level(currentOutputLevel);
124
126
  }
125
127
  // aiAssistant.mockResponse("```js\nI.click('Sign in');\n```");
126
128
  const spinner = ora("Processing OpenAI request...").start();
@@ -148,12 +150,12 @@ async function parseInput(cmd) {
148
150
  })
149
151
 
150
152
  const val = await executeCommand;
151
-
153
+
152
154
  if (isCustomCommand) {
153
155
  if (val !== undefined) console.log('Result', '$res=', val); // eslint-disable-line
154
156
  $res = val;
155
157
  }
156
-
158
+
157
159
  if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) {
158
160
  output.print(output.styles.success(' OK '), cmd);
159
161
  }