Skip to content

Commit dcfc530

Browse files
authored
fix: some path issues on windows (#5595)
* support windows * fix path issues on test * fix path issues on test * fix path issues on test * fix path issues on test * fix path issues on test * fix path issues on test * resolve load config file" * DRY * DRY * DRY * DRY * DRY * fix: uts * fix: uts * fix: uts * relax conditions * relax conditions * debug * debug * fix uts * fix uts * fix uts * fix uts * fix uts * fix uts
1 parent 4f1eb3f commit dcfc530

18 files changed

Lines changed: 302 additions & 169 deletions

File tree

.github/workflows/test.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020

2121
strategy:
2222
matrix:
23-
node-version: [20.x]
23+
node-version: [26.x]
2424

2525
steps:
2626
- uses: actions/checkout@v6
@@ -53,6 +53,27 @@ jobs:
5353
- name: Stop mock server
5454
run: npm run mock-server:stop
5555

56+
unit-tests-windows:
57+
name: Unit tests
58+
runs-on: windows-latest
59+
timeout-minutes: 15
60+
61+
strategy:
62+
matrix:
63+
node-version: [ 20.x ]
64+
65+
steps:
66+
- uses: actions/checkout@v6
67+
- name: Use Node.js ${{ matrix.node-version }}
68+
uses: actions/setup-node@v6
69+
with:
70+
node-version: ${{ matrix.node-version }}
71+
- run: npm i
72+
env:
73+
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true
74+
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
75+
- run: npm run test:unit:windows
76+
5677
runner-tests:
5778
name: Runner tests
5879
runs-on: ubuntu-22.04

bin/codecept.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +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 { resolveImportModulePath } from '../lib/utils.js'
89

910
const commandFlags = {
1011
ai: {
@@ -45,7 +46,8 @@ const errorHandler =
4546
}
4647

4748
const dynamicImport = async modulePath => {
48-
const module = await import(modulePath)
49+
const resolvedPath = resolveImportModulePath(modulePath)
50+
const module = await import(resolvedPath)
4951
return module.default || module
5052
}
5153

lib/ai.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { removeNonInteractiveElements, minifyHtml, splitByChunks } from './html.
66
import { generateText } from 'ai'
77
import { fileURLToPath } from 'url'
88
import path from 'path'
9-
import { fileExists } from './utils.js'
9+
import { fileExists, resolveImportModulePath } from './utils.js'
1010
import store from './store.js'
1111

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

3636
try {
37-
const module = await import(promptPath)
37+
const resolvedPath = resolveImportModulePath(promptPath)
38+
const module = await import(resolvedPath)
3839
prompts[name] = module.default || module
3940
debug(`Loaded prompt ${name} from ${promptPath}`)
4041
} catch (err) {

lib/codecept.js

Lines changed: 6 additions & 4 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 } from './utils.js'
20+
import { emptyFolder, resolveImportModulePath } from './utils.js'
2121
import { initCodeceptGlobals } from './globals.js'
2222
import { validateTypeScriptSetup, getTSNodeESMWarning } from './utils/loaderCheck.js'
2323
import recorder from './recorder.js'
@@ -73,7 +73,7 @@ class Codecept {
7373
// For npm packages, resolve from the user's directory
7474
// This ensures packages like tsx are found in user's node_modules
7575
const userDir = store.codeceptDir || process.cwd()
76-
76+
7777
try {
7878
// Use createRequire to resolve from user's directory
7979
const userRequire = createRequire(pathToFileURL(resolve(userDir, 'package.json')).href)
@@ -86,7 +86,8 @@ class Codecept {
8686
}
8787
}
8888
// Use dynamic import for ESM
89-
await import(modulePath)
89+
const resolvedPath = resolveImportModulePath(modulePath)
90+
await import(resolvedPath)
9091
}
9192
}
9293
}
@@ -137,7 +138,8 @@ class Codecept {
137138
]
138139

139140
for (const modulePath of listenerModules) {
140-
const module = await import(modulePath)
141+
const resolvedPath = resolveImportModulePath(modulePath)
142+
const module = await import(resolvedPath)
141143
runHook(module.default || module)
142144
}
143145
}

