codeceptjs 3.7.6-beta.2 → 3.7.6-beta.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.
package/lib/event.js CHANGED
@@ -2,13 +2,22 @@ const debug = require('debug')('codeceptjs:event')
2
2
  const events = require('events')
3
3
  const { error } = require('./output')
4
4
 
5
+ const MAX_LISTENERS = 200
6
+
5
7
  const dispatcher = new events.EventEmitter()
6
8
 
7
- dispatcher.setMaxListeners(50)
9
+ dispatcher.setMaxListeners(MAX_LISTENERS)
10
+
11
+ // Increase process max listeners to prevent warnings for beforeExit and other events
12
+ if (typeof process.setMaxListeners === 'function') {
13
+ process.setMaxListeners(MAX_LISTENERS)
14
+ }
15
+
8
16
  /**
9
17
  * @namespace
10
18
  * @alias event
11
19
  */
20
+
12
21
  module.exports = {
13
22
  /**
14
23
  * @type {NodeJS.EventEmitter}
@@ -391,6 +391,29 @@ class Appium extends Webdriver {
391
391
  return `${protocol}://${hostname}:${port}${normalizedPath}/session/${this.browser.sessionId}`
392
392
  }
393
393
 
394
+ /**
395
+ * Helper method to safely call isDisplayed() on mobile elements.
396
+ * Handles the case where webdriverio tries to use execute/sync which isn't supported in Appium.
397
+ * @private
398
+ */
399
+ async _isDisplayedSafe(element) {
400
+ if (this.isWeb) {
401
+ // For web contexts, use the normal isDisplayed
402
+ return element.isDisplayed()
403
+ }
404
+
405
+ try {
406
+ return await element.isDisplayed()
407
+ } catch (err) {
408
+ // If isDisplayed fails due to execute/sync not being supported in native mobile contexts,
409
+ // fall back to assuming the element is displayed (since we found it)
410
+ if (err.message && err.message.includes('Method is not implemented')) {
411
+ return true
412
+ }
413
+ throw err
414
+ }
415
+ }
416
+
394
417
  /**
395
418
  * Execute code only on iOS
396
419
  *
@@ -619,6 +642,7 @@ class Appium extends Webdriver {
619
642
  */
620
643
  async resetApp() {
621
644
  onlyForApps.call(this)
645
+ this.isWeb = false // Reset to native context after app reset
622
646
  return this.axios({
623
647
  method: 'post',
624
648
  url: `${this._buildAppiumEndpoint()}/appium/app/reset`,
@@ -1134,7 +1158,7 @@ class Appium extends Webdriver {
1134
1158
  ],
1135
1159
  },
1136
1160
  ])
1137
- await this.browser.pause(1000)
1161
+ await this.browser.pause(2000)
1138
1162
  }
1139
1163
 
1140
1164
  /**
@@ -1296,28 +1320,26 @@ class Appium extends Webdriver {
1296
1320
  let currentSource
1297
1321
  return browser
1298
1322
  .waitUntil(
1299
- () => {
1323
+ async () => {
1300
1324
  if (err) {
1301
1325
  return new Error(`Scroll to the end and element ${searchableLocator} was not found`)
1302
1326
  }
1303
- return browser
1304
- .$$(parseLocator.call(this, searchableLocator))
1305
- .then(els => els.length && els[0].isDisplayed())
1306
- .then(res => {
1307
- if (res) {
1308
- return true
1309
- }
1310
- return this[direction](scrollLocator, offset, speed)
1311
- .getSource()
1312
- .then(source => {
1313
- if (source === currentSource) {
1314
- err = true
1315
- } else {
1316
- currentSource = source
1317
- return false
1318
- }
1319
- })
1320
- })
1327
+ const els = await browser.$$(parseLocator.call(this, searchableLocator))
1328
+ if (els.length) {
1329
+ const displayed = await this._isDisplayedSafe(els[0])
1330
+ if (displayed) {
1331
+ return true
1332
+ }
1333
+ }
1334
+
1335
+ await this[direction](scrollLocator, offset, speed)
1336
+ const source = await this.browser.getPageSource()
1337
+ if (source === currentSource) {
1338
+ err = true
1339
+ } else {
1340
+ currentSource = source
1341
+ return false
1342
+ }
1321
1343
  },
1322
1344
  timeout * 1000,
1323
1345
  errorMsg,
@@ -1523,7 +1545,28 @@ class Appium extends Webdriver {
1523
1545
  */
