codeceptjs 3.7.6-beta.1 → 3.7.6-beta.3

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/codecept.js CHANGED
@@ -122,6 +122,7 @@ class Codecept {
122
122
  /**
123
123
  * Executes bootstrap.
124
124
  *
125
+ * @returns {Promise<void>}
125
126
  */
126
127
  async bootstrap() {
127
128
  return runHook(this.config.bootstrap, 'bootstrap')
@@ -129,7 +130,8 @@ class Codecept {
129
130
 
130
131
  /**
131
132
  * Executes teardown.
132
-
133
+ *
134
+ * @returns {Promise<void>}
133
135
  */
134
136
  async teardown() {
135
137
  return runHook(this.config.teardown, 'teardown')
@@ -262,6 +264,11 @@ class Codecept {
262
264
  })
263
265
  }
264
266
 
267
+ /**
268
+ * Returns the version string of CodeceptJS.
269
+ *
270
+ * @returns {string} The version string.
271
+ */
265
272
  static version() {
266
273
  return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
267
274
  }
@@ -23,11 +23,49 @@ const Codecept = require(process.env.CODECEPT_CLASS_PATH || '../../codecept')
23
23
  const { options, tests, testRoot, workerIndex, poolMode } = workerData
24
24
 
25
25
  // hide worker output
26
- if (!options.debug && !options.verbose)
26
+ // In pool mode, only suppress output if debug is NOT enabled
27
+ // In regular mode, hide result output but allow step output in verbose/debug
28
+ if (poolMode && !options.debug) {
29
+ // In pool mode without debug, suppress only result summaries and failures, but allow Scenario Steps
30
+ const originalWrite = process.stdout.write
31
+ process.stdout.write = string => {
32
+ // Always allow Scenario Steps output (including the circle symbol)
33
+ if (string.includes('Scenario Steps:') || string.includes('◯ Scenario Steps:')) {
34
+ return originalWrite.call(process.stdout, string)
35
+ }
36
+ if (string.includes(' FAIL |') || string.includes(' OK |') || string.includes('-- FAILURES:') || string.includes('AssertionError:') || string.includes('◯ File:')) {
37
+ return true
38
+ }
39
+ return originalWrite.call(process.stdout, string)
40
+ }
41
+ } else if (!poolMode && !options.debug && !options.verbose) {
27
42
  process.stdout.write = string => {
28
43
  stdout += string
29
44
  return true
30
45
  }
46
+ } else {
47
+ // In verbose/debug mode for test/suite modes, show step details
48
+ // but suppress individual worker result summaries to avoid duplicate output
49
+ const originalWrite = process.stdout.write
50
+ const originalConsoleLog = console.log
51
+
52
+ process.stdout.write = string => {
53
+ // Suppress individual worker result summaries and failure reports
54
+ if (string.includes(' FAIL |') || string.includes(' OK |') || string.includes('-- FAILURES:') || string.includes('AssertionError:') || string.includes('◯ File:') || string.includes('◯ Scenario Steps:')) {
55
+ return true
56
+ }
57
+ return originalWrite.call(process.stdout, string)
58
+ }
59
+
60
+ // Override console.log to catch result summaries
61
+ console.log = (...args) => {
62
+ const fullMessage = args.join(' ')
63
+ if (fullMessage.includes(' FAIL |') || fullMessage.includes(' OK |') || fullMessage.includes('-- FAILURES:')) {
64
+ return
65
+ }
66
+ return originalConsoleLog.apply(console, args)
67
+ }
68
+ }
31
69
 
32
70
  const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
33
71
 
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}
@@ -261,11 +261,13 @@ class Appium extends Webdriver {
261
261
 
262
262
  this.platform = null
263
263
  if (config.capabilities[`${vendorPrefix.appium}:platformName`]) {
264
- this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase()
264
+ config.capabilities[`${vendorPrefix.appium}:platformName`] = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase()
265
+ this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`]
265
266
  }
266
267
 
267
268
  if (config.capabilities.platformName) {
268
- this.platform = config.capabilities.platformName.toLowerCase()
269
+ config.capabilities.platformName = config.capabilities.platformName.toLowerCase()
270
+ this.platform = config.capabilities.platformName
269
271
  }
270
272
 
271
273
  return config
@@ -389,6 +391,29 @@ class Appium extends Webdriver {
389
391
  return `${protocol}://${hostname}:${port}${normalizedPath}/session/${this.browser.sessionId}`
390
392
  }
391
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
+
392
417
  /**
393
418
  * Execute code only on iOS
394
419
  *
@@ -617,6 +642,7 @@ class Appium extends Webdriver {
617
642
  */
618
643
  async resetApp() {
619
644
  onlyForApps.call(this)
645
+ this.isWeb = false // Reset to native context after app reset
620
646
  return this.axios({
621
647
  method: 'post',
622
648
  url: `${this._buildAppiumEndpoint()}/appium/app/reset`,
@@ -1132,7 +1158,7 @@ class Appium extends Webdriver {
1132
1158
  ],
1133
1159
  },
1134
1160
  ])
1135
- await this.browser.pause(1000)
1161
+ await this.browser.pause(2000)
1136
1162
  }
1137
1163
 
1138
1164
  /**
@@ -1294,28 +1320,26 @@ class Appium extends Webdriver {
1294
1320
  let currentSource
1295
1321
  return browser
1296
1322
  .waitUntil(
1297
- () => {
1323
+ async () => {
1298
1324
  if (err) {
1299
1325
  return new Error(`Scroll to the end and element ${searchableLocator} was not found`)
1300
1326
  }
1301
- return browser
1302
- .$$(parseLocator.call(this, searchableLocator))
1303
- .then(els => els.length && els[0].isDisplayed())
1304
- .then(res => {
1305
- if (res) {
1306
- return true
1307
- }
1308
- return this[direction](scrollLocator, offset, speed)
1309
- .getSource()
1310
- .then(source => {
1311
- if (source === currentSource) {
1312
- err = true
1313
- } else {
1314
- currentSource = source
1315
- return false
1316
- }
1317
- })
1318
- })
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
+ }
1319
1343
  },
1320
1344
  timeout * 1000,
1321
1345
  errorMsg,
@@ -1521,7 +1545,28 @@ class Appium extends Webdriver {
1521
1545
  */
1522
1546
  async dontSeeElement(locator) {
1523
1547
  if (this.isWeb) return super.dontSeeElement(locator)
1524
- 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
+ }
1525
1570
  }
