Skip to content

Commit 21844d6

Browse files
DavertMikclaude
andcommitted
refactor: eliminate internal globals in favor of store singleton and direct imports
Replace all internal global variable usage with proper ESM patterns: - Add store.initialize() method with immutable required fields (codeceptDir, outputDir, workerMode) - Replace global.codecept_dir/output_dir reads with store.codeceptDir/outputDir (~55 sites) - Replace global.container reads with direct container imports (~15 sites) - Replace global.debugMode with store.debugMode in WebDriver/Puppeteer - Replace process.env.RUNS_WITH_WORKERS with store.workerMode - Replace process.env.FEATURE_ONLY/SCENARIO_ONLY with store.featureOnly/scenarioOnly - Replace global.maskSensitiveData with store.maskSensitiveData - Convert singleton guard globals to module-level variables - Remove dead global.codecept_debug - Keep user-facing DSL globals (Feature, Scenario, etc.) and backward compat writes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c5572aa commit 21844d6

44 files changed

Lines changed: 240 additions & 117 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/ai.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { generateText } from 'ai'
77
import { fileURLToPath } from 'url'
88
import path from 'path'
99
import { fileExists } from './utils.js'
10+
import store from './store.js'
1011

1112
const __dirname = path.dirname(fileURLToPath(import.meta.url))
1213

