codeceptjs 3.7.6-beta.3 → 3.7.6
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 +15 -2
- package/lib/helper/JSONResponse.js +4 -4
- package/lib/helper/Playwright.js +5 -1
- package/lib/helper/WebDriver.js +3 -3
- package/lib/listener/globalTimeout.js +19 -4
- package/lib/listener/steps.js +2 -2
- package/lib/mocha/test.js +4 -2
- package/lib/output.js +2 -2
- package/lib/plugin/htmlReporter.js +855 -134
- package/lib/plugin/retryFailedStep.js +1 -0
- package/lib/result.js +8 -3
- package/lib/step/base.js +1 -1
- package/lib/step/meta.js +1 -1
- package/package.json +21 -18
- package/typings/types.d.ts +14 -3
package/lib/codecept.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const { existsSync, readFileSync } = require('fs')
|
|
2
2
|
const { globSync } = require('glob')
|
|
3
|
-
const shuffle = require('lodash.shuffle')
|
|
4
3
|
const fsPath = require('path')
|
|
5
4
|
const { resolve } = require('path')
|
|
6
5
|
|
|
@@ -185,8 +184,10 @@ class Codecept {
|
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
186
|
|
|
187
|
+
this.testFiles.sort()
|
|
188
|
+
|
|
188
189
|
if (this.opts.shuffle) {
|
|
189
|
-
this.testFiles = shuffle(this.testFiles)
|
|
190
|
+
this.testFiles = this.shuffle(this.testFiles)
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
if (this.opts.shard) {
|
|
@@ -194,6 +195,18 @@ class Codecept {
|
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Fisher–Yates shuffle (non-mutating)
|
|
200
|
+
*/
|
|
201
|
+
shuffle(array) {
|
|
202
|
+
const arr = [...array] // clone to avoid mutating input
|
|
203
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
204
|
+
const j = Math.floor(Math.random() * (i + 1))
|
|
205
|
+
;[arr[i], arr[j]] = [arr[j], arr[i]] // swap
|
|
206
|
+
}
|
|
207
|
+
return arr
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
/**
|
|
198
211
|
* Apply sharding to test files based on shard configuration
|
|
199
212
|
*
|
|
@@ -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() {
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -1876,11 +1876,15 @@ class Playwright extends Helper {
|
|
|
1876
1876
|
* {{> type }}
|
|
1877
1877
|
*/
|
|
1878
1878
|
async type(keys, delay = null) {
|
|
1879
|
+
// Always use page.keyboard.type for any string (including single character and national characters).
|
|
1879
1880
|
if (!Array.isArray(keys)) {
|
|
1880
1881
|
keys = keys.toString()
|
|
1881
|
-
|
|
1882
|
+
const typeDelay = typeof delay === 'number' ? delay : this.options.pressKeyDelay
|
|
1883
|
+
await this.page.keyboard.type(keys, { delay: typeDelay })
|
|
1884
|
+
return
|
|
1882
1885
|
}
|
|
1883
1886
|
|
|
1887
|
+
// For array input, treat each as a key press to keep working combinations such as ['Control', 'A'] or ['T', 'e', 's', 't'].
|
|
1884
1888
|
for (const key of keys) {
|
|
1885
1889
|
await this.page.keyboard.press(key)
|
|
1886
1890
|
if (delay) await this.wait(delay / 1000)
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -998,7 +998,7 @@ class WebDriver extends Helper {
|
|
|
998
998
|
* {{ react }}
|
|
999
999
|
*/
|
|
1000
1000
|
async click(locator, context = null) {
|
|
1001
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -20,14 +20,29 @@ module.exports = function () {
|
|
|
20
20
|
|
|
21
21
|
// disable timeout for BeforeSuite/AfterSuite hooks
|
|
22
22
|
// add separate configs to them?
|
|
23
|
+
// When a BeforeSuite/AfterSuite hook starts we want to disable the
|
|
24
|
+
// per-test timeout during that hook execution only. Previously the
|
|
25
|
+
// code cleared `suiteTimeout` permanently which caused the suite
|
|
26
|
+
// level timeout to be lost for subsequent tests. Save previous
|
|
27
|
+
// values and restore them when the hook finishes.
|
|
28
|
+
let __prevTimeout = undefined
|
|
29
|
+
let __prevSuiteTimeout = undefined
|
|
30
|
+
|
|
23
31
|
event.dispatcher.on(event.hook.started, hook => {
|
|
24
|
-
if (hook instanceof BeforeSuiteHook) {
|
|
32
|
+
if (hook instanceof BeforeSuiteHook || hook instanceof AfterSuiteHook) {
|
|
33
|
+
__prevTimeout = timeout
|
|
34
|
+
// copy array to preserve original values
|
|
35
|
+
__prevSuiteTimeout = suiteTimeout.slice()
|
|
25
36
|
timeout = null
|
|
26
37
|
suiteTimeout = []
|
|
27
38
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
event.dispatcher.on(event.hook.finished, hook => {
|
|
42
|
+
if (hook instanceof BeforeSuiteHook || hook instanceof AfterSuiteHook) {
|
|
43
|
+
// restore previously stored values
|
|
44
|
+
timeout = __prevTimeout
|
|
45
|
+
suiteTimeout = __prevSuiteTimeout.slice()
|
|
31
46
|
}
|
|
32
47
|
})
|
|
33
48
|
|
package/lib/listener/steps.js
CHANGED
|
@@ -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/mocha/test.js
CHANGED
|
@@ -153,14 +153,16 @@ function cloneTest(test) {
|
|
|
153
153
|
function testToFileName(test, { suffix = '', unique = false } = {}) {
|
|
154
154
|
let fileName = test.title
|
|
155
155
|
|
|
156
|
-
if (unique) fileName = `${fileName}_${test?.uid || Math.floor(new Date().getTime() / 1000)}`
|
|
157
|
-
if (suffix) fileName = `${fileName}_${suffix}`
|
|
158
156
|
// remove tags with empty string (disable for now)
|
|
159
157
|
// fileName = fileName.replace(/\@\w+/g, '')
|
|
160
158
|
fileName = fileName.slice(0, 100)
|
|
161
159
|
if (fileName.indexOf('{') !== -1) {
|
|
162
160
|
fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim()
|
|
163
161
|
}
|
|
162
|
+
|
|
163
|
+
// Apply unique suffix AFTER removing data part to ensure uniqueness
|
|
164
|
+
if (unique) fileName = `${fileName}_${test?.uid || Math.floor(new Date().getTime())}`
|
|
165
|
+
if (suffix) fileName = `${fileName}_${suffix}`
|
|
164
166
|
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`)
|
|
165
167
|
// TODO: add suite title to file name
|
|
166
168
|
// if (test.parent && test.parent.title) {
|
package/lib/output.js
CHANGED
|
@@ -222,12 +222,10 @@ module.exports = {
|
|
|
222
222
|
/**
|
|
223
223
|
* @param {Mocha.Test} test
|
|
224
224
|
*/
|
|
225
|
-
|
|
226
225
|
started(test) {
|
|
227
226
|
if (outputLevel < 1) return
|
|
228
227
|
print(` ${colors.dim.bold('Scenario()')}`)
|
|
229
228
|
},
|
|
230
|
-
|
|
231
229
|
/**
|
|
232
230
|
* @param {Mocha.Test} test
|
|
233
231
|
*/
|
|
@@ -273,10 +271,12 @@ module.exports = {
|
|
|
273
271
|
},
|
|
274
272
|
|
|
275
273
|
/**
|
|
274
|
+
* Prints the stats of a test run to the console.
|
|
276
275
|
* @param {number} passed
|
|
277
276
|
* @param {number} failed
|
|
278
277
|
* @param {number} skipped
|
|
279
278
|
* @param {number|string} duration
|
|
279
|
+
* @param {number} [failedHooks]
|
|
280
280
|
*/
|
|
281
281
|
result(passed, failed, skipped, duration, failedHooks = 0) {
|
|
282
282
|
let style = colors.bgGreen
|