codeceptjs 3.5.11 → 3.5.12-beta.2

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.
@@ -63,6 +63,8 @@ const webRoot = 'body';
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
65
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
66
+ * @prop {string} [logLevel=silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
67
+ * @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol.
66
68
  */
67
69
  const config = {};
68
70
 
@@ -72,6 +74,13 @@ const config = {};
72
74
  *
73
75
  * WebDriver requires Selenium Server and ChromeDriver/GeckoDriver to be installed. Those tools can be easily installed via NPM. Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
74
76
  *
77
+ * With the release of WebdriverIO version v8.14.0, and onwards, all driver management hassles are now a thing of the past 🙌. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/).
78
+ * One of the significant advantages of this update is that you can now get rid of any driver services you previously had to manage, such as
79
+ * `wdio-chromedriver-service`, `wdio-geckodriver-service`, `wdio-edgedriver-service`, `wdio-safaridriver-service`, and even `@wdio/selenium-standalone-service`.
80
+ *
81
+ * For those who require custom driver options, fear not; WebDriver Helper allows you to pass in driver options through custom WebDriver configuration.
82
+ * If you have a custom grid, use a cloud service, or prefer to run your own driver, there's no need to worry since WebDriver Helper will only start a driver when there are no other connection information settings like hostname or port specified.
83
+ *
75
84
  * <!-- configuration -->
76
85
  *
77
86
  * Example:
@@ -93,6 +102,28 @@ const config = {};
93
102
  * }
94
103
  * ```
95
104
  *
105
+ * Testing Chrome locally is now more convenient than ever. You can define a browser channel, and WebDriver Helper will take care of downloading the specified browser version for you.
106
+ * For example:
107
+ *
108
+ * ```js
109
+ * {
110
+ * helpers: {
111
+ * WebDriver : {
112
+ * smartWait: 5000,
113
+ * browser: "chrome",
114
+ * browserVersion: '116.0.5793.0', // or 'stable', 'beta', 'dev' or 'canary'
115
+ * restart: false,
116
+ * windowSize: "maximize",
117
+ * timeouts: {
118
+ * "script": 60000,
119
+ * "page load": 10000
120
+ * }
121
+ * }
122
+ * }
123
+ * }
124
+ * ```
125
+ *
126
+ *
96
127
  * Example with basic authentication
97
128
  * ```js
98
129
  * {
@@ -133,6 +164,25 @@ const config = {};
133
164
  * }