lib/command/run-rerun.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Codecept from '../rerun.js'
55
export default async function (test, options) {
66
// registering options globally to use in config
77
// Backward compatibility for --profile
8+
89
process.profile = options.profile
910
process.env.profile = options.profile
1011
const configFile = options.config

lib/config.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'fs'
22
import path from 'path'
33
import { createRequire } from 'module'
4-
import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
4+
import { fileExists, isFile, deepMerge, deepClone, resolveImportModulePath } from './utils.js'
55
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
66

77
const defaultConfig = {
@@ -96,7 +96,7 @@ class Config {
9696
// Try different extensions if the file doesn't exist
9797
const extensions = ['.ts', '.cjs', '.mjs']
9898
let found = false
99-
99+
100100
for (const ext of extensions) {
101101
const altConfig = configFile.replace(/\.js$/, ext)
102102
if (fileExists(altConfig)) {
@@ -105,7 +105,7 @@ class Config {
105105
break
106106
}
107107
}
108-
108+
109109
if (!found) {
110110
throw new Error(`Config file ${configFile} does not exist. Execute 'codeceptjs init' to create config`)
111111
}
@@ -242,7 +242,8 @@ async function loadConfigFile(configFile) {
242242
allTempFiles = result.allTempFiles
243243
fileMapping = result.fileMapping
244244

245-
configModule = await import(tempFile)
245+
const resolvedPath = resolveImportModulePath(tempFile)
246+
configModule = await import(resolvedPath)
246247
cleanupTempFiles(allTempFiles)
247248
} catch (err) {
248249
transpileError = err
@@ -258,7 +259,8 @@ async function loadConfigFile(configFile) {
258259
}
259260
} else {
260261
// Try ESM import first for JS files
261-
configModule = await import(configFile)
262+
const resolvedPath = resolveImportModulePath(configFile)
263+
configModule = await import(resolvedPath)
262264
}
263265
} catch (importError) {
264266
try {

lib/container.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ import { isMainThread } from 'worker_threads'
55
import debugModule from 'debug'
66
const debug = debugModule('codeceptjs:container')
77
import { MetaStep } from './step.js'
8-
import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8+
import {
9+
methodsOfObject,
10+
fileExists,
11+
isFunction,
12+
isAsyncFunction,
13+
installedLocally,
14+
deepMerge,
15+
resolveImportModulePath,
16+
} from './utils.js'
917
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
1018
import Translation from './translation.js'
1119
import MochaFactory from './mocha/factory.js'
@@ -434,7 +442,9 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
434442
try {
435443
// For built-in helpers, use direct relative import with .js extension
436444
const helperPath = `${moduleName}.js`
437-
const mod = await import(helperPath)
445+
446+
const resolvedPath = resolveImportModulePath(helperPath)
447+
const mod = await import(resolvedPath)
438448
HelperClass = mod.default || mod
439449
} catch (err) {
440450
throw err
@@ -472,7 +482,9 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
472482
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
473483
try {
474484
// Try dynamic import for both CommonJS and ESM modules
475-
const mod = await import(importPath)
485+
const resolvedPath = resolveImportModulePath(importPath)
486+
const mod = await import(resolvedPath)
487+
476488
if (!mod && !mod.default) {
477489
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
478490
}
@@ -488,7 +500,7 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
488500
if (fileMapping) {
489501
fixErrorStack(err, fileMapping)
490502
}
491-
503+
492504
// Clean up temp files before rethrowing
493505
if (tempJsFile) {
494506
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
@@ -683,7 +695,8 @@ async function loadPluginAsync(modulePath, config) {
683695
let pluginMod
684696
try {
685697
// Try dynamic import first (works for both ESM and CJS)
686-
pluginMod = await import(modulePath)
698+
const resolvedPath = resolveImportModulePath(modulePath)
699+
pluginMod = await import(resolvedPath)
687700
} catch (err) {
688701
throw new Error(`Could not load plugin from '${modulePath}': ${err.message}`)
689702
}
@@ -890,21 +903,19 @@ async function loadSupportObject(modulePath, supportObjectName) {
890903

891904
let obj
892905
try {
893-
obj = await import(importPath)
906+
const resolvedPath = resolveImportModulePath(importPath)
907+
obj = await import(resolvedPath)
894908
} catch (importError) {
895-
// Fix error stack to point to original .ts files
896909
if (fileMapping) {
897910
fixErrorStack(importError, fileMapping)
898911
}
899-
900-
// Clean up temp files if created before rethrowing
912+
901913
if (tempJsFile) {
902914
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
903915
cleanupTempFiles(filesToClean)
904916
}
905917
throw importError
906918
} finally {
907-
// Clean up temp files if created
908919
if (tempJsFile) {
909920
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
910921
cleanupTempFiles(filesToClean)

lib/helper/ApiDataFactory.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +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 { resolveImportModulePath } from '../utils.js'
56

67
/**
78
* Helper for managing remote data using REST API.
@@ -328,7 +329,8 @@ class ApiDataFactory extends Helper {
328329
modulePath = path.join(store.codeceptDir, modulePath)
329330
}
330331
// 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()`.
331-
const module = await import(modulePath)
332+
const resolvedPath = resolveImportModulePath(modulePath)
333+
const module = await import(resolvedPath)
332334
const builder = module.default || module
333335
return builder.build(data, options)
334336
} catch (err) {

lib/rerun.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +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 { resolveImportModulePath } from './utils.js'
89

910
const require = createRequire(import.meta.url)
1011

@@ -50,8 +51,9 @@ class CodeceptRerunner extends BaseCodecept {
5051
}
5152

5253
// Force reload the module by using a cache-busting query parameter
53-
const fileUrl = `${fsPath.resolve(file)}?t=${Date.now()}`
54-
await import(fileUrl)
54+
const fileUrl = `${fsPath.resolve(file)}`
55+
const resolvedPath = resolveImportModulePath(fileUrl)
56+
await import(resolvedPath)
5557
} catch (e) {
5658
console.error(`Error loading test file ${file}:`, e)
5759
}

lib/translation.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ class Translation {
1616

1717
loadVocabulary(vocabularyFile) {
1818
if (!vocabularyFile) return
19-
const filePath = path.join(store.codeceptDir, vocabularyFile)
19+
20+
let filePath;
21+
if (path.isAbsolute(vocabularyFile)) {
22+
filePath = vocabularyFile;
23+
} else {
24+
filePath = path.join(store.codeceptDir, vocabularyFile);
25+
}
2026

2127
try {
2228
const require = createRequire(import.meta.url)

0 commit comments

Comments
 (0)