codeceptjs 4.0.1-beta.2 → 4.0.1-beta.21

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/locator.js CHANGED
@@ -5,7 +5,7 @@ import { createRequire } from 'module'
5
5
  const require = createRequire(import.meta.url)
6
6
  let cssToXPath
7
7
 
8
- const locatorTypes = ['css', 'by', 'xpath', 'id', 'name', 'fuzzy', 'frame', 'shadow', 'pw', 'role']
8
+ const locatorTypes = ['css', 'by', 'xpath', 'id', 'name', 'fuzzy', 'frame', 'shadow', 'role']
9
9
  /** @class */
10
10
  class Locator {
11
11
  /**
@@ -24,19 +24,16 @@ class Locator {
24
24
  */
25
25
  this.strict = false
26
26
 
27
+ if (typeof locator === 'string' && this.parsedJsonAsString(locator)) {
28
+ return
29
+ }
30
+
27
31
  if (typeof locator === 'object') {
28
32
  if (locator.constructor.name === 'Locator') {
29
33
  Object.assign(this, locator)
30
34
  return
31
35
  }
32
-
33
- this.locator = locator
34
- this.type = Object.keys(locator)[0]
35
- this.value = locator[this.type]
36
- this.strict = true
37
-
38
- Locator.filters.forEach(f => f(locator, this))
39
-
36
+ this._applyObjectLocator(locator)
40
37
  return
41
38
  }
42
39
 
@@ -53,8 +50,9 @@ class Locator {
53
50
  if (isShadow(locator)) {
54
51
  this.type = 'shadow'
55
52
  }
56
- if (isPlaywrightLocator(locator)) {
57
- this.type = 'pw'
53
+ if (isReactVueLocator(locator)) {
54
+ // React/Vue locators - keep as fuzzy type, helpers will handle them specially
55
+ this.type = 'fuzzy'
58
56
  }
59
57
 
60
58
  Locator.filters.forEach(f => f(locator, this))
@@ -76,8 +74,6 @@ class Locator {
76
74
  return this.value
77
75
  case 'shadow':
78
76
  return { shadow: this.value }
79
- case 'pw':
80
- return { pw: this.value }
81
77
  case 'role':
82
78
  return `[role="${this.value}"]`
83
79
  }
@@ -86,14 +82,52 @@ class Locator {
86
82
 
87
83
  toStrict() {
88
84
  if (!this.type) return null
85
+ if (this.type === 'role' && this.locator) {
86
+ return this.locator
87
+ }
89
88
  return { [this.type]: this.value }
90
89
  }
91
90
 
91
+ parsedJsonAsString(locator) {
92
+ if (typeof locator !== 'string') {
93
+ return false
94
+ }
95
+
96
+ const trimmed = locator.trim()
97
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
98
+ return false
99
+ }
100
+
101
+ try {
102
+ const parsed = JSON.parse(trimmed)
103
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
104
+ this._applyObjectLocator(parsed)
105
+ return true
106
+ }
107
+ } catch (e) {
108
+ }
109
+ return false
110
+ }
111
+
112
+ _applyObjectLocator(locator) {
113
+ this.strict = true
114
+ this.locator = locator
115
+ const keys = Object.keys(locator)
116
+ const [type] = keys
117
+ this.type = type
118
+ this.value = keys.length > 1 ? locator : locator[type]
119
+ Locator.filters.forEach(f => f(locator, this))
120
+ }
121
+
92
122
  /**
93
123
  * @returns {string}
94
124
  */
95
125
  toString() {
96
- return this.output || `{${this.type}: ${this.value}}`
126
+ if (this.output) return this.output
127
+ if (this.locator && this.value === this.locator) {
128
+ return JSON.stringify(this.locator)
129
+ }
130
+ return `{${this.type}: ${this.value}}`
97
131
  }
98
132
 
99
133
  /**
@@ -127,17 +161,27 @@ class Locator {
127
161
  /**
128
162
  * @returns {boolean}
129
163
  */
130
- isPlaywrightLocator() {
131
- return this.type === 'pw'
164
+ isRole() {
165
+ return this.type === 'role'
132
166
  }
133
167
 
134
168
  /**
135
- * @returns {boolean}
169
+ * @returns {{role: string, options: object}|null}
136
170
  */
137
- isRole() {
138
- return this.type === 'role'
171
+ getRoleOptions() {
172
+ if (!this.isRole()) return null
173
+ const data = this.locator && typeof this.locator === 'object' ? this.locator : { role: this.value }
174
+ const { role, text, name, exact, includeHidden, ...rest } = data
175
+ let options = { ...rest }
176
+ const accessibleName = name ?? text
177
+ if (accessibleName !== undefined) options.name = accessibleName
178
+ if (exact !== undefined) options.exact = exact
179
+ if (includeHidden !== undefined) options.includeHidden = includeHidden
180
+ if (Object.keys(options).length === 0) options = undefined
181
+ return { role, options }
139
182
  }
140
183
 
184
+
141
185
  /**
142
186
  * @returns {boolean}
143
187
  */
@@ -404,6 +448,16 @@ Locator.build = locator => {
404
448
  return new Locator(locator, 'css')
405
449
  }
406
450
 
451
+ /**
452
+ * @param {CodeceptJS.LocatorOrString|Locator} locator
453
+ * @param {string} [defaultType]
454
+ * @returns {Locator}
455
+ */
456
+ Locator.from = (locator, defaultType = '') => {
457
+ if (locator instanceof Locator) return locator
458
+ return new Locator(locator, defaultType)
459
+ }
460
+
407
461
  /**
408
462
  * Filters to modify locators
409
463
  * @type {Array<function(CodeceptJS.LocatorOrString, Locator): void>}
@@ -604,20 +658,10 @@ function removePrefix(xpath) {
604
658
  * @param {string} locator
605
659
  * @returns {boolean}
606
660
  */
607
- function isPlaywrightLocator(locator) {
661
+ function isReactVueLocator(locator) {
608
662
  return locator.includes('_react') || locator.includes('_vue')
609
663
  }
610
664
 
611
- /**
612
- * @private
613
- * check if the locator is a role locator
614
- * @param {{role: string}} locator
615
- * @returns {boolean}
616
- */
617
- function isRoleLocator(locator) {
618
- return locator.role !== undefined && typeof locator.role === 'string' && Object.keys(locator).length >= 1
619
- }
620
-
621
665
  /**
622
666
  * @private
623
667
  * @param {CodeceptJS.LocatorOrString} locator
@@ -62,34 +62,9 @@ class MochaFactory {
62
62
  const jsFiles = this.files.filter(file => !file.match(/\.feature$/))
63
63
  this.files = this.files.filter(file => !file.match(/\.feature$/))
64
64
 
65
- // Load JavaScript test files using ESM imports
65
+ // Load JavaScript test files using original loadFiles
66
66
  if (jsFiles.length > 0) {
67
- try {
68
- // Try original loadFiles first for compatibility
69
- originalLoadFiles.call(this, fn)
70
- } catch (e) {
71
- // If original loadFiles fails, load ESM files manually
72
- if (e.message.includes('not in cache') || e.message.includes('ESM') || e.message.includes('getStatus')) {
73
- // Load ESM files by importing them synchronously using top-level await workaround
74
- for (const file of jsFiles) {
75
- try {
76
- // Convert file path to file:// URL for dynamic import
77
- const fileUrl = `file://${file}`
78
- // Use import() but don't await it - let it load in the background
79
- import(fileUrl).catch(importErr => {
80
- // If dynamic import fails, the file may have syntax errors or other issues
81
- console.error(`Failed to load test file ${file}:`, importErr.message)
82
- })
83
- if (fn) fn()
84
- } catch (fileErr) {
85
- console.error(`Error processing test file ${file}:`, fileErr.message)
86
- if (fn) fn(fileErr)
87
- }
88
- }
89
- } else {
90
- throw e
91
- }
92
- }
67
+ originalLoadFiles.call(this, fn)
93
68
  }
94
69
 
95
70
  // add ids for each test and check uniqueness
package/lib/step/meta.js CHANGED
@@ -58,17 +58,24 @@ class MetaStep extends Step {
58
58
  this.status = 'queued'
59
59
  this.setArguments(Array.from(arguments).slice(1))
60
60
  let result
61
+ let hasChildSteps = false
61
62
 
62
63
  const registerStep = step => {
63
64
  this.setMetaStep(null)
64
65
  step.setMetaStep(this)
66
+ hasChildSteps = true
65
67
  }
66
68
  event.dispatcher.prependListener(event.step.before, registerStep)
69
+
70
+ // Start timing
71
+ this.startTime = Date.now()
72
+
67
73
  // Handle async and sync methods.
68
74
  if (fn.constructor.name === 'AsyncFunction') {
69
75
  result = fn
70
76
  .apply(this.context, this.args)
71
77
  .then(result => {
78
+ this.setStatus('success')
72
79
  return result
73
80
  })
74
81
  .catch(error => {
@@ -78,17 +85,27 @@ class MetaStep extends Step {
78
85
  .finally(() => {
79
86
  this.endTime = Date.now()
80
87
  event.dispatcher.removeListener(event.step.before, registerStep)
88
+ // Only emit events if no child steps were registered
89
+ if (!hasChildSteps) {
90
+ event.emit(event.step.started, this)
91
+ event.emit(event.step.finished, this)
92
+ }
81
93
  })
82
94
  } else {
83
95
  try {
84
- this.startTime = Date.now()
85
96
  result = fn.apply(this.context, this.args)
97
+ this.setStatus('success')
86
98
  } catch (error) {
87
99
  this.setStatus('failed')
88
100
  throw error
89
101
  } finally {
90
102
  this.endTime = Date.now()
91
103
  event.dispatcher.removeListener(event.step.before, registerStep)
104
+ // Only emit events if no child steps were registered
105
+ if (!hasChildSteps) {
106
+ event.emit(event.step.started, this)
107
+ event.emit(event.step.finished, this)
108
+ }
92
109
  }
93
110
  }
94
111
 
@@ -65,9 +65,18 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
65
65
  ✅ Complete: Handles all TypeScript features
66
66
 
67
67
  ┌─────────────────────────────────────────────────────────────────────────────┐
68
- │ Option 2: ts-node/esm (Alternative - Established, Requires Config)
68
+ │ Option 2: ts-node/esm (Not Recommended - Has Module Resolution Issues)
69
69
  └─────────────────────────────────────────────────────────────────────────────┘
70
70
 
71
+ ⚠️ ts-node/esm has significant limitations and is not recommended:
72
+ - Doesn't work with "type": "module" in package.json
73
+ - Module resolution doesn't work like standard TypeScript ESM
74
+ - Import statements must use explicit file paths
75
+
76
+ We strongly recommend using tsx/cjs instead.
77
+
78
+ If you still want to use ts-node/esm:
79
+
71
80
  Installation:
72
81
  npm install --save-dev ts-node
73
82
 
@@ -84,11 +93,12 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
84
93
  "esModuleInterop": true
85
94
  },
86
95
  "ts-node": {
87
- "esm": true,
88
- "experimentalSpecifierResolution": "node"
96
+ "esm": true
89
97
  }
90
98
  }
91
99
 
100
+ 3. Do NOT use "type": "module" in package.json
101
+
92
102
  📚 Documentation: https://codecept.io/typescript
93
103
 
94
104
  Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
@@ -142,8 +142,13 @@ const __dirname = __dirname_fn(__filename);
142
142
  }
143
143
  }
144
144
 
145
- // Try adding .ts extension if file doesn't exist and no extension provided
146
- if (!path.extname(importedPath)) {
145
+ // Check for standard module extensions to determine if we should try adding .ts
146
+ const ext = path.extname(importedPath)
147
+ const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
148
+ const hasStandardExtension = standardExtensions.includes(ext.toLowerCase())
149
+
150
+ // If it doesn't end with .ts and doesn't have a standard extension, try adding .ts
151
+ if (!importedPath.endsWith('.ts') && !hasStandardExtension) {
147
152
  const tsPath = importedPath + '.ts'
148
153
  if (fs.existsSync(tsPath)) {
149
154
  importedPath = tsPath
@@ -168,6 +173,7 @@ const __dirname = __dirname_fn(__filename);
168
173
  /from\s+['"](\..+?)(?:\.ts)?['"]/g,
169
174
  (match, importPath) => {
170
175
  let resolvedPath = path.resolve(fileBaseDir, importPath)
176
+ const originalExt = path.extname(importPath)
171
177
 
172
178
  // Handle .js extension that might be .ts
173
179
  if (resolvedPath.endsWith('.js')) {
@@ -181,6 +187,8 @@ const __dirname = __dirname_fn(__filename);
181
187
  }
182
188
  return `from '${relPath}'`
183
189
  }
190
+ // Keep .js extension as-is (might be a real .js file)
191
+ return match
184
192
  }
185
193
 
186
194
  // Try with .ts extension
@@ -197,6 +205,18 @@ const __dirname = __dirname_fn(__filename);
197
205
  return `from '${relPath}'`
198
206
  }
199
207
 
208
+ // If the import doesn't have a standard module extension (.js, .mjs, .cjs, .json)
209
+ // add .js for ESM compatibility
210
+ // This handles cases where:
211
+ // 1. Import has no real extension (e.g., "./utils" or "./helper")
212
+ // 2. Import has a non-standard extension that's part of the name (e.g., "./abstract.helper")
213
+ const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
214
+ const hasStandardExtension = standardExtensions.includes(originalExt.toLowerCase())
215
+
216
+ if (!hasStandardExtension) {
217
+ return match.replace(importPath, importPath + '.js')
218
+ }
219
+
200
220
  // Otherwise, keep the import as-is
201
221
  return match
202
222
  }
package/lib/workers.js CHANGED
@@ -5,6 +5,7 @@ import { mkdirp } from 'mkdirp'
5
5
  import { Worker } from 'worker_threads'
6
6
  import { EventEmitter } from 'events'
7
7
  import ms from 'ms'
8
+ import merge from 'lodash.merge'
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url)
10
11
  const __dirname = dirname(__filename)
@@ -66,21 +67,21 @@ const createWorker = (workerObject, isPoolMode = false) => {
66
67
  stdout: true,
67
68
  stderr: true,
68
69
  })
69
-
70
+
70
71
  // Pipe worker stdout/stderr to main process
71
72
  if (worker.stdout) {
72
73
  worker.stdout.setEncoding('utf8')
73
- worker.stdout.on('data', (data) => {
74
+ worker.stdout.on('data', data => {
74
75
  process.stdout.write(data)
75
76
  })
76
77
  }
77
78
  if (worker.stderr) {
78
79
  worker.stderr.setEncoding('utf8')
79
- worker.stderr.on('data', (data) => {
80
+ worker.stderr.on('data', data => {
80
81
  process.stderr.write(data)
81
82
  })
82
83
  }
83
-
84
+
84
85
  worker.on('error', err => {
85
86
  console.error(`[Main] Worker Error:`, err)
86
87
  output.error(`Worker Error: ${err.stack}`)
@@ -221,13 +222,13 @@ class WorkerObject {
221
222
 
222
223
  addConfig(config) {
223
224
  const oldConfig = JSON.parse(this.options.override || '{}')
224
-
225
+
225
226
  // Remove customLocatorStrategies from both old and new config before JSON serialization
226
227
  // since functions cannot be serialized and will be lost, causing workers to have empty strategies
227
228
  const configWithoutFunctions = { ...config }
228
-
229
+
229
230
  // Clean both old and new config
230
- const cleanConfig = (cfg) => {
231
+ const cleanConfig = cfg => {
231
232
  if (cfg.helpers) {
232
233
  cfg.helpers = { ...cfg.helpers }
233
234
  Object.keys(cfg.helpers).forEach(helperName => {
@@ -239,14 +240,12 @@ class WorkerObject {
239
240
  }
240
241
  return cfg
241
242
  }
242
-
243
+
243
244
  const cleanedOldConfig = cleanConfig(oldConfig)
244
245
  const cleanedNewConfig = cleanConfig(configWithoutFunctions)
245
-
246
- const newConfig = {
247
- ...cleanedOldConfig,
248
- ...cleanedNewConfig,
249
- }
246
+
247
+ // Deep merge configurations to preserve all helpers from base config
248
+ const newConfig = merge({}, cleanedOldConfig, cleanedNewConfig)
250
249
  this.options.override = JSON.stringify(newConfig)
251
250
  }
252
251
 
@@ -280,8 +279,8 @@ class Workers extends EventEmitter {
280
279
  this.setMaxListeners(50)
281
280
  this.codeceptPromise = initializeCodecept(config.testConfig, config.options)
282
281
  this.codecept = null
283
- this.config = config // Save config
284
- this.numberOfWorkersRequested = numberOfWorkers // Save requested worker count
282
+ this.config = config // Save config
283
+ this.numberOfWorkersRequested = numberOfWorkers // Save requested worker count
285
284
  this.options = config.options || {}
286
285
  this.errors = []
287
286
  this.numberOfWorkers = 0
@@ -304,11 +303,8 @@ class Workers extends EventEmitter {
304
303
  // Initialize workers in these cases:
305
304
  // 1. Positive number requested AND no manual workers pre-spawned
306
305
  // 2. Function-based grouping (indicated by negative number) AND no manual workers pre-spawned
307
- const shouldAutoInit = this.workers.length === 0 && (
308
- (Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) ||
309
- (this.numberOfWorkersRequested < 0 && isFunction(this.config.by))
310
- )
311
-
306
+ const shouldAutoInit = this.workers.length === 0 && ((Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) || (this.numberOfWorkersRequested < 0 && isFunction(this.config.by)))
307
+
312
308
  if (shouldAutoInit) {
313
309
  this._initWorkers(this.numberOfWorkersRequested, this.config)
314
310
  }
@@ -319,7 +315,7 @@ class Workers extends EventEmitter {
319
315
  this.splitTestsByGroups(numberOfWorkers, config)
320
316
  // For function-based grouping, use the actual number of test groups created
321
317
  const actualNumberOfWorkers = isFunction(config.by) ? this.testGroups.length : numberOfWorkers
322
- this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns)
318
+ this.workers = createWorkerObjects(this.testGroups, this.codecept.config, getTestRoot(config.testConfig), config.options, config.selectedRuns)
323
319
  this.numberOfWorkers = this.workers.length
324
320
  }
325
321
 
@@ -371,9 +367,9 @@ class Workers extends EventEmitter {
371
367
  * @param {Number} numberOfWorkers
372
368
  */
373
369
  createGroupsOfTests(numberOfWorkers) {
374
- // If Codecept isn't initialized yet, return empty groups as a safe fallback
375
- if (!this.codecept) return populateGroups(numberOfWorkers)
376
- const files = this.codecept.testFiles
370
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
371
+ if (!this.codecept) return populateGroups(numberOfWorkers)
372
+ const files = this.codecept.testFiles
377
373
  const mocha = Container.mocha()
378
374
  mocha.files = files
379
375
  mocha.loadFiles()
@@ -430,7 +426,7 @@ class Workers extends EventEmitter {
430
426
  for (const file of files) {
431
427
  this.testPool.push(file)
432
428
  }
433
-
429
+
434
430
  this.testPoolInitialized = true
435
431
  }
436
432
 
@@ -443,7 +439,7 @@ class Workers extends EventEmitter {
443
439
  if (!this.testPoolInitialized) {
444
440
  this._initializeTestPool()
445
441
  }
446
-
442
+
447
443
  return this.testPool.shift()
448
444
  }
449
445
 
@@ -451,9 +447,9 @@ class Workers extends EventEmitter {
451
447
  * @param {Number} numberOfWorkers
452
448
  */
453
449
  createGroupsOfSuites(numberOfWorkers) {
454
- // If Codecept isn't initialized yet, return empty groups as a safe fallback
455
- if (!this.codecept) return populateGroups(numberOfWorkers)
456
- const files = this.codecept.testFiles
450
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
451
+ if (!this.codecept) return populateGroups(numberOfWorkers)
452
+ const files = this.codecept.testFiles
457
453
  const groups = populateGroups(numberOfWorkers)
458
454
 
459
455
  const mocha = Container.mocha()
@@ -494,7 +490,7 @@ class Workers extends EventEmitter {
494
490
  recorder.startUnlessRunning()
495
491
  event.dispatcher.emit(event.workers.before)
496
492
  process.env.RUNS_WITH_WORKERS = 'true'
497
-
493
+
498
494
  // Create workers and set up message handlers immediately (not in recorder queue)
499
495
  // This prevents a race condition where workers start sending messages before handlers are attached
500
496
  const workerThreads = []
@@ -503,11 +499,11 @@ class Workers extends EventEmitter {
503
499
  this._listenWorkerEvents(workerThread)
504
500
  workerThreads.push(workerThread)
505
501
  }
506
-
502
+
507
503
  recorder.add('workers started', () => {
508
504
  // Workers are already running, this is just a placeholder step
509
505
  })
510
-
506
+
511
507
  return new Promise(resolve => {
512
508
  this.on('end', resolve)
513
509
  })
@@ -591,7 +587,7 @@ class Workers extends EventEmitter {
591
587
  // Otherwise skip - we'll emit based on finished state
592
588
  break
593
589
  case event.test.passed:
594
- // Skip individual passed events - we'll emit based on finished state
590
+ // Skip individual passed events - we'll emit based on finished state
595
591
  break
596
592
  case event.test.skipped:
597
593
  this.emit(event.test.skipped, deserializeTest(message.data))
@@ -602,15 +598,15 @@ class Workers extends EventEmitter {
602
598
  const data = message.data
603
599
  const uid = data?.uid
604
600
  const isFailed = !!data?.err || data?.state === 'failed'
605
-
601
+
606
602
  if (uid) {
607
603
  // Track states for each test UID
608
604
  if (!this._testStates) this._testStates = new Map()
609
-
605
+
610
606
  if (!this._testStates.has(uid)) {
611
607
  this._testStates.set(uid, { states: [], lastData: data })
612
608
  }
613
-
609
+
614
610
  const testState = this._testStates.get(uid)
615
611
  testState.states.push({ isFailed, data })
616
612
  testState.lastData = data
@@ -622,7 +618,7 @@ class Workers extends EventEmitter {
622
618
  this.emit(event.test.passed, deserializeTest(data))
623
619
  }
624
620
  }
625
-
621
+
626
622
  this.emit(event.test.finished, deserializeTest(data))
627
623
  }
628
624
  break
@@ -682,11 +678,10 @@ class Workers extends EventEmitter {
682
678
  // For tests with retries configured, emit all failures + final success
683
679
  // For tests without retries, emit only final state
684
680
  const lastState = states[states.length - 1]
685
-
681
+
686
682
  // Check if this test had retries by looking for failure followed by success
687
- const hasRetryPattern = states.length > 1 &&
688
- states.some((s, i) => s.isFailed && i < states.length - 1 && !states[i + 1].isFailed)
689
-
683
+ const hasRetryPattern = states.length > 1 && states.some((s, i) => s.isFailed && i < states.length - 1 && !states[i + 1].isFailed)
684
+
690
685
  if (hasRetryPattern) {
691
686
  // Emit all intermediate failures and final success for retries
692
687
  for (const state of states) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.0.1-beta.2",
3
+ "version": "4.0.1-beta.21",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [
@@ -88,17 +88,17 @@
88
88
  "@codeceptjs/configure": "1.0.6",
89
89
  "@codeceptjs/helper": "2.0.4",
90
90
  "@cucumber/cucumber-expressions": "18",
91
- "@cucumber/gherkin": "35.1.0",
92
- "@cucumber/messages": "29.0.1",
91
+ "@cucumber/gherkin": "37.0.0",
92
+ "@cucumber/messages": "31.0.0",
93
93
  "@xmldom/xmldom": "0.9.8",
94
94
  "acorn": "8.15.0",
95
95
  "ai": "^5.0.60",
96
96
  "arrify": "3.0.0",
97
- "axios": "1.12.2",
97
+ "axios": "1.13.2",
98
98
  "chalk": "4.1.2",
99
99
  "cheerio": "^1.0.0",
100
100
  "chokidar": "^4.0.3",
101
- "commander": "11.1.0",
101
+ "commander": "14.0.2",
102
102
  "cross-spawn": "7.0.6",
103
103
  "css-to-xpath": "0.1.0",
104
104
  "csstoxpath": "1.6.0",
@@ -108,11 +108,11 @@
108
108
  "fn-args": "4.0.0",
109
109
  "fs-extra": "11.3.2",
110
110
  "fuse.js": "^7.0.0",
111
- "glob": ">=9.0.0 <12",
111
+ "glob": ">=9.0.0 <14",
112
112
  "html-minifier-terser": "7.2.0",
113
113
  "inquirer": "^8.2.7",
114
114
  "invisi-data": "^1.0.0",
115
- "joi": "18.0.1",
115
+ "joi": "18.0.2",
116
116
  "js-beautify": "1.15.4",
117
117
  "lodash.clonedeep": "4.5.0",
118
118
  "lodash.merge": "4.6.2",
@@ -144,16 +144,16 @@
144
144
  "@pollyjs/adapter-puppeteer": "6.0.6",
145
145
  "@pollyjs/core": "6.0.6",
146
146
  "@testomatio/reporter": "^2.3.1",
147
- "@types/chai": "5.2.2",
147
+ "@types/chai": "5.2.3",
148
148
  "@types/inquirer": "9.0.9",
149
149
  "@types/node": "^24.9.2",
150
150
  "@wdio/sauce-service": "9.12.5",
151
151
  "@wdio/selenium-standalone-service": "8.15.0",
152
- "@wdio/utils": "9.20.0",
152
+ "@wdio/utils": "9.21.0",
153
153
  "@xmldom/xmldom": "0.9.8",
154
154
  "bunosh": "latest",
155
- "chai": "^4.5.0",
156
- "chai-as-promised": "7.1.2",
155
+ "chai": "^6.2.1",
156
+ "chai-as-promised": "^8.0.2",
157
157
  "chai-subset": "1.6.0",
158
158
  "documentation": "14.0.3",
159
159
  "electron": "38.2.0",
@@ -162,8 +162,8 @@
162
162
  "eslint-plugin-mocha": "11.1.0",
163
163
  "expect": "30.2.0",
164
164
  "express": "^5.1.0",
165
- "globals": "16.4.0",
166
- "graphql": "16.11.0",
165
+ "globals": "16.5.0",
166
+ "graphql": "16.12.0",
167
167
  "graphql-tag": "^2.12.6",
168
168
  "husky": "9.1.7",
169
169
  "jsdoc": "^3.6.11",
@@ -172,19 +172,19 @@
172
172
  "mochawesome": "^7.1.3",
173
173
  "playwright": "1.55.1",
174
174
  "prettier": "^3.3.2",
175
- "puppeteer": "24.15.0",
175
+ "puppeteer": "24.33.0",
176
176
  "qrcode-terminal": "0.12.0",
177
177
  "rosie": "2.1.1",
178
178
  "runok": "^0.9.3",
179
179
  "semver": "7.7.3",
180
180
  "sinon": "21.0.0",
181
- "sinon-chai": "3.7.0",
181
+ "sinon-chai": "^4.0.1",
182
182
  "ts-morph": "27.0.2",
183
183
  "ts-node": "10.9.2",
184
184
  "tsd": "^0.33.0",
185
185
  "tsd-jsdoc": "2.5.0",
186
186
  "tsx": "^4.19.2",
187
- "typedoc": "0.28.13",
187
+ "typedoc": "0.28.15",
188
188
  "typedoc-plugin-markdown": "4.9.0",
189
189
  "typescript": "5.8.3",
190
190
  "wdio-docker-service": "3.2.1",