1526
1571
 
1527
1572
  /**
@@ -1575,7 +1620,18 @@ class Appium extends Webdriver {
1575
1620
  */
1576
1621
  async grabNumberOfVisibleElements(locator) {
1577
1622
  if (this.isWeb) return super.grabNumberOfVisibleElements(locator)
1578
- 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
1579
1635
  }
1580
1636
 
1581
1637
  /**
@@ -1654,7 +1710,30 @@ class Appium extends Webdriver {
1654
1710
  */
1655
1711
  async seeElement(locator) {
1656
1712
  if (this.isWeb) return super.seeElement(locator)
1657
- 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
+ }
1658
1737
  }
1659
1738
 
1660
1739
  /**
@@ -1701,7 +1780,30 @@ class Appium extends Webdriver {
1701
1780
  */
1702
1781
  async waitForVisible(locator, sec = null) {
1703
1782
  if (this.isWeb) return super.waitForVisible(locator, sec)
1704
- 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
+ )
1705
1807
  }
1706
1808
 
1707
1809
  /**
@@ -1710,7 +1812,27 @@ class Appium extends Webdriver {
1710
1812
  */
1711
1813
  async waitForInvisible(locator, sec = null) {
1712
1814
  if (this.isWeb) return super.waitForInvisible(locator, sec)
1713
- 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
+ )
1714
1836
  }
1715
1837
 