1524
1546
  async dontSeeElement(locator) {
1525
1547
  if (this.isWeb) return super.dontSeeElement(locator)
1526
- return super.dontSeeElement(parseLocator.call(this, locator))
1548
+
1549
+ // For mobile native apps, use safe isDisplayed wrapper
1550
+ const parsedLocator = parseLocator.call(this, locator)
1551
+ const res = await this._locate(parsedLocator, false)
1552
+ const { truth } = require('../assert/truth')
1553
+ const Locator = require('../locator')
1554
+
1555
+ if (!res || res.length === 0) {
1556
+ return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(false)
1557
+ }
1558
+
1559
+ const selected = []
1560
+ for (const el of res) {
1561
+ const displayed = await this._isDisplayedSafe(el)
1562
+ if (displayed) selected.push(true)
1563
+ }
1564
+
1565
+ try {
1566
+ return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(selected)
1567
+ } catch (err) {
1568
+ throw err
1569
+ }
1527
1570
  }
1528
1571
 
1529
1572
  /**
@@ -1577,7 +1620,18 @@ class Appium extends Webdriver {
1577
1620
  */
1578
1621
  async grabNumberOfVisibleElements(locator) {
1579
1622
  if (this.isWeb) return super.grabNumberOfVisibleElements(locator)
1580
- return super.grabNumberOfVisibleElements(parseLocator.call(this, locator))
1623
+
1624
+ // For mobile native apps, use safe isDisplayed wrapper
1625
+ const parsedLocator = parseLocator.call(this, locator)
1626
+ const res = await this._locate(parsedLocator)
1627
+
1628
+ const selected = []
1629
+ for (const el of res) {
1630
+ const displayed = await this._isDisplayedSafe(el)
1631
+ if (displayed) selected.push(true)
1632
+ }
1633
+
1634
+ return selected.length
1581
1635
  }
1582
1636
 
1583
1637
  /**
@@ -1656,7 +1710,30 @@ class Appium extends Webdriver {
1656
1710
  */
1657
1711
  async seeElement(locator) {
1658
1712
  if (this.isWeb) return super.seeElement(locator)
1659
- return super.seeElement(parseLocator.call(this, locator))
1713
+
1714
+ // For mobile native apps, use safe isDisplayed wrapper
1715
+ const parsedLocator = parseLocator.call(this, locator)
1716
+ const res = await this._locate(parsedLocator, true)
1717
+ const ElementNotFound = require('./errors/ElementNotFound')
1718
+ const { truth } = require('../assert/truth')
1719
+ const { dontSeeElementError } = require('./errors/ElementAssertion')
1720
+ const Locator = require('../locator')
1721
+
1722
+ if (!res || res.length === 0) {
1723
+ throw new ElementNotFound(parsedLocator)
1724
+ }
1725
+
1726
+ const selected = []
1727
+ for (const el of res) {
1728
+ const displayed = await this._isDisplayedSafe(el)
1729
+ if (displayed) selected.push(true)
1730
+ }
1731
+
1732
+ try {
1733
+ return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').assert(selected)
1734
+ } catch (e) {
1735
+ dontSeeElementError(parsedLocator)
1736
+ }
1660
1737
  }
1661
1738
 
1662
1739
  /**
@@ -1703,7 +1780,30 @@ class Appium extends Webdriver {
1703
1780
  */
1704
1781
  async waitForVisible(locator, sec = null) {
1705
1782
  if (this.isWeb) return super.waitForVisible(locator, sec)
1706
- return super.waitForVisible(parseLocator.call(this, locator), sec)
1783
+
1784
+ // For mobile native apps, use safe isDisplayed wrapper
1785
+ const parsedLocator = parseLocator.call(this, locator)
1786
+ const aSec = sec || this.options.waitForTimeoutInSeconds
1787
+ const Locator = require('../locator')
1788
+
1789
+ return this.browser.waitUntil(
1790
+ async () => {
1791
+ const res = await this._res(parsedLocator)
1792
+ if (!res || res.length === 0) return false
1793
+
1794
+ const selected = []
1795
+ for (const el of res) {
1796
+ const displayed = await this._isDisplayedSafe(el)
1797
+ if (displayed) selected.push(true)
1798
+ }
1799
+
1800
+ return selected.length > 0
1801
+ },
1802
+ {
1803
+ timeout: aSec * 1000,
1804
+ timeoutMsg: `element (${new Locator(parsedLocator)}) still not visible after ${aSec} sec`,
1805
+ },
1806
+ )
1707
1807
  }
