codeceptjs 4.0.0-beta.9.esm-aria → 4.0.1-beta.1

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
@@ -3,8 +3,9 @@ import { globSync } from 'glob'
3
3
  import shuffle from 'lodash.shuffle'
4
4
  import fsPath from 'path'
5
5
  import { resolve } from 'path'
6
- import { fileURLToPath } from 'url'
6
+ import { fileURLToPath, pathToFileURL } from 'url'
7
7
  import { dirname } from 'path'
8
+ import { createRequire } from 'module'
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url)
10
11
  const __dirname = dirname(__filename)
@@ -18,6 +19,7 @@ import ActorFactory from './actor.js'
18
19
  import output from './output.js'
19
20
  import { emptyFolder } from './utils.js'
20
21
  import { initCodeceptGlobals } from './globals.js'
22
+ import { validateTypeScriptSetup } from './utils/loaderCheck.js'
21
23
  import recorder from './recorder.js'
22
24
 
23
25
  import storeListener from './listener/store.js'
@@ -66,6 +68,21 @@ class Codecept {
66
68
  modulePath = `${modulePath}.js`
67
69
  }
68
70
  }
71
+ } else {
72
+ // For npm packages, resolve from the user's directory
73
+ // This ensures packages like tsx are found in user's node_modules
74
+ const userDir = global.codecept_dir || process.cwd()
75
+
76
+ try {
77
+ // Use createRequire to resolve from user's directory
78
+ const userRequire = createRequire(pathToFileURL(resolve(userDir, 'package.json')).href)
79
+ const resolvedPath = userRequire.resolve(requiredModule)
80
+ modulePath = pathToFileURL(resolvedPath).href
81
+ } catch (resolveError) {
82
+ // If resolution fails, try direct import (will check from CodeceptJS node_modules)
83
+ // This is the fallback for globally installed packages
84
+ modulePath = requiredModule
85
+ }
69
86
  }
70
87
  // Use dynamic import for ESM
71
88
  await import(modulePath)
