codeceptjs 4.0.1-beta.3 → 4.0.1-beta.30

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/bin/codecept.js CHANGED
@@ -174,7 +174,7 @@ program
174
174
  .option('-R, --reporter <name>', 'specify the reporter to use')
175
175
  .option('-S, --sort', 'sort test files')
176
176
  .option('-b, --bail', 'bail after first test failure')
177
- .option('-d, --debug', "enable node's debugger, synonym for node --debug")
177
+ .option('--inspec', "enable node's debugger, synonym for node --debug")
178
178
  .option('-g, --grep <pattern>', 'only run tests matching <pattern>')
179
179
  .option('-f, --fgrep <string>', 'only run tests containing <string>')
180
180
  .option('-i, --invert', 'inverts --grep and --fgrep matches')
@@ -276,7 +276,7 @@ program
276
276
  .option('-R, --reporter <name>', 'specify the reporter to use')
277
277
  .option('-S, --sort', 'sort test files')
278
278
  .option('-b, --bail', 'bail after first test failure')
279
- .option('-d, --debug', "enable node's debugger, synonym for node --debug")
279
+ .option('--inspect', "enable node's debugger, synonym for node --debug")
280
280
  .option('-g, --grep <pattern>', 'only run tests matching <pattern>')
281
281
  .option('-f, --fgrep <string>', 'only run tests containing <string>')
282
282
  .option('-i, --invert', 'inverts --grep and --fgrep matches')
package/lib/actor.js CHANGED
@@ -75,7 +75,8 @@ export default function (obj = {}, container) {
75
75
  if (!container) {
76
76
  container = Container
77
77
  }
78
-
78
+
79
+ // Get existing actor or create a new one
79
80
  const actor = container.actor() || new Actor()
80
81
 
81
82
  // load all helpers once container initialized
@@ -111,14 +112,17 @@ export default function (obj = {}, container) {
111
112
  }
112
113
  })
113
114
 
114
- container.append({
115
- support: {
116
- I: actor,
117
- },
118
- })
115
+ // Update container.support.I to ensure it has the latest actor reference
116
+ if (!container.actor() || container.actor() !== actor) {
117
+ container.append({
118
+ support: {
119
+ I: actor,
120
+ },
121
+ })
122
+ }
119
123
  })