1708
1808
 
1709
1809
  /**
@@ -1712,7 +1812,27 @@ class Appium extends Webdriver {
1712
1812
  */
1713
1813
  async waitForInvisible(locator, sec = null) {
1714
1814
  if (this.isWeb) return super.waitForInvisible(locator, sec)
1715
- return super.waitForInvisible(parseLocator.call(this, locator), sec)
1815
+
1816
+ // For mobile native apps, use safe isDisplayed wrapper
1817
+ const parsedLocator = parseLocator.call(this, locator)
1818
+ const aSec = sec || this.options.waitForTimeoutInSeconds
1819
+ const Locator = require('../locator')
1820
+
1821
+ return this.browser.waitUntil(
1822
+ async () => {
1823
+ const res = await this._res(parsedLocator)
1824
+ if (!res || res.length === 0) return true
1825
+
1826
+ const selected = []
1827
+ for (const el of res) {
1828
+ const displayed = await this._isDisplayedSafe(el)
1829
+ if (displayed) selected.push(true)
1830
+ }
1831
+
1832
+ return selected.length === 0
1833
+ },
1834
+ { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(parsedLocator)}) still visible after ${aSec} sec` },
1835
+ )
1716
1836
  }
1717
1837
 
1718
1838
  /**
@@ -72,11 +72,11 @@ class JSONResponse extends Helper {
72
72
  if (!this.helpers[this.options.requestHelper]) {
73
73
  throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`)
74
74
  }
75
- const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse;
75
+ const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse
76
76
  this.helpers[this.options.requestHelper].config.onResponse = response => {
77
- this.response = response;
78
- if (typeof origOnResponse === 'function') origOnResponse(response);
79
- };
77
+ this.response = response
78
+ if (typeof origOnResponse === 'function') origOnResponse(response)
79
+ }
80
80
  }
81
81
 