@@ -246,6 +263,13 @@ class Codecept {
246
263
  async run(test) {
247
264
  await container.started()
248
265
 
266
+ // Check TypeScript loader configuration before running tests
267
+ const tsValidation = validateTypeScriptSetup(this.testFiles, this.requiringModules || [])
268
+ if (tsValidation.hasError) {
269
+ output.error(tsValidation.message)
270
+ process.exit(1)
271
+ }
272
+
249
273
  // Ensure translations are loaded for Gherkin features
250
274
  try {
251
275
  const { loadTranslations } = await import('./mocha/gherkin.js')
@@ -161,7 +161,7 @@ export default async function (initPath) {
161
161
  isTypeScript = true
162
162
  extension = isTypeScript === true ? 'ts' : 'js'
163
163
  packages.push('typescript')
164
- packages.push('ts-node')
164
+ packages.push('tsx') // Add tsx for TypeScript support
165
165
  packages.push('@types/node')
166
166
  }
167
167
 
@@ -172,6 +172,7 @@ export default async function (initPath) {
172
172
  config.tests = result.tests
173
173
  if (isTypeScript) {
174
174
  config.tests = `${config.tests.replace(/\.js$/, `.${extension}`)}`
175
+ config.require = ['tsx/cjs'] // Add tsx/cjs loader for TypeScript tests
175
176
  }
176
177
 
177
178
  // create a directory tests if it is included in tests path
package/lib/config.js CHANGED
@@ -2,6 +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
6
 
6
7
  const defaultConfig = {
7
8
  output: './_output',
@@ -156,31 +157,15 @@ async function loadConfigFile(configFile) {
156
157
  // For .ts files, try to compile and load as JavaScript
157
158
  if (extensionName === '.ts') {
158
159
  try {
159
- // Try to load ts-node and compile the file
160
- const { transpile } = require('typescript')
161
- const tsContent = fs.readFileSync(configFile, 'utf8')
162
-
163
- // Transpile TypeScript to JavaScript with ES module output
164
- const jsContent = transpile(tsContent, {
165
- module: 99, // ModuleKind.ESNext
166
- target: 99, // ScriptTarget.ESNext
167
- esModuleInterop: true,
168
- allowSyntheticDefaultImports: true,
169
- })
170
-
171
- // Create a temporary JS file with .mjs extension to force ES module treatment
172
- const tempJsFile = configFile.replace('.ts', '.temp.mjs')
173
- fs.writeFileSync(tempJsFile, jsContent)
160
+ // Use the TypeScript transpilation utility
161
+ const typescript = require('typescript')
162
+ const { tempFile, allTempFiles } = await transpileTypeScript(configFile, typescript)
174
163
 
175
164
  try {
176
- configModule = await import(tempJsFile)
177
- // Clean up temp file
178
- fs.unlinkSync(tempJsFile)
165
+ configModule = await import(tempFile)
166
+ cleanupTempFiles(allTempFiles)
179
167
  } catch (err) {
180
- // Clean up temp file even on error
181
- if (fs.existsSync(tempJsFile)) {
182
- fs.unlinkSync(tempJsFile)
183
- }
168
+ cleanupTempFiles(allTempFiles)
184
169
  throw err
185
170
  }
186
171
  } catch (tsError) {
package/lib/container.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { globSync } from 'glob'
2
2
  import path from 'path'
3
+ import fs from 'fs'
3
4
  import debugModule from 'debug'
4
5
  const debug = debugModule('codeceptjs:container')
5
6
  import { MetaStep } from './step.js'
6
7
  import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8
+ import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
7
9
  import Translation from './translation.js'
8
10
  import MochaFactory from './mocha/factory.js'
9
11
  import recorder from './recorder.js'
@@ -30,7 +32,7 @@ let container = {
30
32
  translation: {},
31
33
  /** @type {Result | null} */
32
34
  result: null,
33
- sharedKeys: new Set() // Track keys shared via share() function
35
+ sharedKeys: new Set(), // Track keys shared via share() function
34
36
  }
35
37
 
36
38
  /**
@@ -200,6 +202,15 @@ class Container {
200
202
  static append(newContainer) {
201
203
  container = deepMerge(container, newContainer)
202
204
 
205
+ // If new helpers are added, set the helpers property on them
206
+ if (newContainer.helpers) {
207
+ for (const name in newContainer.helpers) {
208
+ if (container.helpers[name] && typeof container.helpers[name] === 'object') {
209
+ container.helpers[name].helpers = container.helpers
210
+ }
211
+ }
212
+ }
213
+
203
214
  // If new support objects are added, update the proxy support
204
215
  if (newContainer.support) {
205
216
  const newProxySupport = createSupportObjects(newContainer.support)
@@ -248,10 +259,10 @@ class Container {
248
259
  // Instead of using append which replaces the entire container,
249
260
  // directly update the support object to maintain proxy references
250
261
  Object.assign(container.support, data)
251
-
262
+
252
263
  // Track which keys were explicitly shared
253
264
  Object.keys(data).forEach(key => container.sharedKeys.add(key))
254
-
265
+
255
266
  if (!options.local) {
256
267
  WorkerStorage.share(data)
257
268
  }
@@ -347,6 +358,16 @@ async function createHelpers(config) {
347
358
  }
348
359
  }
349
360
 
361
+ // Set helpers property on each helper to allow access to other helpers
362
+ for (const name in helpers) {
363
+ if (helpers[name] && typeof helpers[name] === 'object') {
364
+ helpers[name].helpers = helpers
365
+ }
366
+ }
367
+
368
+ // Wait for async helpers and call _init
369
+ await asyncHelperPromise
370
+
350
371
  for (const name in helpers) {
351
372
  if (helpers[name]._init) await helpers[name]._init()
352
373
  }
@@ -674,12 +695,50 @@ async function loadSupportObject(modulePath, supportObjectName) {
674
695
  try {
675
696
  // Use dynamic import for both ESM and CJS modules
676
697
  let importPath = modulePath
677
- // Append .js if no extension provided (ESM resolution requires it)
698
+ let tempJsFile = null
699
+
678
700
  if (typeof importPath === 'string') {
679
701
  const ext = path.extname(importPath)
680
- if (!ext) importPath = `${importPath}.js`
702
+
703
+ // Handle TypeScript files
704
+ if (ext === '.ts') {
705
+ try {
706
+ // Use the TypeScript transpilation utility
707
+ const typescript = await import('typescript')
708
+ const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
709
+
710
+ debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
711
+
712
+ // Attach cleanup handler
713
+ importPath = tempFile
714
+ // Store temp files list in a way that cleanup can access them
715
+ tempJsFile = allTempFiles
716
+ } catch (tsError) {
717
+ throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
718
+ }
719
+ } else if (!ext) {
720
+ // Append .js if no extension provided (ESM resolution requires it)
721
+ importPath = `${importPath}.js`
722
+ }
723
+ }
724
+
725
+ let obj
726
+ try {
727
+ obj = await import(importPath)
728
+ } catch (importError) {
729
+ // Clean up temp files if created before rethrowing
730
+ if (tempJsFile) {
731
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
732
+ cleanupTempFiles(filesToClean)
733
+ }
734
+ throw importError
735
+ } finally {
736
+ // Clean up temp files if created
737
+ if (tempJsFile) {
738
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
739
+ cleanupTempFiles(filesToClean)
740
+ }
681
741
  }
682
- const obj = await import(importPath)
683
742
 
684
743
  // Handle ESM module wrapper
685
744
  let actualObj = obj
@@ -1,4 +1,4 @@
1
- import Gherkin from '@cucumber/gherkin'
1
+ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from '@cucumber/gherkin'
2
2
  import { IdGenerator } from '@cucumber/messages'
3
3
  import { Context, Suite } from 'mocha'
4
4
  import debug from 'debug'
@@ -15,9 +15,9 @@ import { createTest } from './test.js'
15
15
  import { matchStep } from './bdd.js'
16
16
 
17
17
  const uuidFn = IdGenerator.uuid()
18
- const builder = new Gherkin.AstBuilder(uuidFn)
19
- const matcher = new Gherkin.GherkinClassicTokenMatcher()
20
- const parser = new Gherkin.Parser(builder, matcher)
18
+ const builder = new AstBuilder(uuidFn)
19
+ const matcher = new GherkinClassicTokenMatcher()
20
+ const parser = new Parser(builder, matcher)
21
21
  parser.stopAtFirstError = false
22
22
 
23
23
  const gherkinParser = (text, file) => {
@@ -143,7 +143,7 @@ export default function (config) {
143
143
 
144
144
  event.dispatcher.on(event.step.finished, step => {
145
145
  if (step.htmlReporterStartTime) {
146
- step.duration = Date.now() - step.htmlReporterStartTime
146
+ step.htmlReporterDuration = Date.now() - step.htmlReporterStartTime
147
147
  }
148
148
 
149
149
  // Serialize args immediately to preserve them through worker serialization
@@ -170,7 +170,7 @@ export default function (config) {
170
170
  actor: step.actor,
171
171
  args: serializedArgs,
172
172
  status: step.failed ? 'failed' : 'success',
173
- duration: step.duration || 0,
173
+ duration: step.htmlReporterDuration || step.duration || 0,
174
174
  })
175
175
  })
176
176
 
@@ -210,13 +210,13 @@ export default function (config) {
210
210
 
211
211
  event.dispatcher.on(event.bddStep.finished, step => {
212
212
  if (step.htmlReporterStartTime) {
213
- step.duration = Date.now() - step.htmlReporterStartTime
213
+ step.htmlReporterDuration = Date.now() - step.htmlReporterStartTime
214
214
  }
215
215
  currentBddSteps.push({
216
216
  keyword: step.actor || 'Given',
217
217
  text: step.name,
218
218
  status: step.failed ? 'failed' : 'success',
219
- duration: step.duration || 0,
219
+ duration: step.htmlReporterDuration || step.duration || 0,
220
220
  comment: step.comment,
221
221
  })
222
222
  })
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Utilities for checking TypeScript loader availability
3
+ */
4
+
5
+ /**
6
+ * Check if a TypeScript loader is available for test files
7
+ * Note: This checks if loaders are in the require array, not if packages are installed
8
+ * Package installation is checked when actually requiring modules
9
+ * @param {string[]} requiredModules - Array of required modules from config
10
+ * @returns {boolean}
11
+ */
12
+ export function checkTypeScriptLoader(requiredModules = []) {
13
+ // Check if a loader is configured in the require array
14
+ return (
15
+ requiredModules.includes('tsx/esm') ||
16
+ requiredModules.includes('tsx/cjs') ||
17
+ requiredModules.includes('tsx') ||
18
+ requiredModules.includes('ts-node/esm') ||
19
+ requiredModules.includes('ts-node/register') ||
20
+ requiredModules.includes('ts-node')
21
+ )
22
+ }
23
+
24
+ /**
25
+ * Generate helpful error message if .ts tests found but no loader configured
26
+ * @param {string[]} testFiles - Array of test file paths
27
+ * @returns {string|null} Error message or null if no TypeScript files
28
+ */
29
+ export function getTypeScriptLoaderError(testFiles) {
30
+ const tsFiles = testFiles.filter(f => f.endsWith('.ts'))
31
+
32
+ if (tsFiles.length === 0) return null
33
+
34
+ return `
35
+ ╔═════════════════════════════════════════════════════════════════════════════╗
36
+ ║ ║
37
+ ║ ⚠️ TypeScript Test Files Detected but No Loader Configured ║
38
+ ║ ║
39
+ ╚═════════════════════════════════════════════════════════════════════════════╝
40
+
41
+ Found ${tsFiles.length} TypeScript test file(s) but no TypeScript loader is configured.
42
+
43
+ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tests.
44
+
45
+ ┌─────────────────────────────────────────────────────────────────────────────┐
46
+ │ Option 1: tsx (Recommended - Fast, Zero Config) │
47
+ └─────────────────────────────────────────────────────────────────────────────┘
48
+
49
+ Installation:
50
+ npm install --save-dev tsx
51
+
52
+ Configuration:
53
+ Add to your codecept.conf.ts or codecept.conf.js:
54
+
55
+ export const config = {
56
+ tests: './**/*_test.ts',
57
+ require: ['tsx/cjs'], // ← Add this line
58
+ helpers: { /* ... */ }
59
+ }
60
+
61
+ Why tsx?
62
+ ⚡ Fast: Built on esbuild
63
+ 🎯 Zero config: No tsconfig.json required
64
+ ✅ Works with Mocha: Uses CommonJS hooks
65
+ ✅ Complete: Handles all TypeScript features
66
+
67
+ ┌─────────────────────────────────────────────────────────────────────────────┐
68
+ │ Option 2: ts-node/esm (Alternative - Established, Requires Config) │
69
+ └─────────────────────────────────────────────────────────────────────────────┘
70
+
71
+ Installation:
72
+ npm install --save-dev ts-node
73
+
74
+ Configuration:
75
+ 1. Add to your codecept.conf.ts:
76
+ require: ['ts-node/esm']
77
+
78
+ 2. Create tsconfig.json:
79
+ {
80
+ "compilerOptions": {
81
+ "module": "ESNext",
82
+ "target": "ES2022",
83
+ "moduleResolution": "node",
84
+ "esModuleInterop": true
85
+ },
86
+ "ts-node": {
87
+ "esm": true,
88
+ "experimentalSpecifierResolution": "node"
89
+ }
90
+ }
91
+
92
+ 📚 Documentation: https://codecept.io/typescript
93
+
94
+ Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
95
+ transpiled. Only test files require a loader to be configured.
96
+ `
97
+ }
98
+
99
+ /**
100
+ * Check if user is trying to run TypeScript tests without proper loader
101
+ * @param {string[]} testFiles - Array of test file paths
102
+ * @param {string[]} requiredModules - Array of required modules from config
103
+ * @returns {{hasError: boolean, message: string|null}}
104
+ */
105
+ export function validateTypeScriptSetup(testFiles, requiredModules = []) {
106
+ const tsFiles = testFiles.filter(f => f.endsWith('.ts'))
107
+
108
+ if (tsFiles.length === 0) {
109
+ // No TypeScript test files, all good
110
+ return { hasError: false, message: null }
111
+ }
112
+
113
+ // Check if a loader is configured in the require array
114
+ const hasLoader = checkTypeScriptLoader(requiredModules)
115
+
116
+ if (hasLoader) {
117
+ // Loader configured, all good (package will be checked when requireModules runs)
118
+ return { hasError: false, message: null }
119
+ }
120
+
121
+ // No loader configured and TypeScript tests exist
122
+ const message = getTypeScriptLoaderError(testFiles)
123
+ return { hasError: true, message }
124
+ }
@@ -0,0 +1,237 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * Transpile TypeScript files to ES modules with CommonJS shim support
6
+ * Handles recursive transpilation of imported TypeScript files
7
+ *
8
+ * @param {string} mainFilePath - Path to the main TypeScript file to transpile
9
+ * @param {object} typescript - TypeScript compiler instance
10
+ * @returns {Promise<{tempFile: string, allTempFiles: string[]}>} - Main temp file and all temp files created
11
+ */
12
+ export async function transpileTypeScript(mainFilePath, typescript) {
13
+ const { transpile } = typescript
14
+
15
+ /**
16
+ * Transpile a single TypeScript file to JavaScript
17
+ * Injects CommonJS shims (require, module, exports, __dirname, __filename) as needed
18
+ */
19
+ const transpileTS = (filePath) => {
20
+ const tsContent = fs.readFileSync(filePath, 'utf8')
21
+
22
+ // Transpile TypeScript to JavaScript with ES module output
23
+ let jsContent = transpile(tsContent, {
24
+ module: 99, // ModuleKind.ESNext
25
+ target: 99, // ScriptTarget.ESNext
26
+ esModuleInterop: true,
27
+ allowSyntheticDefaultImports: true,
28
+ lib: ['lib.esnext.d.ts'], // Enable latest features including top-level await
29
+ suppressOutputPathCheck: true,
30
+ skipLibCheck: true,
31
+ })
32
+
33
+ // Check if the code uses CommonJS globals
34
+ const usesCommonJSGlobals = /__dirname|__filename/.test(jsContent)
35
+ const usesRequire = /\brequire\s*\(/.test(jsContent)
36
+ const usesModuleExports = /\b(module\.exports|exports\.)/.test(jsContent)
37
+
38
+ if (usesCommonJSGlobals || usesRequire || usesModuleExports) {
39
+ // Inject ESM equivalents at the top of the file
40
+ let esmGlobals = ''
41
+
42
+ if (usesRequire || usesModuleExports) {
43
+ // IMPORTANT: Use the original .ts file path as the base for require()
44
+ // This ensures dynamic require() calls work with relative paths from the original file location
45
+ const originalFileUrl = `file://${filePath.replace(/\\/g, '/')}`
46
+ esmGlobals += `import { createRequire } from 'module';
47
+ import { extname as __extname } from 'path';
48
+ const __baseRequire = createRequire('${originalFileUrl}');
49
+
50
+ // Wrap require to auto-resolve extensions (mimics CommonJS behavior)
51
+ const require = (id) => {
52
+ try {
53
+ return __baseRequire(id);
54
+ } catch (err) {
55
+ // If module not found and it's a relative/absolute path without extension, try common extensions
56
+ if (err.code === 'MODULE_NOT_FOUND' && (id.startsWith('./') || id.startsWith('../') || id.startsWith('/'))) {
57
+ const ext = __extname(id);
58
+ // Only treat known file extensions as real extensions (so names like .TEST don't block probing)
59
+ const __knownExts = ['.js', '.cjs', '.mjs', '.json', '.node'];
60
+ const hasKnownExt = ext && __knownExts.includes(ext.toLowerCase());
61
+ if (!hasKnownExt) {
62
+ // Try common extensions in order: .js, .cjs, .json, .node
63
+ // Note: .ts files cannot be required - they need transpilation first
64
+ const extensions = ['.js', '.cjs', '.json', '.node'];
65
+ for (const testExt of extensions) {
66
+ try {
67
+ return __baseRequire(id + testExt);
68
+ } catch (e) {
69
+ // Continue to next extension
70
+ }
71
+ }
72
+ }
73
+ }
74
+ // Re-throw original error if all attempts failed
75
+ throw err;
76
+ }
77
+ };
78
+
79
+ const module = { exports: {} };
80
+ const exports = module.exports;
81
+
82
+ `
83
+ }
84
+
85
+ if (usesCommonJSGlobals) {
86
+ // For __dirname and __filename, also use the original file path
87
+ const originalFileUrl = `file://${filePath.replace(/\\/g, '/')}`
88
+ esmGlobals += `import { fileURLToPath as __fileURLToPath } from 'url';
89
+ import { dirname as __dirname_fn } from 'path';
90
+ const __filename = '${filePath.replace(/\\/g, '/')}';
91
+ const __dirname = __dirname_fn(__filename);
92
+
93
+ `
94
+ }
95
+
96
+ jsContent = esmGlobals + jsContent
97
+
98
+ // If module.exports is used, we need to export it as default
99
+ if (usesModuleExports) {
100
+ jsContent += `\nexport default module.exports;\n`
101
+ }
102
+ }
103
+
104
+ return jsContent
105
+ }
106
+
107
+ // Create a map to track transpiled files
108
+ const transpiledFiles = new Map()
109
+ const baseDir = path.dirname(mainFilePath)
110
+
111
+ // Recursive function to transpile a file and all its TypeScript dependencies
112
+ const transpileFileAndDeps = (filePath) => {
113
+ // Already transpiled, skip
114
+ if (transpiledFiles.has(filePath)) {
115
+ return
116
+ }
117
+
118
+ // Transpile this file
119
+ let jsContent = transpileTS(filePath)
120
+
121
+ // Find all relative TypeScript imports in this file
122
+ const importRegex = /from\s+['"](\..+?)(?:\.ts)?['"]/g
123
+ let match
124
+ const imports = []
125
+
126
+ while ((match = importRegex.exec(jsContent)) !== null) {
127
+ imports.push(match[1])
128
+ }
129
+
130
+ // Get the base directory for this file
131
+ const fileBaseDir = path.dirname(filePath)
132
+
133
+ // Recursively transpile each imported TypeScript file
134
+ for (const relativeImport of imports) {
135
+ let importedPath = path.resolve(fileBaseDir, relativeImport)
136
+
137
+ // Handle .js extensions that might actually be .ts files
138
+ if (importedPath.endsWith('.js')) {
139
+ const tsVersion = importedPath.replace(/\.js$/, '.ts')
140
+ if (fs.existsSync(tsVersion)) {
141
+ importedPath = tsVersion
142
+ }
143
+ }
144
+
145
+ // Try adding .ts extension if file doesn't exist and no extension provided
146
+ if (!path.extname(importedPath)) {
147
+ const tsPath = importedPath + '.ts'
148
+ if (fs.existsSync(tsPath)) {
149
+ importedPath = tsPath
150
+ } else {
151
+ // Try .js extension as well
152
+ const jsPath = importedPath + '.js'
153
+ if (fs.existsSync(jsPath)) {
154
+ // Skip .js files, they don't need transpilation
155
+ continue
156
+ }
157
+ }
158
+ }
159
+
160
+ // If it's a TypeScript file, recursively transpile it and its dependencies
161
+ if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
162
+ transpileFileAndDeps(importedPath)
163
+ }
164
+ }
165
+
166
+ // After all dependencies are transpiled, rewrite imports in this file
167
+ jsContent = jsContent.replace(
168
+ /from\s+['"](\..+?)(?:\.ts)?['"]/g,
169
+ (match, importPath) => {
170
+ let resolvedPath = path.resolve(fileBaseDir, importPath)
171
+
172
+ // Handle .js extension that might be .ts
173
+ if (resolvedPath.endsWith('.js')) {
174
+ const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
175
+ if (transpiledFiles.has(tsVersion)) {
176
+ const tempFile = transpiledFiles.get(tsVersion)
177
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
178
+ // Ensure the path starts with ./
179
+ if (!relPath.startsWith('.')) {
180
+ return `from './${relPath}'`
181
+ }
182
+ return `from '${relPath}'`
183
+ }
184
+ }
185
+
186
+ // Try with .ts extension
187
+ const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
188
+
189
+ // If we transpiled this file, use the temp file
190
+ if (transpiledFiles.has(tsPath)) {
191
+ const tempFile = transpiledFiles.get(tsPath)
192
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
193
+ // Ensure the path starts with ./
194
+ if (!relPath.startsWith('.')) {
195
+ return `from './${relPath}'`
196
+ }
197
+ return `from '${relPath}'`
198
+ }
199
+
200
+ // Otherwise, keep the import as-is
201
+ return match
202
+ }
203
+ )
204
+
205
+ // Write the transpiled file with updated imports
206
+ const tempFile = filePath.replace(/\.ts$/, '.temp.mjs')
207
+ fs.writeFileSync(tempFile, jsContent)
208
+ transpiledFiles.set(filePath, tempFile)
209
+ }
210
+
211
+ // Start recursive transpilation from the main file
212
+ transpileFileAndDeps(mainFilePath)
213
+
214
+ // Get the main transpiled file
215
+ const tempJsFile = transpiledFiles.get(mainFilePath)
216
+
217
+ // Store all temp files for cleanup
218
+ const allTempFiles = Array.from(transpiledFiles.values())
219
+
220
+ return { tempFile: tempJsFile, allTempFiles }
221
+ }
222
+
223
+ /**
224
+ * Clean up temporary transpiled files
225
+ * @param {string[]} tempFiles - Array of temp file paths to delete
226
+ */
227
+ export function cleanupTempFiles(tempFiles) {
228
+ for (const file of tempFiles) {
229
+ if (fs.existsSync(file)) {
230
+ try {
231
+ fs.unlinkSync(file)
232
+ } catch (err) {
233
+ // Ignore cleanup errors
234
+ }
235
+ }
236
+ }
237
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.0.0-beta.9.esm-aria",
3
+ "version": "4.0.1-beta.1",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [
@@ -183,6 +183,7 @@
183
183
  "ts-node": "10.9.2",
184
184
  "tsd": "^0.33.0",
185
185
  "tsd-jsdoc": "2.5.0",
186
+ "tsx": "^4.19.2",
186
187
  "typedoc": "0.28.13",
187
188
  "typedoc-plugin-markdown": "4.9.0",
188
189
  "typescript": "5.8.3",
@@ -191,6 +192,14 @@
191
192
  "xml2js": "0.6.2",
192
193
  "xpath": "0.0.34"
193
194
  },
195
+ "peerDependencies": {
196
+ "tsx": "^4.0.0"
197
+ },
198
+ "peerDependenciesMeta": {
199
+ "tsx": {
200
+ "optional": true
201
+ }
202
+ },
194
203
  "engines": {
195
204
  "node": ">=16.0",
196
205
  "npm": ">=5.6.0"
@@ -2742,6 +2742,7 @@ declare namespace CodeceptJS {
2742
2742
  * `grabStorageState({ indexedDB: true })`) IndexedDB data; treat as sensitive and do not commit.
2743
2743
  */
2744
2744
  // @ts-ignore
2745
+ // @ts-ignore
2745
2746
  type PlaywrightConfig = {
2746
2747
  url?: string;
2747
2748
  browser?: 'chromium' | 'firefox' | 'webkit' | 'electron';
@@ -6142,6 +6143,7 @@ declare namespace CodeceptJS {
6142
6143
  * @property [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
6143
6144
  */
6144
6145
  // @ts-ignore
6146
+ // @ts-ignore
6145
6147
  type PuppeteerConfig = {
6146
6148
  url: string;
6147
6149
  basicAuth?: any;
@@ -7987,6 +7989,7 @@ declare namespace CodeceptJS {
7987
7989
  * @property [maxUploadFileSize] - set the max content file size in MB when performing api calls.
7988
7990
  */
7989
7991
  // @ts-ignore
7992
+ // @ts-ignore
7990
7993
  type RESTConfig = {
7991
7994
  endpoint?: string;
7992
7995
  prettyPrintJson?: boolean;
@@ -9143,6 +9146,7 @@ declare namespace CodeceptJS {
9143
9146
  * @property [logLevel = silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
9144
9147
  */
9145
9148
  // @ts-ignore
9149
+ // @ts-ignore
9146
9150
  type WebDriverConfig = {
9147
9151
  url: string;
9148
9152
  browser: string;
@@ -2832,6 +2832,7 @@ declare namespace CodeceptJS {
2832
2832
  * `grabStorageState({ indexedDB: true })`) IndexedDB data; treat as sensitive and do not commit.
2833
2833
  */
2834
2834
  // @ts-ignore
2835
+ // @ts-ignore
2835
2836
  type PlaywrightConfig = {
2836
2837
  url?: string;
2837
2838
  browser?: 'chromium' | 'firefox' | 'webkit' | 'electron';
@@ -6383,6 +6384,7 @@ declare namespace CodeceptJS {
6383
6384
  * @property [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
6384
6385
  */
6385
6386
  // @ts-ignore
6387
+ // @ts-ignore
6386
6388
  type PuppeteerConfig = {
6387
6389
  url: string;
6388
6390
  basicAuth?: any;
@@ -8364,6 +8366,7 @@ declare namespace CodeceptJS {
8364
8366
  * @property [maxUploadFileSize] - set the max content file size in MB when performing api calls.
8365
8367
  */
8366
8368
  // @ts-ignore
8369
+ // @ts-ignore
8367
8370
  type RESTConfig = {
8368
8371
  endpoint?: string;
8369
8372
  prettyPrintJson?: boolean;
@@ -9580,6 +9583,7 @@ declare namespace CodeceptJS {
9580
9583
  * @property [logLevel = silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
9581
9584
  */
9582
9585
  // @ts-ignore
9586
+ // @ts-ignore
9583
9587
  type WebDriverConfig = {
9584
9588
  url: string;
9585
9589
  browser: string;