120
- // store.actor = actor;
121
- // add custom steps from actor
124
+
125
+ // add custom steps from actor immediately
122
126
  Object.keys(obj).forEach(key => {
123
127
  const ms = new MetaStep('I', key)
124
128
  ms.setContext(actor)
@@ -41,7 +41,7 @@ const getDefinitionsFileContent = ({ hasCustomHelper, hasCustomStepsFile, helper
41
41
 
42
42
  const importPathsFragment = importPaths.join('\n')
43
43
  const supportObjectsTypeFragment = convertMapToType(supportObject)
44
- const methodsTypeFragment = helperNames.length > 0 ? `interface Methods extends ${helperNames.join(', ')} {}` : ''
44
+ const methodsTypeFragment = helperNames.length > 0 ? `interface Methods extends ${helperNames.join(', ')} {}` : 'interface Methods {}'
45
45
  const translatedActionsFragment = JSON.stringify(translations.vocabulary.actions, null, 2)
46
46
 
47
47
  return generateDefinitionsContent({
@@ -239,8 +239,13 @@ function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue)
239
239
  }
240
240
 
241
241
  for (const name in pathsToValue) {
242
- const relativePath = getPath(pathsToValue[name], targetFolderPath, testsPath)
243
- importStrings.push(`type ${name} = import('${relativePath}');`)
242
+ const originalPath = pathsToValue[name]
243
+ const relativePath = getPath(originalPath, targetFolderPath, testsPath)
244
+ if (originalPath.endsWith('.js') || originalPath.endsWith('.ts')) {
245
+ importStrings.push(`type ${name} = InstanceType<typeof import('${relativePath}').default>;`)
246
+ } else {
247
+ importStrings.push(`type ${name} = import('${relativePath}');`)
248
+ }
244
249
  }
245
250
 
246
251
  return importStrings
@@ -104,7 +104,9 @@ initPromise = (async function () {
104
104
  // important deep merge so dynamic things e.g. functions on config are not overridden
105
105
  config = deepMerge(baseConfig, overrideConfigs)
106
106
 
107
- codecept = new Codecept(config, options)
107
+ // Pass workerIndex as child option for output.process() to display worker prefix
108
+ const optsWithChild = { ...options, child: workerIndex }
109
+ codecept = new Codecept(config, optsWithChild)
108
110
  await codecept.init(testRoot)
109
111
  codecept.loadTests()
110
112
  mocha = container.mocha()
package/lib/config.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { createRequire } from 'module'
4
4
  import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
5
- import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
5
+ import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
6
6
 
7
7
  const defaultConfig = {
8
8
  output: './_output',
@@ -159,12 +159,13 @@ async function loadConfigFile(configFile) {
159
159
  try {
160
160
  // Use the TypeScript transpilation utility
161
161
  const typescript = require('typescript')
162
- const { tempFile, allTempFiles } = await transpileTypeScript(configFile, typescript)
162
+ const { tempFile, allTempFiles, fileMapping } = await transpileTypeScript(configFile, typescript)
163
163
 
164
164
  try {
165
165
  configModule = await import(tempFile)
166
166
  cleanupTempFiles(allTempFiles)
167
167
  } catch (err) {
168
+ fixErrorStack(err, fileMapping)
168
169
  cleanupTempFiles(allTempFiles)
169
170
  throw err
170
171
  }
package/lib/container.js CHANGED
@@ -5,7 +5,7 @@ import debugModule from 'debug'
5
5
  const debug = debugModule('codeceptjs:container')
6
6
  import { MetaStep } from './step.js'
7
7
  import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8
- import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
8
+ import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
9
9
  import Translation from './translation.js'
10
10
  import MochaFactory from './mocha/factory.js'
11
11
  import recorder from './recorder.js'
@@ -22,6 +22,7 @@ let container = {
22
22
  helpers: {},
23
23
  support: {},
24
24
  proxySupport: {},
25
+ proxySupportConfig: {}, // Track config used to create proxySupport
25
26
  plugins: {},
26
27
  actor: null,
27
28
  /**
@@ -33,6 +34,7 @@ let container = {
33
34
  /** @type {Result | null} */
34
35
  result: null,
35
36
  sharedKeys: new Set(), // Track keys shared via share() function
37
+ tsFileMapping: null, // TypeScript file mapping for error stack fixing
36
38
  }
37
39
 
38
40
  /**
@@ -67,14 +69,15 @@ class Container {
67
69
  container.support = {}
68
70
  container.helpers = await createHelpers(config.helpers || {})
69
71
  container.translation = await loadTranslation(config.translation || null, config.vocabularies || [])
70
- container.proxySupport = createSupportObjects(config.include || {})
72
+ container.proxySupportConfig = config.include || {}
73
+ container.proxySupport = createSupportObjects(container.proxySupportConfig)
71
74
  container.plugins = await createPlugins(config.plugins || {}, opts)
72
75
  container.result = new Result()
73
76
 
74
77
  // Preload includes (so proxies can expose real objects synchronously)
75
78
  const includes = config.include || {}
76
79
 
77
- // Ensure I is available for DI modules at import time
80
+ // Check if custom I is provided
78
81
  if (Object.prototype.hasOwnProperty.call(includes, 'I')) {
79
82
  try {
80
83
  const mod = includes.I
@@ -89,7 +92,7 @@ class Container {
89
92
  throw new Error(`Could not include object I: ${e.message}`)
90
93
  }
91
94
  } else {
92
- // Create default actor if not provided via includes
95
+ // Create default actor - this sets up the callback in asyncHelperPromise
93
96
  createActor()
94
97
  }
95
98
 
@@ -110,7 +113,7 @@ class Container {
110
113
  }
111
114
  }
112
115
 
113
- // Wait for all async helpers to finish loading and populate the actor with helper methods
116
+ // Wait for all async helpers to finish loading and populate the actor
114
117
  await asyncHelperPromise
115
118
 
116
119
  if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
@@ -174,6 +177,15 @@ class Container {
174
177
  return container.translation
175
178
  }
176
179
 
180
+ /**
181
+ * Get TypeScript file mapping for error stack fixing
182
+ *
183
+ * @api
184
+ */
185
+ static tsFileMapping() {
186
+ return container.tsFileMapping
187
+ }
188
+
177
189
  /**
178
190
  * Get Mocha instance
179
191
  *
@@ -207,8 +219,10 @@ class Container {
207
219
 
208
220
  // If new support objects are added, update the proxy support
209
221
  if (newContainer.support) {
210
- const newProxySupport = createSupportObjects(newContainer.support)
211
- container.proxySupport = { ...container.proxySupport, ...newProxySupport }
222
+ // Merge the new support config with existing config
223
+ container.proxySupportConfig = { ...container.proxySupportConfig, ...newContainer.support }
224
+ // Recreate the proxy with merged config
225
+ container.proxySupport = createSupportObjects(container.proxySupportConfig)
212
226
  }
213
227
 
214
228
  debug('appended', JSON.stringify(newContainer).slice(0, 300))
@@ -224,6 +238,7 @@ class Container {
224
238
  static async clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
225
239
  container.helpers = newHelpers
226
240
  container.translation = await loadTranslation()
241
+ container.proxySupportConfig = newSupport
227
242
  container.proxySupport = createSupportObjects(newSupport)
228
243
  container.plugins = newPlugins
229
244
  container.sharedKeys = new Set() // Clear shared keys
@@ -350,16 +365,17 @@ async function createHelpers(config) {
350
365
  }
351
366
  }
352
367
 
353
- // Wait for all async helpers to be fully loaded
354
- await asyncHelperPromise
355
-
356
- // Call _init on all helpers after they're all loaded
357
- for (const name in helpers) {
358
- if (helpers[name]._init) {
359
- await helpers[name]._init()
360
- debug(`helper ${name} _init() called`)
368
+ // Don't await here - let Container.create() handle the await
369
+ // This allows actor callbacks to be registered before resolution
370
+ asyncHelperPromise = asyncHelperPromise.then(async () => {
371
+ // Call _init on all helpers after they're all loaded
372
+ for (const name in helpers) {
373
+ if (helpers[name]._init) {
374
+ await helpers[name]._init()
375
+ debug(`helper ${name} _init() called`)
376
+ }
361
377
  }
362
- }
378
+ })
363
379
 
364
380
  return helpers
365
381
  }
@@ -392,20 +408,66 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
392
408
  throw err
393
409
  }
394
410
  } else {
411
+ // Handle TypeScript files
412
+ let importPath = moduleName
413
+ let tempJsFile = null
414
+ let fileMapping = null
415
+ const ext = path.extname(moduleName)
416
+
417
+ if (ext === '.ts') {
418
+ try {
419
+ // Use the TypeScript transpilation utility
420
+ const typescript = await import('typescript')
421
+ const { tempFile, allTempFiles, fileMapping: mapping } = await transpileTypeScript(importPath, typescript)
422
+
423
+ debug(`Transpiled TypeScript helper: ${importPath} -> ${tempFile}`)
424
+
425
+ importPath = tempFile
426
+ tempJsFile = allTempFiles
427
+ fileMapping = mapping
428
+ // Store file mapping in container for runtime error fixing (merge with existing)
429
+ if (!container.tsFileMapping) {
430
+ container.tsFileMapping = new Map()
431
+ }
432
+ for (const [key, value] of mapping.entries()) {
433
+ container.tsFileMapping.set(key, value)
434
+ }
435
+ } catch (tsError) {
436
+ throw new Error(`Failed to load TypeScript helper ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
437
+ }
438
+ }
439
+
395
440
  // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
396
441
  try {
397
442
  // Try dynamic import for both CommonJS and ESM modules
398
- const mod = await import(moduleName)
443
+ const mod = await import(importPath)
399
444
  if (!mod && !mod.default) {
400
445
  throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
401
446
  }
402
447
  HelperClass = mod.default || mod
448
+
449
+ // Clean up temp files if created
450
+ if (tempJsFile) {
451
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
452
+ cleanupTempFiles(filesToClean)
453
+ }
403
454
  } catch (err) {
455
+ // Fix error stack to point to original .ts files
456
+ if (fileMapping) {
457
+ fixErrorStack(err, fileMapping)
458
+ }
459
+
460
+ // Clean up temp files before rethrowing
461
+ if (tempJsFile) {
462
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
463
+ cleanupTempFiles(filesToClean)
464
+ }
465
+
404
466
  if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
405
467
  // This is an ESM module, use dynamic import
406
468
  try {
407
469
  const pathModule = await import('path')
408
- const absolutePath = pathModule.default.resolve(moduleName)
470
+ const absolutePath = pathModule.default.resolve(importPath)
409
471
  const mod = await import(absolutePath)
410
472
  HelperClass = mod.default || mod
411
473
  debug(`helper ${helperName} loaded via ESM import`)
@@ -534,10 +596,17 @@ function createSupportObjects(config) {
534
596
  return [...new Set([...keys, ...container.sharedKeys])]
535
597
  },
536
598
  getOwnPropertyDescriptor(target, prop) {
599
+ // For destructuring to work, we need to return the actual value from the getter
600
+ let value
601
+ if (container.sharedKeys.has(prop) && prop in container.support) {
602
+ value = container.support[prop]
603
+ } else {
604
+ value = lazyLoad(prop)
605
+ }
537
606
  return {
538
607
  enumerable: true,
539
608
  configurable: true,
540
- value: target[prop],
609
+ value: value,
541
610
  }
542
611
  },
543
612
  get(target, key) {
@@ -686,6 +755,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
686
755
  // Use dynamic import for both ESM and CJS modules
687
756
  let importPath = modulePath
688
757
  let tempJsFile = null
758
+ let fileMapping = null
689
759
 
690
760
  if (typeof importPath === 'string') {
691
761
  const ext = path.extname(importPath)
@@ -695,7 +765,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
695
765
  try {
696
766
  // Use the TypeScript transpilation utility
697
767
  const typescript = await import('typescript')
698
- const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
768
+ const { tempFile, allTempFiles, fileMapping: mapping } = await transpileTypeScript(importPath, typescript)
699
769
 
700
770
  debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
701
771
 
@@ -703,6 +773,14 @@ async function loadSupportObject(modulePath, supportObjectName) {
703
773
  importPath = tempFile
704
774
  // Store temp files list in a way that cleanup can access them
705
775
  tempJsFile = allTempFiles
776
+ fileMapping = mapping
777
+ // Store file mapping in container for runtime error fixing (merge with existing)
778
+ if (!container.tsFileMapping) {
779
+ container.tsFileMapping = new Map()
780
+ }
781
+ for (const [key, value] of mapping.entries()) {
782
+ container.tsFileMapping.set(key, value)
783
+ }
706
784
  } catch (tsError) {
707
785
  throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
708
786
  }
@@ -716,6 +794,11 @@ async function loadSupportObject(modulePath, supportObjectName) {
716
794
  try {
717
795
  obj = await import(importPath)
718
796
  } catch (importError) {
797
+ // Fix error stack to point to original .ts files
798
+ if (fileMapping) {
799
+ fixErrorStack(importError, fileMapping)
800
+ }
801
+
719
802
  // Clean up temp files if created before rethrowing
720
803
  if (tempJsFile) {
721
804
  const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
@@ -45,6 +45,8 @@ class GraphQL extends Helper {
45
45
  timeout: 10000,
46
46
  defaultHeaders: {},
47
47
  endpoint: '',
48
+ onRequest: null,
49
+ onResponse: null,
48
50
  }
49
51
  this.options = Object.assign(this.options, config)
50
52
  this.headers = { ...this.options.defaultHeaders }
@@ -87,8 +89,8 @@ class GraphQL extends Helper {
87
89
 
88
90
  request.headers = { ...this.headers, ...request.headers }
89
91
 
90
- if (this.config.onRequest) {
91
- await this.config.onRequest(request)
92
+ if (this.options.onRequest) {
93
+ await this.options.onRequest(request)
92
94
  }
93
95
 
94
96
  this.debugSection('Request', JSON.stringify(request))
@@ -102,8 +104,8 @@ class GraphQL extends Helper {
102
104
  response = err.response
103
105
  }
104
106
 
105
- if (this.config.onResponse) {
106
- await this.config.onResponse(response)
107
+ if (this.options.onResponse) {
108
+ await this.options.onResponse(response)
107
109
  }
108
110
 
109
111
  this.debugSection('Response', JSON.stringify(response.data))
@@ -72,8 +72,8 @@ 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
76
- this.helpers[this.options.requestHelper].config.onResponse = response => {
75
+ const origOnResponse = this.helpers[this.options.requestHelper].options.onResponse
76
+ this.helpers[this.options.requestHelper].options.onResponse = response => {
77
77
  this.response = response
78
78
  if (typeof origOnResponse === 'function') origOnResponse(response)
79
79
  }
@@ -83,7 +83,6 @@ class JSONResponse extends Helper {
83
83
  this.response = null
84
84
  }
85
85
 
86
-
87
86
  /**
88
87
  * Checks that response code is equal to the provided one
89
88
  *
@@ -372,4 +371,4 @@ class JSONResponse extends Helper {
372
371
  }
373
372
  }
374
373
 
375
- export { JSONResponse as default }
374
+ export { JSONResponse, JSONResponse as default }