82
82
  _before() {
@@ -370,7 +370,6 @@ class Playwright extends Helper {
370
370
  if (typeof config.storageState !== 'undefined') {
371
371
  this.storageState = config.storageState
372
372
  }
373
-
374
373
  }
375
374
 
376
375
  _validateConfig(config) {
@@ -1844,7 +1843,7 @@ class Playwright extends Helper {
1844
1843
 
1845
1844
  /**
1846
1845
  *
1847
- * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/Puppeteer#1313](https://github.com/GoogleChrome/puppeteer/issues/1313)).
1846
+ * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([puppeteer/puppeteer#1313](https://github.com/puppeteer/puppeteer/issues/1313)).
1848
1847
  *
1849
1848
  * {{> pressKeyWithKeyNormalization }}
1850
1849
  */
@@ -3191,7 +3190,7 @@ class Playwright extends Helper {
3191
3190
  /**
3192
3191
  * Waits for navigation to finish. By default, it takes configured `waitForNavigation` option.
3193
3192
  *
3194
- * See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
3193
+ * See [Playwright's reference](https://playwright.dev/docs/api/class-page#page-wait-for-navigation)
3195
3194
  *
3196
3195
  * @param {*} options
3197
3196
  */
@@ -64,7 +64,7 @@ const consoleLogStore = new Console()
64
64
  * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to false.
65
65
  * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to false.
66
66
  * @prop {number} [waitForAction=100] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
67
- * @prop {string} [waitForNavigation=load] - when to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions). Array values are accepted as well.
67
+ * @prop {string|string[]} [waitForNavigation=load] - when to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API](https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.waitforoptions.md). Array values are accepted as well.
68
68
  * @prop {number} [pressKeyDelay=10] - delay between key presses in ms. Used when calling Puppeteers page.type(...) in fillField/appendField
69
69
  * @prop {number} [getPageTimeout=30000] - config option to set maximum navigation time in milliseconds. If the timeout is set to 0, then timeout will be disabled.
70
70
  * @prop {number} [waitForTimeout=1000] - default wait* timeout in ms.
@@ -72,13 +72,13 @@ const consoleLogStore = new Console()
72
72
  * @prop {string} [userAgent] - user-agent string.
73
73
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
74
74
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
75
- * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
75
+ * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.launchoptions.md).
76
76
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
77
77
  */
78
78
  const config = {}
79
79
 
80
80
  /**
81
- * Uses [Google Chrome's Puppeteer](https://github.com/GoogleChrome/puppeteer) library to run tests inside headless Chrome.
81
+ * Uses [Google Chrome's Puppeteer](https://github.com/puppeteer/puppeteer) library to run tests inside headless Chrome.
82
82
  * Browser control is executed via DevTools Protocol (instead of Selenium).
83
83
  * This helper works with a browser out of the box with no additional tools required to install.
84
84
  *
@@ -1349,7 +1349,7 @@ class Puppeteer extends Helper {
1349
1349
  }
1350
1350
 
1351
1351
  /**
1352
- * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313](https://github.com/GoogleChrome/puppeteer/issues/1313)).
1352
+ * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([puppeteer/puppeteer#1313](https://github.com/puppeteer/puppeteer/issues/1313)).
1353
1353
  *
1354
1354
  * {{> pressKeyWithKeyNormalization }}
1355
1355
  */
@@ -2463,7 +2463,7 @@ class Puppeteer extends Helper {
2463
2463
  /**
2464
2464
  * Waits for navigation to finish. By default, takes configured `waitForNavigation` option.
2465
2465
  *
2466
- * See [Puppeteer's reference](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions)
2466
+ * See [Puppeteer's reference](https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.page.waitfornavigation.md)
2467
2467
  *
2468
2468
  * @param {*} opts
2469
2469
  */
@@ -3093,7 +3093,7 @@ async function getClickablePoint(el) {
3093
3093
  }
3094
3094
 
3095
3095
  // List of key values to key definitions
3096
- // https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
3096
+ // https://github.com/puppeteer/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
3097
3097
  const keyDefinitionMap = {
3098
3098
  0: 'Digit0',
3099
3099
  1: 'Digit1',
@@ -998,7 +998,7 @@ class WebDriver extends Helper {
998
998
  * {{ react }}
999
999
  */
1000
1000
  async click(locator, context = null) {
1001
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1001
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1002
1002
  const locateFn = prepareLocateFn.call(this, context)
1003
1003
 
1004
1004
  const res = await findClickable.call(this, locator, locateFn)
@@ -1217,7 +1217,7 @@ class WebDriver extends Helper {
1217
1217
  * {{> checkOption }}
1218
1218
  */
1219
1219
  async checkOption(field, context = null) {
1220
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1220
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1221
1221
  const locateFn = prepareLocateFn.call(this, context)
1222
1222
 
1223
1223
  const res = await findCheckable.call(this, field, locateFn)
@@ -1237,7 +1237,7 @@ class WebDriver extends Helper {
1237
1237
  * {{> uncheckOption }}
1238
1238
  */
1239
1239
  async uncheckOption(field, context = null) {
1240
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1240
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1241
1241
  const locateFn = prepareLocateFn.call(this, context)
1242
1242
 
1243
1243
  const res = await findCheckable.call(this, field, locateFn)
@@ -79,14 +79,14 @@ module.exports = function () {
79
79
  return currentHook.steps.push(step)
80
80
  }
81
81
  if (!currentTest || !currentTest.steps) return
82
-
82
+
83
83
  // Check if we're in a session that should be excluded from main test steps
84
84
  const currentSessionId = recorder.getCurrentSessionId()
85
85
  if (currentSessionId && EXCLUDED_SESSIONS.includes(currentSessionId)) {
86
86
  // Skip adding this step to the main test steps
87
87
  return
88
88
  }
89
-
89
+
90
90
  currentTest.steps.push(step)
91
91
  })
92
92
 
package/lib/output.js CHANGED
@@ -50,7 +50,40 @@ module.exports = {
50
50
  */
51
51
  process(process) {
52
52
  if (process === null) return (outputProcess = '')
53
- if (process) outputProcess = String(process).length === 1 ? `[0${process}]` : `[${process}]`
53
+ if (process) {
54
+ // Handle objects by converting to empty string or extracting properties
55
+ let processValue = process
56
+ if (typeof process === 'object') {
57
+ // If it's an object, try to extract a numeric value or use empty string
58
+ processValue = process.id || process.index || process.worker || ''
59
+ }
60
+
61
+ // Check if this is a run-multiple process (contains : or .)
62
+ // Format: "1.runName:browserName" from run-multiple
63
+ if (String(processValue).includes(':') || (String(processValue).includes('.') && String(processValue).split('.').length > 1)) {
64
+ // Keep original format for run-multiple
65
+ outputProcess = colors.cyan.bold(`[${processValue}]`)
66
+ } else {
67
+ // Standard worker format for run-workers
68
+ const processNum = parseInt(processValue, 10)
69
+ const processStr = !isNaN(processNum) ? String(processNum).padStart(2, '0') : String(processValue).padStart(2, '0')
70
+
71
+ // Assign different colors to different workers for better identification
72
+ const workerColors = [
73
+ colors.cyan, // Worker 01 - Cyan
74
+ colors.magenta, // Worker 02 - Magenta
75
+ colors.green, // Worker 03 - Green
76
+ colors.yellow, // Worker 04 - Yellow
77
+ colors.blue, // Worker 05 - Blue
78
+ colors.red, // Worker 06 - Red
79
+ colors.white, // Worker 07 - White
80
+ colors.gray, // Worker 08 - Gray
81
+ ]
82
+ const workerIndex = !isNaN(processNum) ? processNum - 1 : -1
83
+ const colorFn = workerIndex >= 0 && workerColors[workerIndex % workerColors.length] ? workerColors[workerIndex % workerColors.length] : colors.cyan
84
+ outputProcess = colorFn.bold(`[Worker ${processStr}]`)
85
+ }
86
+ }
54
87
  return outputProcess
55
88
  },
56
89
 
@@ -149,25 +182,38 @@ module.exports = {
149
182
  * @param {Mocha.Test} test
150
183
  */
151
184
  started(test) {
152
- print(` ${colors.magenta.bold(test.title)}`)
185
+ // Only show feature name in workers mode (when outputProcess is set)
186
+ const featureName = outputProcess && test.parent?.title ? `${colors.cyan.bold(test.parent.title)} › ` : ''
187
+ print(` ${featureName}${colors.magenta.bold(test.title)}`)
153
188
  },
154
189
  /**
155
190
  * @param {Mocha.Test} test
156
191
  */
157
192
  passed(test) {
158
- print(` ${colors.green.bold(figures.tick)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`)
193
+ // Only show feature name in workers mode (when outputProcess is set)
194
+ const featureName = outputProcess && test.parent?.title ? `${colors.cyan(test.parent.title)} › ` : ''
195
+ const scenarioName = colors.bold(test.title)
196
+ const executionTime = colors.cyan(`in ${test.duration}ms`)
197
+ print(` ${colors.green.bold(figures.tick)} ${featureName}${scenarioName} ${executionTime}`)
159
198
  },
160
199
  /**
161
200
  * @param {Mocha.Test} test
162
201
  */
163
202
  failed(test) {
164
- print(` ${colors.red.bold(figures.cross)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`)
203
+ // Only show feature name in workers mode (when outputProcess is set)
204
+ const featureName = outputProcess && test.parent?.title ? `${colors.yellow(test.parent.title)} › ` : ''
205
+ const scenarioName = colors.bold(test.title)
206
+ const executionTime = colors.yellow(`in ${test.duration}ms`)
207
+ print(` ${colors.red.bold(figures.cross)} ${featureName}${scenarioName} ${executionTime}`)
165
208
  },
166
209
  /**
167
210
  * @param {Mocha.Test} test
168
211
  */
169
212
  skipped(test) {
170
- print(` ${colors.yellow.bold('S')} ${test.title}`)
213
+ // Only show feature name in workers mode (when outputProcess is set)
214
+ const featureName = outputProcess && test.parent?.title ? `${colors.gray(test.parent.title)} › ` : ''
215
+ const scenarioName = colors.bold(test.title)
216
+ print(` ${colors.yellow.bold('S')} ${featureName}${scenarioName}`)
171
217
  },
172
218
  },
173
219