Skip to content

Commit 56d4d44

Browse files
committed
DRY
1 parent d735b33 commit 56d4d44

9 files changed

Lines changed: 68 additions & 45 deletions

File tree

bin/codecept.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import Codecept from '../lib/codecept.js'
55
import output from '../lib/output.js'
66
const { print, error } = output
77
import { printError } from '../lib/command/utils.js'
8-
import { isWindows } from '../lib/utils.js'
9-
import { pathToFileURL } from 'url'
8+
import { importModule } from '../lib/utils.js'
109

1110
const commandFlags = {
1211
ai: {
@@ -47,7 +46,7 @@ const errorHandler =
4746
}
4847

4948
const dynamicImport = async modulePath => {
50-
const module = isWindows() ? await import(pathToFileURL(modulePath).href) : await import(modulePath)
49+
const module = await importModule(modulePath)
5150
return module.default || module
5251
}
5352

lib/ai.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import output from './output.js'
44
import event from './event.js'
55
import { removeNonInteractiveElements, minifyHtml, splitByChunks } from './html.js'
66
import { generateText } from 'ai'
7-
import { fileURLToPath, pathToFileURL } from 'url'
7+
import { fileURLToPath } from 'url'
88
import path from 'path'
9-
import { fileExists, isWindows } from './utils.js'
9+
import { fileExists, importModule } from './utils.js'
1010
import store from './store.js'
1111

1212
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -34,7 +34,7 @@ async function loadPrompts() {
3434
}
3535

3636
try {
37-
const module = isWindows() ? await import(pathToFileURL(promptPath).href) : await import(promptPath)
37+
const module = await importModule(promptPath)
3838
prompts[name] = module.default || module
3939
debug(`Loaded prompt ${name} from ${promptPath}`)
4040
} catch (err) {

lib/codecept.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import event from './event.js'
1717
import runHook from './hooks.js'
1818
import ActorFactory from './actor.js'
1919
import output from './output.js'
20-
import { emptyFolder, isWindows } from './utils.js'
20+
import { emptyFolder, importModule } from './utils.js'
2121
import { initCodeceptGlobals } from './globals.js'
2222
import { validateTypeScriptSetup, getTSNodeESMWarning } from './utils/loaderCheck.js'
2323
import recorder from './recorder.js'
@@ -32,6 +32,7 @@ import globalTimeoutListener from './listener/globalTimeout.js'
3232
import globalRetryListener from './listener/globalRetry.js'
3333
import exitListener from './listener/exit.js'
3434
import emptyRunListener from './listener/emptyRun.js'
35+
import path from 'path'
3536

3637
/**
3738
* CodeceptJS runner
@@ -86,7 +87,7 @@ class Codecept {
8687
}
8788
}
8889
// Use dynamic import for ESM
89-
isWindows() ? await import(pathToFileURL(modulePath).href) : await import(modulePath)
90+
await importModule(modulePath)
9091
}
9192
}
9293
}
@@ -137,7 +138,7 @@ class Codecept {
137138
]
138139

139140
for (const modulePath of listenerModules) {
140-
const module = isWindows() ? await import(pathToFileURL(modulePath).href) : await import(modulePath)
141+
const module = await importModule(modulePath)
141142
runHook(module.default || module)
142143
}
143144
}

lib/config.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import fs from 'fs'
22
import path from 'path'
33
import { createRequire } from 'module'
4-
import { fileExists, isFile, deepMerge, deepClone, isWindows } from './utils.js'
4+
import { fileExists, isFile, deepMerge, deepClone, importModule } from './utils.js'
55
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
6-
import { pathToFileURL } from 'url'
76

87
const defaultConfig = {
98
output: './_output',
@@ -243,7 +242,7 @@ async function loadConfigFile(configFile) {
243242
allTempFiles = result.allTempFiles
244243
fileMapping = result.fileMapping
245244

246-
configModule = isWindows() ? await import(pathToFileURL(tempFile).href) : await import(tempFile)
245+
configModule = await importModule(tempFile)
247246
cleanupTempFiles(allTempFiles)
248247
} catch (err) {
249248
transpileError = err
@@ -259,12 +258,7 @@ async function loadConfigFile(configFile) {
259258
}
260259
} else {
261260
// Try ESM import first for JS files
262-
const resolvedImportPath =
263-
isWindows() && typeof configFile === 'string' && path.isAbsolute(configFile)
264-
? pathToFileURL(configFile).href
265-
: configFile
266-
267-
configModule = await import(resolvedImportPath)
261+
configModule = await importModule(configFile)
268262
}
269263
} catch (importError) {
270264
try {

lib/container.js

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
isAsyncFunction,
1313
installedLocally,
1414
deepMerge,
15-
isWindows,
15+
importModule,
1616
} from './utils.js'
1717
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
1818
import Translation from './translation.js'
@@ -25,7 +25,6 @@ import Result from './result.js'
2525
import ai from './ai.js'
2626
import actorFactory from './actor.js'
2727
import Config from './config.js'
28-
import { pathToFileURL } from 'url'
2928

3029
let asyncHelperPromise
3130

@@ -444,12 +443,7 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
444443
// For built-in helpers, use direct relative import with .js extension
445444
const helperPath = `${moduleName}.js`
446445

447-
const resolvedImportPath =
448-
isWindows() && typeof helperPath === 'string' && path.isAbsolute(helperPath)
449-
? pathToFileURL(helperPath).href
450-
: helperPath
451-
452-
const mod = await import(resolvedImportPath)
446+
const mod = await importModule(helperPath)
453447
HelperClass = mod.default || mod
454448
} catch (err) {
455449
throw err
@@ -487,12 +481,7 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
487481
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
488482
try {
489483
// Try dynamic import for both CommonJS and ESM modules
490-
const resolvedImportPath =
491-
isWindows() && typeof importPath === 'string' && path.isAbsolute(importPath)
492-
? pathToFileURL(importPath).href
493-
: importPath
494-
495-
const mod = await import(resolvedImportPath)
484+
const mod = await importModule(importPath)
496485

497486
if (!mod && !mod.default) {
498487
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
@@ -704,7 +693,7 @@ async function loadPluginAsync(modulePath, config) {
704693
let pluginMod
705694
try {
706695
// Try dynamic import first (works for both ESM and CJS)
707-
pluginMod = isWindows() ? await import(pathToFileURL(modulePath).href) : await import(modulePath)
696+
pluginMod = await importModule(modulePath)
708697
} catch (err) {
709698
throw new Error(`Could not load plugin from '${modulePath}': ${err.message}`)
710699
}
@@ -911,12 +900,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
911900

912901
let obj
913902
try {
914-
const resolvedImportPath =
915-
isWindows() && typeof importPath === 'string' && path.isAbsolute(importPath)
916-
? pathToFileURL(importPath).href
917-
: importPath
918-
919-
obj = await import(resolvedImportPath)
903+
obj = await importModule(importPath)
920904
} catch (importError) {
921905
if (fileMapping) {
922906
fixErrorStack(importError, fileMapping)

lib/helper/ApiDataFactory.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import path from 'path'
22
import Helper from '@codeceptjs/helper'
33
import REST from './REST.js'
44
import store from '../store.js'
5-
import { isWindows } from '../utils.js'
6-
import { pathToFileURL } from 'url'
5+
import { importModule } from '../utils.js'
76

87
/**
98
* Helper for managing remote data using REST API.
@@ -330,7 +329,7 @@ class ApiDataFactory extends Helper {
330329
modulePath = path.join(store.codeceptDir, modulePath)
331330
}
332331
// check if the new syntax `export default new Factory()` is used and loads the builder, otherwise loads the module that used old syntax `module.exports = new Factory()`.
333-
const module = isWindows() ? await import(pathToFileURL(modulePath).href) : await import(modulePath)
332+
const module = await importModule(modulePath)
334333
const builder = module.default || module
335334
return builder.build(data, options)
336335
} catch (err) {

lib/rerun.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import event from './event.js'
55
import BaseCodecept from './codecept.js'
66
import output from './output.js'
77
import { createRequire } from 'module'
8-
import { isWindows } from './utils.js'
9-
import { pathToFileURL } from 'url'
8+
import { importModule } from './utils.js'
109

1110
const require = createRequire(import.meta.url)
1211

@@ -53,7 +52,7 @@ class CodeceptRerunner extends BaseCodecept {
5352

5453
// Force reload the module by using a cache-busting query parameter
5554
const fileUrl = `${fsPath.resolve(file)}?t=${Date.now()}`
56-
isWindows() ? await import(pathToFileURL(fileUrl).href) : await import(fileUrl)
55+
await importModule(fileUrl)
5756
} catch (e) {
5857
console.error(`Error loading test file ${file}:`, e)
5958
}

lib/utils.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from 'fs'
22
import os from 'os'
33
import path from 'path'
4+
import { pathToFileURL } from 'url'
45
import { createRequire } from 'module'
56
import chalk from 'chalk'
67
import getFunctionArguments from 'fn-args'
@@ -38,6 +39,20 @@ export const isAsyncFunction = function (fn) {
3839
return fn[Symbol.toStringTag] === 'AsyncFunction'
3940
}
4041

42+
/**
43+
* Dynamically imports a module.
44+
* On Windows, absolute paths are converted to file URLs.
45+
* @param {string} modulePath - The path to the module.
46+
* @param {Object} [options] - Import options.
47+
* @returns {Promise<any>} The imported module.
48+
*/
49+
export const importModule = async function (modulePath, options) {
50+
if (isWindows() && typeof modulePath === 'string' && path.isAbsolute(modulePath)) {
51+
return import(pathToFileURL(modulePath).href, options)
52+
}
53+
return import(modulePath, options)
54+
}
55+
4156
export const fileExists = function (filePath) {
4257
return fs.existsSync(filePath)
4358
}

test/unit/utils_test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,36 @@ describe('utils', () => {
344344
expect(() => utils.requireWithFallback('unexisting-package', 'unexisting-package2')).to.throw(Error, 'Cannot find modules unexisting-package,unexisting-package2')
345345
})
346346
})
347+
348+
describe('#importModule', () => {
349+
let osStub
350+
351+
afterEach(() => {
352+
if (osStub) osStub.restore()
353+
})
354+
355+
it('should import a module', async () => {
356+
const module = await utils.importModule(path.join(__dirname, '../../lib/output.js'))
357+
expect(module.default).to.be.ok
358+
})
359+
360+
it('should import a module on Windows simulation', async () => {
361+
// Mock Windows
362+
osStub = sinon.stub(os, 'platform').callsFake(() => 'win32')
363+
364+
// Use an absolute path that exists
365+
const absolutePath = path.resolve(__dirname, '../../lib/output.js')
366+
367+
const module = await utils.importModule(absolutePath)
368+
expect(module.default).to.be.ok
369+
})
370+
371+
it('should import a relative module', async () => {
372+
// Relative to lib/utils.js where importModule is defined
373+
// lib/utils.js is in lib/
374+
// we want to import lib/output.js
375+
const module = await utils.importModule('./output.js')
376+
expect(module.default).to.be.ok
377+
})
378+
})
347379
})

0 commit comments

Comments
 (0)