134
165
  * ```
135
166
  *
167
+ * ### Running with devtools protocol
168
+ *
169
+ * ```js
170
+ * {
171
+ * helpers: {
172
+ * WebDriver : {
173
+ * url: "http://localhost",
174
+ * browser: "chrome",
175
+ * devtoolsProtocol: true,
176
+ * desiredCapabilities: {
177
+ * chromeOptions: {
178
+ * args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
179
+ * }
180
+ * }
181
+ * }
182
+ * }
183
+ * }
184
+ * ```
185
+ *
136
186
  * ### Internet Explorer
137
187
  *
138
188
  * Additional configuration params can be used from [IE options](https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/IE/Options.html)
@@ -415,7 +465,6 @@ class WebDriver extends Helper {
415
465
  _validateConfig(config) {
416
466
  const defaults = {
417
467
  logLevel: 'silent',
418
- path: '/wd/hub',
419
468
  // codeceptjs
420
469
  remoteFileUpload: true,
421
470
  smartWait: 0,
@@ -435,12 +484,17 @@ class WebDriver extends Helper {
435
484
  // override defaults with config
436
485
  config = Object.assign(defaults, config);
437
486
 
438
- if (typeof config.host !== 'undefined') config.hostname = config.host; // webdriverio spec
487
+ if (config.host) {
488
+ // webdriverio spec
489
+ config.hostname = config.host;
490
+ config.path = '/wd/hub';
491
+ }
439
492
  config.baseUrl = config.url || config.baseUrl;
440
493
  if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
441
494
  config.capabilities = config.desiredCapabilities;
442
495
  }
443
496
  config.capabilities.browserName = config.browser || config.capabilities.browserName;
497
+ config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion;
444
498
  if (config.capabilities.chromeOptions) {
445
499
  config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions;
446
500
  delete config.capabilities.chromeOptions;
@@ -542,6 +596,10 @@ class WebDriver extends Helper {
542
596
  delete this.options.capabilities.hostname;
543
597
  delete this.options.capabilities.port;
544
598
  delete this.options.capabilities.path;
599
+ if (this.options.devtoolsProtocol) {
600
+ if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) throw Error('The devtools protocol is only working with Chrome or Chromium');
601
+ this.options.automationProtocol = 'devtools';
602
+ }
545
603
  this.browser = await webdriverio.remote(this.options);
546
604
  }
547
605
  } catch (err) {
@@ -1043,7 +1101,8 @@ class WebDriver extends Helper {
1043
1101
  assertElementExists(res, field, 'Field');
1044
1102
  const elem = usingFirstElement(res);
1045
1103
  highlightActiveElement.call(this, elem);
1046
- return elem.setValue(value.toString());
1104
+ await elem.clearValue();
1105
+ await elem.setValue(value.toString());
1047
1106
  }
1048
1107
 
1049
1108
  /**
@@ -1055,6 +1114,10 @@ class WebDriver extends Helper {
1055
1114
  assertElementExists(res, field, 'Field');
1056
1115
  const elem = usingFirstElement(res);
1057
1116
  highlightActiveElement.call(this, elem);
1117
+ if (this.options.automationProtocol) {
1118
+ const curentValue = await elem.getValue();
1119
+ return elem.setValue(curentValue + value.toString());
1120
+ }
1058
1121
  return elem.addValue(value.toString());
1059
1122
  }
1060
1123
 
@@ -1067,6 +1130,9 @@ class WebDriver extends Helper {
1067
1130
  assertElementExists(res, field, 'Field');
1068
1131
  const elem = usingFirstElement(res);
1069
1132
  highlightActiveElement.call(this, elem);
1133
+ if (this.options.automationProtocol) {
1134
+ return elem.setValue('');
1135
+ }
1070
1136
  return elem.clearValue(getElementId(elem));
1071
1137
  }
1072
1138
 
@@ -1120,7 +1186,7 @@ class WebDriver extends Helper {
1120
1186
  const el = usingFirstElement(res);
1121
1187
 
1122
1188
  // Remote Upload (when running Selenium Server)
1123
- if (this.options.remoteFileUpload) {
1189
+ if (this.options.remoteFileUpload && !this.options.automationProtocol) {
1124
1190
  try {
1125
1191
  this.debugSection('File', 'Uploading file to remote server');
1126
1192
  file = await this.browser.uploadFile(file);
@@ -1498,35 +1564,33 @@ class WebDriver extends Helper {
1498
1564
  async seeCssPropertiesOnElements(locator, cssProperties) {
1499
1565
  const res = await this._locate(locator);
1500
1566
  assertElementExists(res, locator);
1567
+
1568
+ const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1501
1569
  const elemAmount = res.length;
1570
+ let props = [];
1502
1571
 
1503
- let props = await forEachAsync(res, async (el) => {
1504
- return forEachAsync(Object.keys(cssProperties), async (prop) => {
1505
- const propValue = await this.browser.getElementCSSValue(getElementId(el), prop);
1506
- if (isColorProperty(prop) && propValue && propValue.value) {
1507
- return convertColorToRGBA(propValue.value);
1572
+ for (const element of res) {
1573
+ for (const prop of Object.keys(cssProperties)) {
1574
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
1575
+ if (isColorProperty(prop)) {
1576
+ props.push(convertColorToRGBA(cssProp));
1577
+ } else {
1578
+ props.push(cssProp);
1508
1579
  }
1509
- return propValue;
1510
- });
1511
- });
1512
-
1513
- const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1580
+ }
1581
+ }
1514
1582
 
1515
1583
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
1516
1584
  if (!Array.isArray(props)) props = [props];
1517
1585
  let chunked = chunkArray(props, values.length);
1518
1586
  chunked = chunked.filter((val) => {
1519
1587
  for (let i = 0; i < val.length; ++i) {
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;
1588
+ // eslint-disable-next-line eqeqeq
1589
+ if (val[i] != values[i]) return false;
1523
1590
  }
1524
1591
  return true;
1525
1592
  });
1526
- return assert.ok(
1527
- chunked.length === elemAmount,
1528
- `expected all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`,
1529
- );
1593
+ return equals(`all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount);
1530
1594
  }
1531
1595
 
