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 +8 -1
- package/lib/command/workers/runTests.js +39 -1
- package/lib/event.js +10 -1
- package/lib/helper/Appium.js +149 -27
- package/lib/helper/Playwright.js +39 -5
- package/lib/helper/Puppeteer.js +6 -6
- package/lib/output.js +51 -5
- package/lib/plugin/htmlReporter.js +31 -31
- package/lib/result.js +100 -23
- package/package.json +5 -5
- package/typings/index.d.ts +10 -11
- package/typings/promiseBasedTypes.d.ts +37 -17
- package/typings/types.d.ts +127 -22
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
|
|
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(
|
|
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}
|
package/lib/helper/Appium.js
CHANGED
|
@@ -261,11 +261,13 @@ class Appium extends Webdriver {
|
|
|
261
261
|
|
|
262
262
|
this.platform = null
|
|
263
263
|
if (config.capabilities[`${vendorPrefix.appium}:platformName`]) {
|
|
264
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -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(
|
|
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 ([
|
|
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
|
|
3193
|
+
* See [Playwright's reference](https://playwright.dev/docs/api/class-page#page-wait-for-navigation)
|
|
3160
3194
|
*
|
|
3161
3195
|
* @param {*} options
|
|
3162
3196
|
*/
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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 ([
|
|
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/
|
|
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/
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|