1716
1838
  /**
@@ -98,7 +98,13 @@ const pathSeparator = path.sep
98
98
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
99
99
  * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
100
100
  * @prop {string} [testIdAttribute=data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id).
101
- * @prop {object} [customLocatorStrategies] - custom locator strategies. An object with keys as strategy names and values as JavaScript functions. Example: `{ byRole: (selector, root) => { return root.querySelector(\`[role="\${selector}\"]\`) } }`
101
+ * @prop {object} [customLocatorStrategies] - custom locator strategies. An object with keys as strategy names and values as JavaScript functions. Example: `{ byRole: (selector, root) => { return root.querySelector(`[role="${selector}"]`) } }`
102
+ * @prop {string|object} [storageState] - Playwright storage state (path to JSON file or object)
103
+ * passed directly to `browser.newContext`.
104
+ * If a Scenario is declared with a `cookies` option (e.g. `Scenario('name', { cookies: [...] }, fn)`),
105
+ * those cookies are used instead and the configured `storageState` is ignored (no merge).
106
+ * May include session cookies, auth tokens, localStorage and (if captured with
107
+ * `grabStorageState({ indexedDB: true })`) IndexedDB data; treat as sensitive and do not commit.
102
108
  */
103
109
  const config = {}
104
110
 
@@ -360,6 +366,10 @@ class Playwright extends Helper {
360
366
  // override defaults with config
361
367
  this._setConfig(config)
362
368
 
369
+ // pass storageState directly (string path or object) and let Playwright handle errors/missing file
370
+ if (typeof config.storageState !== 'undefined') {
371
+ this.storageState = config.storageState
372
+ }
363
373
  }
364
374
 
365
375
  _validateConfig(config) {
@@ -386,6 +396,7 @@ class Playwright extends Helper {
386
396
  use: { actionTimeout: 0 },
387
397
  ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors,
388
398
  highlightElement: false,
399
+ storageState: undefined,
389
400
  }
390
401
 
391
402
  process.env.testIdAttribute = 'data-testid'
@@ -589,8 +600,7 @@ class Playwright extends Helper {
589
600
 
590
601
  // load pre-saved cookies
591
602
  if (test?.opts?.cookies) contextOptions.storageState = { cookies: test.opts.cookies }
592
-
593
- if (this.storageState) contextOptions.storageState = this.storageState
603
+ else if (this.storageState) contextOptions.storageState = this.storageState
594
604
  if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent
595
605
  if (this.options.locale) contextOptions.locale = this.options.locale
596
606
  if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme
@@ -1833,7 +1843,7 @@ class Playwright extends Helper {
1833
1843
 
1834
1844
  /**
1835
1845
  *
1836
- * _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)).
1837
1847
  *
1838
1848
  * {{> pressKeyWithKeyNormalization }}
1839
1849
  */
@@ -2162,6 +2172,30 @@ class Playwright extends Helper {
2162
2172
  if (cookie[0]) return cookie[0]
2163
2173
  }
2164
2174
 
2175
+ /**
2176
+ * Grab the current storage state (cookies, localStorage, etc.) via Playwright's `browserContext.storageState()`.
2177
+ * Returns the raw object that Playwright provides.
2178
+ *
2179
+ * Security: The returned object can contain authentication tokens, session cookies
2180
+ * and (when `indexedDB: true` is used) data that may include user PII. Treat it as a secret.
2181
+ * Avoid committing it to source control and prefer storing it in a protected secrets store / CI artifact vault.
2182
+ *
2183
+ * @param {object} [options]
2184
+ * @param {boolean} [options.indexedDB] set to true to include IndexedDB in snapshot (Playwright >=1.51)
2185
+ *
2186
+ * ```js
2187
+ * // basic usage
2188
+ * const state = await I.grabStorageState();
2189
+ * require('fs').writeFileSync('authState.json', JSON.stringify(state));
2190
+ *
2191
+ * // include IndexedDB when using Firebase Auth, etc.
2192
+ * const stateWithIDB = await I.grabStorageState({ indexedDB: true });
2193
+ * ```
2194
+ */
2195
+ async grabStorageState(options = {}) {
2196
+ return this.browserContext.storageState(options)
2197
+ }
2198
+
2165
2199
  /**
2166
2200
  * {{> clearCookie }}
2167
2201
  */
@@ -3156,7 +3190,7 @@ class Playwright extends Helper {
3156
3190
  /**
3157
3191
  * Waits for navigation to finish. By default, it takes configured `waitForNavigation` option.
3158
3192
  *
3159
- * 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)
3160
3194
  *
3161
3195
  * @param {*} options
3162
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',
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