1532
1596
  /**
@@ -1546,9 +1610,9 @@ class WebDriver extends Helper {
1546
1610
  let chunked = chunkArray(attrs, values.length);
1547
1611
  chunked = chunked.filter((val) => {
1548
1612
  for (let i = 0; i < val.length; ++i) {
1549
- const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1613
+ const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1550
1614
  const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1551
- if (_acutal !== _expected) return false;
1615
+ if (_actual !== _expected) return false;
1552
1616
  }
1553
1617
  return true;
1554
1618
  });
@@ -1672,7 +1736,11 @@ class WebDriver extends Helper {
1672
1736
  const res = await this._locate(withStrictLocator(locator), true);
1673
1737
  assertElementExists(res, locator);
1674
1738
  const elem = usingFirstElement(res);
1675
- return elem.moveTo({ xOffset, yOffset });
1739
+ try {
1740
+ await elem.moveTo({ xOffset, yOffset });
1741
+ } catch (e) {
1742
+ debug(e.message);
1743
+ }
1676
1744
  }
1677
1745
 
1678
1746
  /**
@@ -1925,7 +1993,7 @@ class WebDriver extends Helper {
1925
1993
  * {{> resizeWindow }}
1926
1994
  */
1927
1995
  async resizeWindow(width, height) {
1928
- return this._resizeBrowserWindow(this.browser, width, height);
1996
+ return this.browser.setWindowSize(width, height);
1929
1997
  }
1930
1998
 
