codeceptjs 4.0.3 → 4.0.5
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 +3 -1
- package/docs/agents.md +32 -25
- package/lib/ai.js +3 -2
- package/lib/codecept.js +9 -4
- package/lib/command/info.js +19 -13
- package/lib/command/init.js +4 -6
- package/lib/command/run-rerun.js +1 -0
- package/lib/config.js +7 -5
- package/lib/container.js +23 -12
- package/lib/helper/ApiDataFactory.js +3 -1
- package/lib/pause.js +1 -1
- package/lib/rerun.js +4 -2
- package/lib/translation.js +7 -1
- package/lib/utils.js +22 -1
- package/package.json +7 -6
- package/typings/index.d.ts +7 -27
package/bin/codecept.js
CHANGED
|
@@ -5,6 +5,7 @@ import Codecept from '../lib/codecept.js'
|
|
|
5
5
|
import output from '../lib/output.js'
|
|
6
6
|
const { print, error } = output
|
|
7
7
|
import { printError } from '../lib/command/utils.js'
|
|
8
|
+
import { resolveImportModulePath } from '../lib/utils.js'
|
|
8
9
|
|
|
9
10
|
const commandFlags = {
|
|
10
11
|
ai: {
|
|
@@ -45,7 +46,8 @@ const errorHandler =
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
const dynamicImport = async modulePath => {
|
|
48
|
-
const
|
|
49
|
+
const resolvedPath = resolveImportModulePath(modulePath)
|
|
50
|
+
const module = await import(resolvedPath)
|
|
49
51
|
return module.default || module
|
|
50
52
|
}
|
|
51
53
|
|
package/docs/agents.md
CHANGED
|
@@ -23,6 +23,38 @@ Agents get full control over test and browser execution:
|
|
|
23
23
|
|
|
24
24
|
CodeceptJS is token-efficient: it stores HTML, ARIA, logs, and HTTP request data as files instead of streaming them through MCP. Agents read these files with their native shell tools—no extra API calls, no redundant context.
|
|
25
25
|
|
|
26
|
+
## Essential Setup
|
|
27
|
+
|
|
28
|
+
Two things make agent testing work: the **skills** that teach the agent CodeceptJS, and the **MCP server** that lets it drive the browser. Set both up once, from your project directory.
|
|
29
|
+
|
|
30
|
+
Install the skills:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx skills add codeceptjs/skills
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Connect the MCP server (`npx codeceptjs-mcp`).
|
|
37
|
+
|
|
38
|
+
**Claude Code:**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
claude mcp add codeceptjs -- npx codeceptjs-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Codex:**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
codex mcp add codeceptjs -- npx codeceptjs-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Cursor** — add to `.cursor/mcp.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{ "mcpServers": { "codeceptjs": { "command": "npx", "args": ["codeceptjs-mcp"] } } }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
See [/mcp](/mcp) for full client setup. Now the agent is ready to run the loop.
|
|
57
|
+
|
|
26
58
|
## The loop
|
|
27
59
|
|
|
28
60
|
Whether the agent is writing a new test or fixing an old one, it follows the same cycle.
|
|
@@ -96,31 +128,6 @@ Only `url` is inline. The rest are paths the agent opens with the right tool:
|
|
|
96
128
|
|
|
97
129
|
Saved HTML is formatted, with non-semantic elements stripped out: `<style>`, `<script>`, Tailwind-style trash classes, and inline `style=""` attributes. `grep` can then effectively find the correct tree branch in raw page source. ARIA snapshots are smaller and more structured than HTML, which is why the agent prefers them when picking locators.
|
|
98
130
|
|
|
99
|
-
## Setup
|
|
100
|
-
|
|
101
|
-
When CodeceptJS is installed, the MCP server can be launched with this command:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
npx codeceptjs-mcp
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
> See [/mcp](/mcp) for detailed client setup.
|
|
108
|
-
|
|
109
|
-
We recommend pairing CodeceptJS MCP with the skills bundle.
|
|
110
|
-
|
|
111
|
-
Install for any agent:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
npx skills add codeceptjs/skills
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Or, in Claude Code:
|
|
118
|
-
|
|
119
|
-
```text
|
|
120
|
-
/plugin marketplace add codeceptjs/skills
|
|
121
|
-
/plugin install codeceptjs@codeceptjs-skills
|
|
122
|
-
```
|
|
123
|
-
|
|
124
131
|
## Usage Examples
|
|
125
132
|
|
|
126
133
|
When MCP and skills are connected, the agent receives predefined workflows and can act effectively for testing purposes. Common scenarios it handles:
|
package/lib/ai.js
CHANGED
|
@@ -6,7 +6,7 @@ import { removeNonInteractiveElements, minifyHtml, splitByChunks } from './html.
|
|
|
6
6
|
import { generateText } from 'ai'
|
|
7
7
|
import { fileURLToPath } from 'url'
|
|
8
8
|
import path from 'path'
|
|
9
|
-
import { fileExists } from './utils.js'
|
|
9
|
+
import { fileExists, resolveImportModulePath } from './utils.js'
|
|
10
10
|
import store from './store.js'
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -34,7 +34,8 @@ async function loadPrompts() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
|
-
const
|
|
37
|
+
const resolvedPath = resolveImportModulePath(promptPath)
|
|
38
|
+
const module = await import(resolvedPath)
|
|
38
39
|
prompts[name] = module.default || module
|
|
39
40
|
debug(`Loaded prompt ${name} from ${promptPath}`)
|
|
40
41
|
} catch (err) {
|
package/lib/codecept.js
CHANGED
|
@@ -17,7 +17,7 @@ import event from './event.js'
|
|
|
17
17
|
import runHook from './hooks.js'
|
|
18
18
|
import ActorFactory from './actor.js'
|
|
19
19
|
import output from './output.js'
|
|
20
|
-
import { emptyFolder } from './utils.js'
|
|
20
|
+
import { emptyFolder, resolveImportModulePath } from './utils.js'
|
|
21
21
|
import { initCodeceptGlobals } from './globals.js'
|
|
22
22
|
import { validateTypeScriptSetup, getTSNodeESMWarning } from './utils/loaderCheck.js'
|
|
23
23
|
import recorder from './recorder.js'
|
|
@@ -73,7 +73,7 @@ class Codecept {
|
|
|
73
73
|
// For npm packages, resolve from the user's directory
|
|
74
74
|
// This ensures packages like tsx are found in user's node_modules
|
|
75
75
|
const userDir = store.codeceptDir || process.cwd()
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
try {
|
|
78
78
|
// Use createRequire to resolve from user's directory
|
|
79
79
|
const userRequire = createRequire(pathToFileURL(resolve(userDir, 'package.json')).href)
|
|
@@ -86,7 +86,8 @@ class Codecept {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
// Use dynamic import for ESM
|
|
89
|
-
|
|
89
|
+
const resolvedPath = resolveImportModulePath(modulePath)
|
|
90
|
+
await import(resolvedPath)
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -137,7 +138,8 @@ class Codecept {
|
|
|
137
138
|
]
|
|
138
139
|
|
|
139
140
|
for (const modulePath of listenerModules) {
|
|
140
|
-
const
|
|
141
|
+
const resolvedPath = resolveImportModulePath(modulePath)
|
|
142
|
+
const module = await import(resolvedPath)
|
|
141
143
|
runHook(module.default || module)
|
|
142
144
|
}
|
|
143
145
|
}
|
|
@@ -287,6 +289,9 @@ class Codecept {
|
|
|
287
289
|
// Ignore if gherkin module not available
|
|
288
290
|
}
|
|
289
291
|
|
|
292
|
+
// Sort test files alphabetically for consistent execution order
|
|
293
|
+
this.testFiles.sort()
|
|
294
|
+
|
|
290
295
|
return new Promise((resolve, reject) => {
|
|
291
296
|
const mocha = container.mocha()
|
|
292
297
|
mocha.files = this.testFiles
|
package/lib/command/info.js
CHANGED
|
@@ -5,22 +5,26 @@ import Codecept from '../codecept.js'
|
|
|
5
5
|
import output from '../output.js'
|
|
6
6
|
import { execSync } from 'child_process'
|
|
7
7
|
|
|
8
|
+
// Unified regex for both formats (excludes chromium-headless-shell):
|
|
9
|
+
// - 1.58+: "Firefox 146.0.1 (playwright firefox v1509)"
|
|
10
|
+
// - 1.57: "browser: firefox version 144.0.2"
|
|
11
|
+
const playwrightBrowserRegex = /(?:([\d.]+)\s+\(playwright\s+(chromium|firefox|webkit)\s)|(?:browser:\s*(chromium|firefox|webkit)\s+version\s+([\d.]+))/gi
|
|
12
|
+
|
|
13
|
+
function parsePlaywrightBrowsers(output) {
|
|
14
|
+
const versions = []
|
|
15
|
+
const matches = [...output.matchAll(playwrightBrowserRegex)]
|
|
16
|
+
matches.forEach(match => {
|
|
17
|
+
const browser = match[2] || match[3]
|
|
18
|
+
const version = match[1] || match[4]
|
|
19
|
+
versions.push(`${browser}: ${version}`)
|
|
20
|
+
})
|
|
21
|
+
return versions.join(', ')
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
async function getPlaywrightBrowsers() {
|
|
9
25
|
try {
|
|
10
|
-
const regex = /(chromium|firefox|webkit)\s+version\s+([\d.]+)/gi
|
|
11
|
-
let versions = []
|
|
12
|
-
|
|
13
26
|
const info = execSync('npx playwright install --dry-run').toString().trim()
|
|
14
|
-
|
|
15
|
-
const matches = [...info.matchAll(regex)]
|
|
16
|
-
|
|
17
|
-
matches.forEach(match => {
|
|
18
|
-
const browser = match[1]
|
|
19
|
-
const version = match[2]
|
|
20
|
-
versions.push(`${browser}: ${version}`)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
return versions.join(', ')
|
|
27
|
+
return parsePlaywrightBrowsers(info)
|
|
24
28
|
} catch (err) {
|
|
25
29
|
return 'Playwright not installed'
|
|
26
30
|
}
|
|
@@ -73,6 +77,8 @@ export default async function (path) {
|
|
|
73
77
|
output.print('***************************************')
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
export { parsePlaywrightBrowsers }
|
|
81
|
+
|
|
76
82
|
export const getMachineInfo = async () => {
|
|
77
83
|
const info = {
|
|
78
84
|
nodeInfo: await envinfo.helpers.getNodeInfo(),
|
package/lib/command/init.js
CHANGED
|
@@ -245,14 +245,12 @@ export default async function (initPath, options = {}) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
const tsconfig = {
|
|
248
|
-
'ts-node': {
|
|
249
|
-
files: true,
|
|
250
|
-
},
|
|
251
248
|
compilerOptions: {
|
|
252
|
-
target: '
|
|
253
|
-
lib: ['
|
|
249
|
+
target: 'ES2022',
|
|
250
|
+
lib: ['ES2022', 'DOM'],
|
|
254
251
|
esModuleInterop: true,
|
|
255
|
-
module: '
|
|
252
|
+
module: 'ESNext',
|
|
253
|
+
moduleResolution: 'bundler',
|
|
256
254
|
strictNullChecks: false,
|
|
257
255
|
types: ['codeceptjs', 'node'],
|
|
258
256
|
declaration: true,
|
package/lib/command/run-rerun.js
CHANGED
|
@@ -5,6 +5,7 @@ import Codecept from '../rerun.js'
|
|
|
5
5
|
export default async function (test, options) {
|
|
6
6
|
// registering options globally to use in config
|
|
7
7
|
// Backward compatibility for --profile
|
|
8
|
+
|
|
8
9
|
process.profile = options.profile
|
|
9
10
|
process.env.profile = options.profile
|
|
10
11
|
const configFile = options.config
|
package/lib/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { createRequire } from 'module'
|
|
4
|
-
import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
|
|
4
|
+
import { fileExists, isFile, deepMerge, deepClone, resolveImportModulePath } from './utils.js'
|
|
5
5
|
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
|
|
6
6
|
|
|
7
7
|
const defaultConfig = {
|
|
@@ -96,7 +96,7 @@ class Config {
|
|
|
96
96
|
// Try different extensions if the file doesn't exist
|
|
97
97
|
const extensions = ['.ts', '.cjs', '.mjs']
|
|
98
98
|
let found = false
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
for (const ext of extensions) {
|
|
101
101
|
const altConfig = configFile.replace(/\.js$/, ext)
|
|
102
102
|
if (fileExists(altConfig)) {
|
|
@@ -105,7 +105,7 @@ class Config {
|
|
|
105
105
|
break
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
if (!found) {
|
|
110
110
|
throw new Error(`Config file ${configFile} does not exist. Execute 'codeceptjs init' to create config`)
|
|
111
111
|
}
|
|
@@ -242,7 +242,8 @@ async function loadConfigFile(configFile) {
|
|
|
242
242
|
allTempFiles = result.allTempFiles
|
|
243
243
|
fileMapping = result.fileMapping
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
const resolvedPath = resolveImportModulePath(tempFile)
|
|
246
|
+
configModule = await import(resolvedPath)
|
|
246
247
|
cleanupTempFiles(allTempFiles)
|
|
247
248
|
} catch (err) {
|
|
248
249
|
transpileError = err
|
|
@@ -258,7 +259,8 @@ async function loadConfigFile(configFile) {
|
|
|
258
259
|
}
|
|
259
260
|
} else {
|
|
260
261
|
// Try ESM import first for JS files
|
|
261
|
-
|
|
262
|
+
const resolvedPath = resolveImportModulePath(configFile)
|
|
263
|
+
configModule = await import(resolvedPath)
|
|
262
264
|
}
|
|
263
265
|
} catch (importError) {
|
|
264
266
|
try {
|
package/lib/container.js
CHANGED
|
@@ -5,7 +5,15 @@ import { isMainThread } from 'worker_threads'
|
|
|
5
5
|
import debugModule from 'debug'
|
|
6
6
|
const debug = debugModule('codeceptjs:container')
|
|
7
7
|
import { MetaStep } from './step.js'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
methodsOfObject,
|
|
10
|
+
fileExists,
|
|
11
|
+
isFunction,
|
|
12
|
+
isAsyncFunction,
|
|
13
|
+
installedLocally,
|
|
14
|
+
deepMerge,
|
|
15
|
+
resolveImportModulePath,
|
|
16
|
+
} from './utils.js'
|
|
9
17
|
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
|
|
10
18
|
import Translation from './translation.js'
|
|
11
19
|
import MochaFactory from './mocha/factory.js'
|
|
@@ -434,7 +442,9 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
|
434
442
|
try {
|
|
435
443
|
// For built-in helpers, use direct relative import with .js extension
|
|
436
444
|
const helperPath = `${moduleName}.js`
|
|
437
|
-
|
|
445
|
+
|
|
446
|
+
const resolvedPath = resolveImportModulePath(helperPath)
|
|
447
|
+
const mod = await import(resolvedPath)
|
|
438
448
|
HelperClass = mod.default || mod
|
|
439
449
|
} catch (err) {
|
|
440
450
|
throw err
|
|
@@ -449,7 +459,7 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
|
449
459
|
if (ext === '.ts') {
|
|
450
460
|
try {
|
|
451
461
|
// Use the TypeScript transpilation utility
|
|
452
|
-
const typescript = await import('typescript')
|
|
462
|
+
const typescript = ((await import('typescript')).default || (await import('typescript')))
|
|
453
463
|
const { tempFile, allTempFiles, fileMapping: mapping } = await transpileTypeScript(importPath, typescript)
|
|
454
464
|
|
|
455
465
|
debug(`Transpiled TypeScript helper: ${importPath} -> ${tempFile}`)
|
|
@@ -472,7 +482,9 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
|
472
482
|
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
473
483
|
try {
|
|
474
484
|
// Try dynamic import for both CommonJS and ESM modules
|
|
475
|
-
const
|
|
485
|
+
const resolvedPath = resolveImportModulePath(importPath)
|
|
486
|
+
const mod = await import(resolvedPath)
|
|
487
|
+
|
|
476
488
|
if (!mod && !mod.default) {
|
|
477
489
|
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
478
490
|
}
|
|
@@ -488,7 +500,7 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
|
488
500
|
if (fileMapping) {
|
|
489
501
|
fixErrorStack(err, fileMapping)
|
|
490
502
|
}
|
|
491
|
-
|
|
503
|
+
|
|
492
504
|
// Clean up temp files before rethrowing
|
|
493
505
|
if (tempJsFile) {
|
|
494
506
|
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
|
|
@@ -683,7 +695,8 @@ async function loadPluginAsync(modulePath, config) {
|
|
|
683
695
|
let pluginMod
|
|
684
696
|
try {
|
|
685
697
|
// Try dynamic import first (works for both ESM and CJS)
|
|
686
|
-
|
|
698
|
+
const resolvedPath = resolveImportModulePath(modulePath)
|
|
699
|
+
pluginMod = await import(resolvedPath)
|
|
687
700
|
} catch (err) {
|
|
688
701
|
throw new Error(`Could not load plugin from '${modulePath}': ${err.message}`)
|
|
689
702
|
}
|
|
@@ -862,7 +875,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
|
|
|
862
875
|
if (ext === '.ts') {
|
|
863
876
|
try {
|
|
864
877
|
// Use the TypeScript transpilation utility
|
|
865
|
-
const typescript = await import('typescript')
|
|
878
|
+
const typescript = ((await import('typescript')).default || (await import('typescript')))
|
|
866
879
|
const { tempFile, allTempFiles, fileMapping: mapping } = await transpileTypeScript(importPath, typescript)
|
|
867
880
|
|
|
868
881
|
debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
|
|
@@ -890,21 +903,19 @@ async function loadSupportObject(modulePath, supportObjectName) {
|
|
|
890
903
|
|
|
891
904
|
let obj
|
|
892
905
|
try {
|
|
893
|
-
|
|
906
|
+
const resolvedPath = resolveImportModulePath(importPath)
|
|
907
|
+
obj = await import(resolvedPath)
|
|
894
908
|
} catch (importError) {
|
|
895
|
-
// Fix error stack to point to original .ts files
|
|
896
909
|
if (fileMapping) {
|
|
897
910
|
fixErrorStack(importError, fileMapping)
|
|
898
911
|
}
|
|
899
|
-
|
|
900
|
-
// Clean up temp files if created before rethrowing
|
|
912
|
+
|
|
901
913
|
if (tempJsFile) {
|
|
902
914
|
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
|
|
903
915
|
cleanupTempFiles(filesToClean)
|
|
904
916
|
}
|
|
905
917
|
throw importError
|
|
906
918
|
} finally {
|
|
907
|
-
// Clean up temp files if created
|
|
908
919
|
if (tempJsFile) {
|
|
909
920
|
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
|
|
910
921
|
cleanupTempFiles(filesToClean)
|
|
@@ -2,6 +2,7 @@ import path from 'path'
|
|
|
2
2
|
import Helper from '@codeceptjs/helper'
|
|
3
3
|
import REST from './REST.js'
|
|
4
4
|
import store from '../store.js'
|
|
5
|
+
import { resolveImportModulePath } from '../utils.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Helper for managing remote data using REST API.
|
|
@@ -328,7 +329,8 @@ class ApiDataFactory extends Helper {
|
|
|
328
329
|
modulePath = path.join(store.codeceptDir, modulePath)
|
|
329
330
|
}
|
|
330
331
|
// 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
|
|
332
|
+
const resolvedPath = resolveImportModulePath(modulePath)
|
|
333
|
+
const module = await import(resolvedPath)
|
|
332
334
|
const builder = module.default || module
|
|
333
335
|
return builder.build(data, options)
|
|
334
336
|
} catch (err) {
|
package/lib/pause.js
CHANGED
|
@@ -105,7 +105,7 @@ async function parseInput(cmd) {
|
|
|
105
105
|
recorder.session.start('pause')
|
|
106
106
|
if (cmd === '') next = true
|
|
107
107
|
if (!cmd || cmd === 'resume' || cmd === 'exit') {
|
|
108
|
-
finish()
|
|
108
|
+
if (typeof finish === 'function') finish()
|
|
109
109
|
recorder.session.restore('pause')
|
|
110
110
|
rl.close()
|
|
111
111
|
history.save()
|
package/lib/rerun.js
CHANGED
|
@@ -5,6 +5,7 @@ import event from './event.js'
|
|
|
5
5
|
import BaseCodecept from './codecept.js'
|
|
6
6
|
import output from './output.js'
|
|
7
7
|
import { createRequire } from 'module'
|
|
8
|
+
import { resolveImportModulePath } from './utils.js'
|
|
8
9
|
|
|
9
10
|
const require = createRequire(import.meta.url)
|
|
10
11
|
|
|
@@ -50,8 +51,9 @@ class CodeceptRerunner extends BaseCodecept {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
// Force reload the module by using a cache-busting query parameter
|
|
53
|
-
const fileUrl = `${fsPath.resolve(file)}
|
|
54
|
-
|
|
54
|
+
const fileUrl = `${fsPath.resolve(file)}`
|
|
55
|
+
const resolvedPath = resolveImportModulePath(fileUrl)
|
|
56
|
+
await import(resolvedPath)
|
|
55
57
|
} catch (e) {
|
|
56
58
|
console.error(`Error loading test file ${file}:`, e)
|
|
57
59
|
}
|
package/lib/translation.js
CHANGED
|
@@ -16,7 +16,13 @@ class Translation {
|
|
|
16
16
|
|
|
17
17
|
loadVocabulary(vocabularyFile) {
|
|
18
18
|
if (!vocabularyFile) return
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
let filePath;
|
|
21
|
+
if (path.isAbsolute(vocabularyFile)) {
|
|
22
|
+
filePath = vocabularyFile;
|
|
23
|
+
} else {
|
|
24
|
+
filePath = path.join(store.codeceptDir, vocabularyFile);
|
|
25
|
+
}
|
|
20
26
|
|
|
21
27
|
try {
|
|
22
28
|
const require = createRequire(import.meta.url)
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import os from 'os'
|
|
3
3
|
import path from 'path'
|
|
4
|
+
import { pathToFileURL } from 'url'
|
|
4
5
|
import { createRequire } from 'module'
|
|
5
6
|
import chalk from 'chalk'
|
|
6
7
|
import getFunctionArguments from 'fn-args'
|
|
@@ -38,6 +39,22 @@ export const isAsyncFunction = function (fn) {
|
|
|
38
39
|
return fn[Symbol.toStringTag] === 'AsyncFunction'
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
export const resolveImportModulePath = function (modulePath) {
|
|
43
|
+
// 1. If it's an absolute path, convert to a file:// URL
|
|
44
|
+
if (path.isAbsolute(modulePath)) {
|
|
45
|
+
return pathToFileURL(modulePath).href;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. If it's a relative path (starts with ./ or ../), resolve it fully
|
|
49
|
+
if (modulePath.startsWith('./') || modulePath.startsWith('../')) {
|
|
50
|
+
return modulePath
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Otherwise, it's likely a bare NPM module (e.g., 'chai', 'codeceptjs')
|
|
54
|
+
// Let Node.js resolve it natively from node_modules
|
|
55
|
+
return modulePath;
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
export const fileExists = function (filePath) {
|
|
42
59
|
return fs.existsSync(filePath)
|
|
43
60
|
}
|
|
@@ -229,7 +246,7 @@ export const test = {
|
|
|
229
246
|
// Use Node.js child_process.spawnSync with platform-specific sleep commands
|
|
230
247
|
// This avoids busy waiting and allows other processes to run
|
|
231
248
|
try {
|
|
232
|
-
if (
|
|
249
|
+
if (isWindows()) {
|
|
233
250
|
// Windows: use ping with precise timing (ping waits exactly the specified ms)
|
|
234
251
|
spawnSync('ping', ['-n', '1', '-w', pollInterval.toString(), '127.0.0.1'], { stdio: 'ignore' })
|
|
235
252
|
} else {
|
|
@@ -735,3 +752,7 @@ export const markdownToAnsi = function (markdown) {
|
|
|
735
752
|
})
|
|
736
753
|
)
|
|
737
754
|
}
|
|
755
|
+
|
|
756
|
+
export function isWindows() {
|
|
757
|
+
return os.platform() === 'win32'
|
|
758
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
6
6
|
"keywords": [
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"prettier": "prettier --config prettier.config.js --write bin/**/*.js lib/**/*.js test/**/*.js translations/**/*.js runok.cjs",
|
|
65
65
|
"docs": "./runok.cjs docs",
|
|
66
66
|
"test:unit": "mocha test/unit --recursive --timeout 10000 --reporter @testomatio/reporter/mocha",
|
|
67
|
+
"test:unit:windows": "mocha test/unit/container_test.js --recursive --timeout 10000 --reporter @testomatio/reporter/mocha",
|
|
67
68
|
"test:rest": "mocha test/rest --recursive --timeout 20000 --reporter @testomatio/reporter/mocha",
|
|
68
69
|
"test:runner": "mocha test/runner --recursive --timeout 10000 --reporter @testomatio/reporter/mocha",
|
|
69
70
|
"test": "npm run test:unit && npm run test:rest && npm run test:runner",
|
|
@@ -94,7 +95,7 @@
|
|
|
94
95
|
"@codeceptjs/helper": "2.0.4",
|
|
95
96
|
"@cucumber/cucumber-expressions": "18",
|
|
96
97
|
"@cucumber/gherkin": "38.0.0",
|
|
97
|
-
"@cucumber/messages": "32.
|
|
98
|
+
"@cucumber/messages": "32.3.1",
|
|
98
99
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
99
100
|
"@xmldom/xmldom": "0.9.8",
|
|
100
101
|
"acorn": "8.15.0",
|
|
@@ -104,7 +105,7 @@
|
|
|
104
105
|
"chalk": "4.1.2",
|
|
105
106
|
"cheerio": "^1.0.0",
|
|
106
107
|
"chokidar": "^5.0.0",
|
|
107
|
-
"commander": "
|
|
108
|
+
"commander": "15.0.0",
|
|
108
109
|
"cross-spawn": "7.0.6",
|
|
109
110
|
"css-to-xpath": "0.1.0",
|
|
110
111
|
"csstoxpath": "1.6.0",
|
|
@@ -182,15 +183,15 @@
|
|
|
182
183
|
"rosie": "2.1.1",
|
|
183
184
|
"runok": "^0.9.3",
|
|
184
185
|
"semver": "7.7.3",
|
|
185
|
-
"sinon": "
|
|
186
|
+
"sinon": "22.0.0",
|
|
186
187
|
"sinon-chai": "^4.0.1",
|
|
187
|
-
"ts-morph": "
|
|
188
|
+
"ts-morph": "28.0.0",
|
|
188
189
|
"ts-node": "10.9.2",
|
|
189
190
|
"tsd": "^0.33.0",
|
|
190
191
|
"tsd-jsdoc": "2.5.0",
|
|
191
192
|
"tsx": "^4.19.2",
|
|
192
193
|
"typedoc": "0.28.16",
|
|
193
|
-
"typedoc-plugin-markdown": "4.
|
|
194
|
+
"typedoc-plugin-markdown": "4.12.0",
|
|
194
195
|
"typescript": "5.9.3",
|
|
195
196
|
"wdio-docker-service": "3.2.1",
|
|
196
197
|
"webdriverio": "9.23.0",
|
package/typings/index.d.ts
CHANGED
|
@@ -524,24 +524,11 @@ type RetryTo = (fn: (tries: number) => Promise<void> | void, maxTries: number, p
|
|
|
524
524
|
// Globals
|
|
525
525
|
declare const codecept_dir: string
|
|
526
526
|
declare const output_dir: string
|
|
527
|
-
|
|
528
|
-
declare const retryTo: RetryTo
|
|
529
|
-
declare const hopeThat: HopeThat
|
|
530
|
-
|
|
531
|
-
declare const actor: CodeceptJS.actor
|
|
532
|
-
declare const codecept_actor: CodeceptJS.actor
|
|
533
|
-
declare const Helper: typeof CodeceptJS.Helper
|
|
534
|
-
declare const codecept_helper: typeof CodeceptJS.Helper
|
|
527
|
+
|
|
535
528
|
declare const pause: typeof CodeceptJS.pause
|
|
536
|
-
declare const within: typeof CodeceptJS.within
|
|
537
|
-
declare const session: typeof CodeceptJS.session
|
|
538
|
-
declare const DataTable: typeof CodeceptJS.DataTable
|
|
539
|
-
declare const DataTableArgument: typeof CodeceptJS.DataTableArgument
|
|
540
529
|
declare const codeceptjs: typeof CodeceptJS
|
|
541
|
-
declare const locate: typeof CodeceptJS.Locator.build
|
|
542
530
|
declare function inject(): CodeceptJS.SupportObject
|
|
543
531
|
declare function inject<T extends keyof CodeceptJS.SupportObject>(name: T): CodeceptJS.SupportObject[T]
|
|
544
|
-
declare const secret: typeof CodeceptJS.Secret.secret
|
|
545
532
|
|
|
546
533
|
// BDD
|
|
547
534
|
declare const Given: typeof CodeceptJS.addStep
|
|
@@ -579,21 +566,8 @@ declare namespace NodeJS {
|
|
|
579
566
|
codecept_dir: typeof codecept_dir
|
|
580
567
|
output_dir: typeof output_dir
|
|
581
568
|
|
|
582
|
-
actor: typeof actor
|
|
583
|
-
codecept_actor: typeof codecept_actor
|
|
584
|
-
Helper: typeof Helper
|
|
585
|
-
codecept_helper: typeof codecept_helper
|
|
586
569
|
pause: typeof pause
|
|
587
|
-
within: typeof within
|
|
588
|
-
session: typeof session
|
|
589
|
-
DataTable: typeof DataTable
|
|
590
|
-
DataTableArgument: typeof DataTableArgument
|
|
591
|
-
locate: typeof locate
|
|
592
570
|
inject: typeof inject
|
|
593
|
-
secret: typeof secret
|
|
594
|
-
// plugins
|
|
595
|
-
tryTo: typeof tryTo
|
|
596
|
-
retryTo: typeof retryTo
|
|
597
571
|
|
|
598
572
|
// BDD
|
|
599
573
|
Given: typeof Given
|
|
@@ -729,6 +703,12 @@ declare module 'codeceptjs' {
|
|
|
729
703
|
* Create a secret value
|
|
730
704
|
*/
|
|
731
705
|
export const secret: typeof CodeceptJS.Secret.secret
|
|
706
|
+
|
|
707
|
+
export const session: typeof CodeceptJS.session
|
|
708
|
+
|
|
709
|
+
export const inject: typeof globalThis.inject
|
|
710
|
+
|
|
711
|
+
export const locate: typeof CodeceptJS.Locator.build
|
|
732
712
|
}
|
|
733
713
|
|
|
734
714
|
declare module '@codeceptjs/helper' {
|