@@ -24,8 +25,8 @@ async function loadPrompts() {
2425
for (const name of promptNames) {
2526
let promptPath
2627

27-
if (global.codecept_dir) {
28-
promptPath = path.join(global.codecept_dir, `prompts/${name}.js`)
28+
if (store.codeceptDir) {
29+
promptPath = path.join(store.codeceptDir, `prompts/${name}.js`)
2930
}
3031

3132
if (!promptPath || !fileExists(promptPath)) {

lib/codecept.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { emptyFolder } from './utils.js'
2121
import { initCodeceptGlobals } from './globals.js'
2222
import { validateTypeScriptSetup, getTSNodeESMWarning } from './utils/loaderCheck.js'
2323
import recorder from './recorder.js'
24+
import store from './store.js'
2425

2526
import storeListener from './listener/store.js'
2627
import stepsListener from './listener/steps.js'
@@ -71,7 +72,7 @@ class Codecept {
7172
} else {
7273
// For npm packages, resolve from the user's directory
7374
// This ensures packages like tsx are found in user's node_modules
74-
const userDir = global.codecept_dir || process.cwd()
75+
const userDir = store.codeceptDir || process.cwd()
7576

7677
try {
7778
// Use createRequire to resolve from user's directory
@@ -102,8 +103,6 @@ class Codecept {
102103
await this.requireModules(this.requiringModules)
103104
// initializing listeners
104105
await container.create(this.config, this.opts)
105-
// Store container globally for easy access
106-
global.container = container
107106
await this.runHooks()
108107
}
109108

@@ -171,7 +170,7 @@ class Codecept {
171170
*/
172171
loadTests(pattern) {
173172
const options = {
174-
cwd: global.codecept_dir,
173+
cwd: store.codeceptDir,
175174
}
176175

177176
let patterns = [pattern]
@@ -203,7 +202,7 @@ class Codecept {
203202
globSync(pattern, options).forEach(file => {
204203
if (file.includes('node_modules')) return
205204
if (!fsPath.isAbsolute(file)) {
206-
file = fsPath.join(global.codecept_dir, file)
205+
file = fsPath.join(store.codeceptDir, file)
207206
}
208207
if (!this.testFiles.includes(fsPath.resolve(file))) {
209208
this.testFiles.push(fsPath.resolve(file))
@@ -293,7 +292,7 @@ class Codecept {
293292

294293
if (test) {
295294
if (!fsPath.isAbsolute(test)) {
296-
test = fsPath.join(global.codecept_dir, test)
295+
test = fsPath.join(store.codeceptDir, test)
297296
}
298297
const testBasename = fsPath.basename(test, '.js')
299298
const testFeatureBasename = fsPath.basename(test, '.feature')

lib/command/dryRun.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async function printTests(files) {
5050
const { default: figures } = await import('figures')
5151
const { default: colors } = await import('chalk')
5252

53-
output.print(output.styles.debug(`Tests from ${global.codecept_dir}:`))
53+
output.print(output.styles.debug(`Tests from ${store.codeceptDir}:`))
5454
output.print()
5555

5656
const mocha = Container.mocha()

lib/command/generate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { mkdirp } from 'mkdirp'
55
import path from 'path'
66
import { fileExists, ucfirst, lcfirst, beautify } from '../utils.js'
77
import output from '../output.js'
8+
import store from '../store.js'
89
import generateDefinitions from './definitions.js'
910
import { getConfig, getTestRoot, safeFileWrite, readConfig } from './utils.js'
1011

@@ -20,6 +21,7 @@ Scenario('test something', async ({ {{actor}} }) => {
2021
// generates empty test
2122
export async function test(genPath) {
2223
const testsPath = getTestRoot(genPath)
24+
store.codeceptDir = testsPath
2325
global.codecept_dir = testsPath
2426
const config = await getConfig(testsPath)
2527
if (!config) return

lib/command/gherkin/snippets.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import fsPath from 'path'
88
import { getConfig, getTestRoot } from '../utils.js'
99
import Codecept from '../../codecept.js'
1010
import output from '../../output.js'
11+
import store from '../../store.js'
1112
import { matchStep } from '../../mocha/bdd.js'
1213

1314
const uuidFn = IdGenerator.uuid()
@@ -43,9 +44,9 @@ export default async function (genPath, options) {
4344
}
4445

4546
const files = []
46-
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => {
47+
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : store.codeceptDir }).forEach(file => {
4748
if (!fsPath.isAbsolute(file)) {
48-
file = fsPath.join(global.codecept_dir, file)
49+
file = fsPath.join(store.codeceptDir, file)
4950
}
5051
files.push(fsPath.resolve(file))
5152
})
@@ -92,7 +93,7 @@ export default async function (genPath, options) {
9293
if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline
9394
parseSteps(child.scenario.steps)
9495
.map(step => {
95-
return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) })
96+
return Object.assign(step, { file: file.replace(store.codeceptDir, '').slice(1) })
9697
})
9798
.map(step => newSteps.set(`${step.type}(${step})`, step))
9899
}
@@ -107,7 +108,7 @@ export default async function (genPath, options) {
107108
}
108109

109110
if (!fsPath.isAbsolute(stepFile)) {
110-
stepFile = fsPath.join(global.codecept_dir, stepFile)
111+
stepFile = fsPath.join(store.codeceptDir, stepFile)
111112
}
112113

113114
const snippets = [...newSteps.values()]

lib/command/run-multiple.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import event from '../event.js'
88
import { createRuns } from './run-multiple/collection.js'
99
import { clearString, replaceValueDeep } from '../utils.js'
1010
import { getConfig, getTestRoot, fail } from './utils.js'
11+
import store from '../store.js'
1112

1213
const __filename = fileURLToPath(import.meta.url)
1314
const __dirname = path.dirname(__filename)
@@ -35,6 +36,7 @@ export default async function (selectedRuns, options) {
3536
const configFile = options.config
3637

3738
const testRoot = getTestRoot(configFile)
39+
store.codeceptDir = testRoot
3840
global.codecept_dir = testRoot
3941

4042
// copy opts to run

lib/command/run-workers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default async function (workerCount, selectedRuns, options) {
4141
output.print(`CodeceptJS v${Codecept.version()} ${output.standWithUkraine()}`)
4242
output.print(`Running tests in ${output.styles.bold(numberOfWorkers)} workers...`)
4343
store.hasWorkers = true
44-
process.env.RUNS_WITH_WORKERS = 'true'
44+
store.workerMode = true
4545

4646
const workers = new Workers(numberOfWorkers, config)
4747
workers.overrideConfig(overrideConfigs)

lib/command/run.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default async function (test, options) {
3232
codecept.loadTests(test)
3333

3434
if (options.verbose) {
35-
global.debugMode = true
35+
store.debugMode = true
3636
const { getMachineInfo } = await import('./info.js')
3737
await getMachineInfo()
3838
}

lib/command/workers/runTests.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ const { options, tests, testRoot, workerIndex, poolMode } = workerData
2121

2222
// Global error handlers to catch critical errors but not test failures
2323
process.on('uncaughtException', (err) => {
24-
if (global.container?.tsFileMapping && fixErrorStack) {
25-
const fileMapping = global.container.tsFileMapping()
24+
if (container?.tsFileMapping && fixErrorStack) {
25+
const fileMapping = container.tsFileMapping()
2626
if (fileMapping) {
2727
fixErrorStack(err, fileMapping)
2828
}
@@ -40,8 +40,8 @@ process.on('uncaughtException', (err) => {
4040
})
4141

4242
process.on('unhandledRejection', (reason, promise) => {
43-
if (reason && typeof reason === 'object' && reason.stack && global.container?.tsFileMapping && fixErrorStack) {
44-
const fileMapping = global.container.tsFileMapping()
43+
if (reason && typeof reason === 'object' && reason.stack && container?.tsFileMapping && fixErrorStack) {
44+
const fileMapping = container.tsFileMapping()
4545
if (fileMapping) {
4646
fixErrorStack(reason, fileMapping)
4747
}
@@ -163,8 +163,8 @@ initPromise = (async function () {
163163
// IMPORTANT: await is required here since getConfig is async
164164
baseConfig = await getConfig(options.config || testRoot)
165165
} catch (configErr) {
166-
if (global.container?.tsFileMapping && fixErrorStack) {
167-
const fileMapping = global.container.tsFileMapping()
166+
if (container?.tsFileMapping && fixErrorStack) {
167+
const fileMapping = container.tsFileMapping()
168168
if (fileMapping) {
169169
fixErrorStack(configErr, fileMapping)
170170
}
@@ -185,8 +185,8 @@ initPromise = (async function () {
185185
try {
186186
await codecept.init(testRoot)
187187
} catch (initErr) {
188-
if (global.container?.tsFileMapping && fixErrorStack) {
189-
const fileMapping = global.container.tsFileMapping()
188+
if (container?.tsFileMapping && fixErrorStack) {
189+
const fileMapping = container.tsFileMapping()
190190
if (fileMapping) {
191191
fixErrorStack(initErr, fileMapping)
192192
}
@@ -218,8 +218,8 @@ initPromise = (async function () {
218218
parentPort?.close()
219219
}
220220
} catch (err) {
221-
if (global.container?.tsFileMapping && fixErrorStack) {
222-
const fileMapping = global.container.tsFileMapping()
221+
if (container?.tsFileMapping && fixErrorStack) {
222+
const fileMapping = container.tsFileMapping()
223223
if (fileMapping) {
224224
fixErrorStack(err, fileMapping)
225225
}

lib/container.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ async function createPlugins(config, options = {}) {
672672
continue
673673
}
674674

675-
if (!options.child && process.env.RUNS_WITH_WORKERS === 'true' && !runInParent) {
675+
if (!options.child && store.workerMode && !runInParent) {
676676
continue
677677
}
678678
let module
@@ -681,7 +681,7 @@ async function createPlugins(config, options = {}) {
681681
module = pluginConfig.require
682682
if (module.startsWith('.')) {
683683
// local
684-
module = path.resolve(global.codecept_dir, module) // custom plugin
684+
module = path.resolve(store.codeceptDir, module) // custom plugin
685685
}
686686
} else {
687687
module = `./plugin/${pluginName}.js`
@@ -716,7 +716,7 @@ async function loadGherkinStepsAsync(paths) {
716716
bddModule.clearCurrentStepFile()
717717
}
718718
} else {
719-
const folderPath = paths.startsWith('.') ? normalizeAndJoin(global.codecept_dir, paths) : ''
719+
const folderPath = paths.startsWith('.') ? normalizeAndJoin(store.codeceptDir, paths) : ''
720720
if (folderPath !== '') {
721721
const files = globSync(folderPath)
722722
for (const file of files) {
@@ -764,7 +764,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
764764
}
765765
}
766766
if (typeof modulePath === 'string' && modulePath.charAt(0) === '.') {
767-
modulePath = path.join(global.codecept_dir, modulePath)
767+
modulePath = path.join(store.codeceptDir, modulePath)
768768
}
769769
try {
770770
// Use dynamic import for both ESM and CJS modules
@@ -888,7 +888,7 @@ async function loadTranslation(locale, vocabularies) {
888888
const langs = await Translation.getLangs()
889889
if (langs[locale]) {
890890
translation = new Translation(langs[locale])
891-
} else if (fileExists(path.join(global.codecept_dir, locale))) {
891+
} else if (fileExists(path.join(store.codeceptDir, locale))) {
892892
// get from a provided file instead
893893
translation = Translation.createDefault()
894894
translation.loadVocabulary(locale)
@@ -905,7 +905,7 @@ function getHelperModuleName(helperName, config) {
905905
// classical require
906906
if (config[helperName].require) {
907907
if (config[helperName].require.startsWith('.')) {
908-
let helperPath = path.resolve(global.codecept_dir, config[helperName].require)
908+
let helperPath = path.resolve(store.codeceptDir, config[helperName].require)
909909
// Add .js extension if not present for ESM compatibility
910910
if (!path.extname(helperPath)) {
911911
helperPath += '.js'

0 commit comments

Comments
 (0)