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 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() {
@@ -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
- keys = keys.split('')
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)
@@ -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)
@@ -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
- if (hook instanceof AfterSuiteHook) {
29
- timeout = null
30
- suiteTimeout = []
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
 
@@ -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