1931
1999
  async _resizeBrowserWindow(browser, width, height) {
@@ -2306,12 +2374,33 @@ class WebDriver extends Helper {
2306
2374
  return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
2307
2375
  }
2308
2376
 
2377
+ /**
2378
+ * {{> waitForNumberOfTabs }}
2379
+ */
2380
+ async waitForNumberOfTabs(expectedTabs, sec) {
2381
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds;
2382
+ let currentTabs;
2383
+ let count = 0;
2384
+
2385
+ do {
2386
+ currentTabs = await this.grabNumberOfOpenTabs();
2387
+ await this.wait(1);
2388
+ count += 1000;
2389
+ if (currentTabs >= expectedTabs) return;
2390
+ } while (count <= waitTimeout);
2391
+
2392
+ throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
2393
+ }
2394
+
2309
2395
  /**
2310
2396
  * {{> switchTo }}
2311
2397
  */
2312
2398
  async switchTo(locator) {
2313
2399
  this.browser.isInsideFrame = true;
2314
2400
  if (Number.isInteger(locator)) {
2401
+ if (this.options.automationProtocol) {
2402
+ return this.browser.switchToFrame(locator + 1);
2403
+ }
2315
2404
  return this.browser.switchToFrame(locator);
2316
2405
  }
2317
2406
  if (!locator) {
@@ -2448,21 +2537,41 @@ class WebDriver extends Helper {
2448
2537
  }
2449
2538
 
2450
2539
  /**
2540
+ * This method is **deprecated**.
2541
+ *
2542
+ *
2451
2543
  * {{> setGeoLocation }}
2452
2544
  */
2453
- async setGeoLocation(latitude, longitude, altitude = null) {
2454
- if (altitude) {
2455
- return this.browser.setGeoLocation({ latitude, longitude });
2545
+ async setGeoLocation(latitude, longitude) {
2546
+ if (!this.options.automationProtocol) {
2547
+ console.log(`setGeoLocation deprecated:
2548
+ * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation
2549
+ * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
2550
+ return;
2456
2551
  }
2457
- return this.browser.setGeoLocation({ latitude, longitude, altitude });
2552
+ this.geoLocation = { latitude, longitude };
2553
+ const puppeteerBrowser = await this.browser.getPuppeteer();
2554
+ await this.browser.call(async () => {
2555
+ const pages = await puppeteerBrowser.pages();
2556
+ await pages[0].setGeolocation({ latitude, longitude });
2557
+ });
2458
2558
  }
2459
2559
 
2460
2560
  /**
2561
+ * This method is **deprecated**.
2562
+ *
2461
2563
  * {{> grabGeoLocation }}
2462
2564
  *
2463
2565
  */
2464
2566
  async grabGeoLocation() {
2465
- return this.browser.getGeoLocation();
2567
+ if (!this.options.automationProtocol) {
2568
+ console.log(`grabGeoLocation deprecated:
2569
+ * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation
2570
+ * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
2571
+ return;
2572
+ }
2573
+ if (!this.geoLocation) return 'No GeoLocation is set!';
2574
+ return this.geoLocation;
2466
2575
  }
2467
2576
 
2468
2577
  /**
@@ -2658,7 +2767,7 @@ async function proceedSeeField(assertType, field, value) {
2658
2767
  }
2659
2768
  };
2660
2769
 
2661
- const proceedSingle = el => this.browser.getElementAttribute(getElementId(el), 'value').then((res) => {
2770
+ const proceedSingle = el => el.getValue().then((res) => {
2662
2771
  if (res === null) {
2663
2772
  throw new Error(`Element ${el.selector} has no value attribute`);
2664
2773
  }
package/lib/locator.js CHANGED
@@ -1,4 +1,4 @@
1
- const cssToXPath = require('convert-cssxpath');
1
+ let cssToXPath;
2
2
  const { sprintf } = require('sprintf-js');
3
3
 
4
4
  const { xpathLocator } = require('./utils');
@@ -158,11 +158,26 @@ class Locator {
158
158
  }
159
159
 
160
160
  /**
161
+ * @param {string} [pseudoSelector] CSS to XPath extension pseudo: https://www.npmjs.com/package/csstoxpath?activeTab=explore#extension-pseudos
161
162
  * @returns {string}
162
163
  */
163
- toXPath() {
164
+ toXPath(pseudoSelector = '') {
165
+ const limitation = [':nth-of-type', ':first-of-type', ':last-of-type', ':nth-last-child', ':nth-last-of-type', ':checked', ':disabled', ':enabled', ':required', ':lang'];
166
+
167
+ if (pseudoSelector.includes(':text-')) {
168
+ cssToXPath = require('csstoxpath');
169
+ } else if (this.value.includes('-')) {
170
+ if (!limitation.some(item => this.value.includes(item))) {
171
+ cssToXPath = require('csstoxpath');
172
+ }
173
+ } else if (limitation.some(item => this.value.includes(item))) {
174
+ cssToXPath = require('./css2xpath/js/css_to_xpath');
175
+ } else {
176
+ cssToXPath = require('./css2xpath/js/css_to_xpath');
177
+ }
178
+
164
179
  if (this.isXPath()) return this.value;
165
- if (this.isCSS()) return cssToXPath.convert(this.value);
180
+ if (this.isCSS()) return cssToXPath(`${this.value}${pseudoSelector}`);
166
181
 
167
182
  throw new Error('Can\'t be converted to XPath');
168
183
  }
@@ -243,12 +258,24 @@ class Locator {
243
258
  }
244
259
 
245
260
  /**
261
+ * Find an element containing a text
246
262
  * @param {string} text
247
263
  * @returns {Locator}
248
264
  */
249
265
  withText(text) {
250
266
  text = xpathLocator.literal(text);
251
- const xpath = sprintf('%s[%s]', this.toXPath(), `contains(., ${text})`);
267
+ const xpath = this.toXPath(`:text-contains-case(${text})`);
268
+ return new Locator({ xpath });
269
+ }
270
+
271
+ /**
272
+ * Find an element with exact text
273
+ * @param {string} text
274
+ * @returns {Locator}
275
+ */
276
+ withTextEquals(text) {
277
+ text = xpathLocator.literal(text);
278
+ const xpath = this.toXPath(`:text-case(${text})`);
252
279
  return new Locator({ xpath });
253
280
  }
254
281
 
@@ -1,6 +1,7 @@
1
1
  const event = require('../event');
2
2
  const recorder = require('../recorder');
3
3
  const container = require('../container');
4
+ const { log } = require('../output');
4
5
 
5
6
  const defaultConfig = {
6
7
  retries: 3,
@@ -99,7 +100,10 @@ module.exports = (config) => {
99
100
  config.when = when;
100
101
 
101
102
  event.dispatcher.on(event.step.started, (step) => {
102
- if (process.env.TRY_TO) return;
103
+ if (process.env.TRY_TO === 'true') {
104
+ log('Info: RetryFailedStep plugin is disabled inside tryTo block');
105
+ return;
106
+ }
103
107
 
104
108
  // if a step is ignored - return
105
109
  for (const ignored of config.ignoredSteps) {
@@ -89,9 +89,9 @@ module.exports = function (config) {
89
89
  let err = null;
90
90
 
91
91
  return new Promise((done) => {
92
- const tryBlock = () => {
92
+ const tryBlock = async () => {
93
93
  recorder.session.start(`retryTo ${tries}`);
94
- callback(tries);
94
+ await callback(tries);
95
95
  recorder.add(() => {
96
96
  recorder.session.restore(`retryTo ${tries}`);
97
97
  done(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.5.11",
3
+ "version": "3.5.12-beta.02",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -52,8 +52,11 @@
52
52
  "test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js",
53
53
  "test:unit:webbapi:puppeteer": "mocha test/helper/Puppeteer_test.js",
54
54
  "test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js",
55
+ "test:unit:webbapi:webDriver:noSeleniumServer": "mocha test/helper/WebDriver.noSeleniumServer_test.js",
56
+ "test:unit:webbapi:webDriver:devtools": "mocha test/helper/WebDriver_devtools_test.js --exit",
55
57
  "test:unit:webbapi:testCafe": "mocha test/helper/TestCafe_test.js",
56
58
  "test:unit:expect": "mocha test/helper/Expect_test.js",
59
+ "test:plugin": "mocha test/plugin/plugin_test.js",
57
60
  "def": "./runok.js def",
58
61
  "dev:graphql": "node test/data/graphql/index.js",
59
62
  "publish:site": "./runok.js publish:site",
@@ -67,29 +70,31 @@
67
70
  "@codeceptjs/helper": "2.0.1",
68
71
  "@cucumber/cucumber-expressions": "17",
69
72
  "@cucumber/gherkin": "26",
70
- "@cucumber/messages": "22.0.0",
73
+ "@cucumber/messages": "24.0.1",
71
74
  "@xmldom/xmldom": "0.8.10",
72
75
  "acorn": "8.11.2",
73
76
  "arrify": "2.0.1",
74
77
  "axios": "1.6.3",
78
+ "bo-selector": "0.0.10",
75
79
  "chai": "4.3.8",
76
80
  "chai-deep-match": "1.2.1",
77
- "chai-exclude": "^2.1.0",
78
- "chai-json-schema": "^1.5.1",
79
- "chai-json-schema-ajv": "^5.2.4",
80
- "chai-match-pattern": "^1.3.0",
81
- "chai-string": "^1.5.0",
81
+ "chai-exclude": "2.1.0",
82
+ "chai-json-schema": "1.5.1",
83
+ "chai-json-schema-ajv": "5.2.4",
84
+ "chai-match-pattern": "1.3.0",
85
+ "chai-string": "1.5.0",
82
86
  "chalk": "4.1.2",
83
87
  "commander": "11.1.0",
84
- "convert-cssxpath": "1.0.2",
85
88
  "cross-spawn": "7.0.3",
89
+ "csstoxpath": "1.6.0",
90
+ "devtools": "8.27.2",
86
91
  "envinfo": "7.11.0",
87
92
  "escape-string-regexp": "4.0.0",
88
93
  "figures": "3.2.0",
89
94
  "fn-args": "4.0.0",
90
95
  "fs-extra": "11.2.0",
91
96
  "glob": "6.0.1",
92
- "html-minifier-terser": "^7.2.0",
97
+ "html-minifier-terser": "7.2.0",
93
98
  "inquirer": "6.5.2",
94
99
  "joi": "17.11.0",
95
100
  "js-beautify": "1.14.11",
@@ -105,7 +110,8 @@
105
110
  "promise-retry": "1.1.1",
106
111
  "resq": "1.11.0",
107
112
  "sprintf-js": "1.1.1",
108
- "uuid": "9.0"
113
+ "uuid": "9.0",
114
+ "xpath-builder": "0.0.7"
109
115
  },
110
116
  "optionalDependencies": {
111
117
  "@codeceptjs/detox-helper": "1.0.2"
@@ -115,12 +121,13 @@
115
121
  "@faker-js/faker": "7.6.0",
116
122
  "@pollyjs/adapter-puppeteer": "6.0.6",
117
123
  "@pollyjs/core": "5.1.0",
118
- "@types/chai": "^4.3.7",
124
+ "@types/chai": "4.3.7",
119
125
  "@types/inquirer": "9.0.3",
120
- "@types/node": "20.4.4",
126
+ "@types/node": "20.10.7",
121
127
  "@wdio/sauce-service": "8.27.0",
122
128
  "@wdio/selenium-standalone-service": "8.3.2",
123
- "@wdio/utils": "8.27.0",
129
+ "@wdio/utils": "8.27.2",
130
+ "@xmldom/xmldom": "0.8.10",
124
131
  "apollo-server-express": "2.25.3",
125
132
  "chai-as-promised": "7.1.1",
126
133
  "chai-subset": "1.6.0",
@@ -130,7 +137,7 @@
130
137
  "electron": "28.0.0",
131
138
  "eslint": "8.56.0",
132
139
  "eslint-config-airbnb-base": "15.0.0",
133
- "eslint-plugin-import": "2.29.0",
140
+ "eslint-plugin-import": "2.29.1",
134
141
  "eslint-plugin-mocha": "6.3.0",
135
142
  "expect": "29.7.0",
136
143
  "express": "4.18.2",
@@ -143,7 +150,7 @@
143
150
  "playwright": "1.40.1",
144
151
  "puppeteer": "21.1.1",
145
152
  "qrcode-terminal": "0.12.0",
146
- "rosie": "2.1.0",
153
+ "rosie": "2.1.1",
147
154
  "runok": "0.9.3",
148
155
  "sinon": "17.0.1",
149
156
  "sinon-chai": "3.7.0",
@@ -151,13 +158,12 @@
151
158
  "ts-morph": "21.0.1",
152
159
  "ts-node": "10.9.2",
153
160
  "tsd-jsdoc": "2.5.0",
154
- "typedoc": "0.25.4",
161
+ "typedoc": "0.25.7",
155
162
  "typedoc-plugin-markdown": "3.17.1",
156
163
  "typescript": "5.3.3",
157
164
  "wdio-docker-service": "1.5.0",
158
- "webdriverio": "8.3.8",
165
+ "webdriverio": "8.27.2",
159
166
  "xml2js": "0.6.2",
160
- "@xmldom/xmldom": "0.8.10",
161
167
  "xpath": "0.0.34"
162
168
  },
163
169
  "engines": {
@@ -97,7 +97,7 @@ declare namespace CodeceptJS {
97
97
  * }
98
98
  * }
99
99
  * ```
100
- */
100
+ */
101
101
  helpers?: {
102
102
  /**
103
103
  * Run web tests controlling browsers via Playwright engine.
@@ -289,7 +289,7 @@ declare namespace CodeceptJS {
289
289
  * ```js
290
290
  * bootstrap: 'bootstrap.js',
291
291
  * ```
292
- */
292
+ */
293
293
  bootstrap?: (() => Promise<void>) | boolean | string;
294
294
  /**
295
295
  * [Execute code after tests](https://codecept.io/bootstrap/) finished.
@@ -303,7 +303,7 @@ declare namespace CodeceptJS {
303
303
  * ```js
304
304
  * teardown: 'teardown.js',
305
305
  * ```
306
- */
306
+ */
307
307
  teardown?: (() => Promise<void>) | boolean | string;
308
308
  /**
309
309
  * [Execute code before launching tests in parallel mode](https://codecept.io/bootstrap/#bootstrapall-teardownall)
@@ -312,7 +312,7 @@ declare namespace CodeceptJS {
312
312
  bootstrapAll?: (() => Promise<void>) | boolean | string;
313
313
  /**
314
314
  * [Execute JS code after finishing tests in parallel mode](https://codecept.io/bootstrap/#bootstrapall-teardownall)
315
- */
315
+ */
316
316
  teardownAll?: (() => Promise<void>) | boolean | string;
317
317
 
318
318
  /** Enable [localized test commands](https://codecept.io/translation/) */
@@ -328,7 +328,7 @@ declare namespace CodeceptJS {
328
328
  * ```
329
329
  * require: ["should"]
330
330
  * ```
331
- */
331
+ */
332
332
  require?: Array<string>;
333
333
 
334
334
  /**
@@ -423,15 +423,18 @@ declare namespace CodeceptJS {
423
423
  | { ios: string }
424
424
  | { android: string; ios: string }
425
425
  | { react: string }
426
+ | { vue: string }
426
427
  | { shadow: string[] }
427
428
  | { custom: string };
428
429
 
429
430
  interface CustomLocators {}
431
+ interface OtherLocators { props?: object }
430
432
  type LocatorOrString =
431
433
  | string
432
434
  | ILocator
433
435
  | Locator
434
- | CustomLocators;
436
+ | OtherLocators
437
+ | CustomLocators[keyof CustomLocators];
435
438
 
436
439
  type StringOrSecret = string | CodeceptJS.Secret;
437
440