codeceptjs
Version:
Supercharged End 2 End Testing Framework for NodeJS
149 lines (130 loc) • 5 kB
JavaScript
import Mocha from 'mocha'
import fsPath from 'path'
import fs from 'fs'
import { fileURLToPath } from 'url'
import reporter from './cli.js'
import gherkinParser, { loadTranslations } from './gherkin.js'
import output from '../output.js'
import scenarioUiFunction from './ui.js'
import { initMochaGlobals } from '../globals.js'
import { fixErrorStack } from '../utils/typescript.js'
import container from '../container.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = fsPath.dirname(__filename)
let mocha
class MochaFactory {
static create(config, opts) {
const merged = Object.assign({}, config, opts)
mocha = new Mocha(merged)
if (merged.cleanReferencesAfterRun !== true) {
mocha.cleanReferencesAfterRun(false)
}
output.process(opts.child)
mocha.ui(scenarioUiFunction)
// Manually trigger UI setup for globals to be available in ESM context
// This ensures Feature, Scenario, Before, etc. are available immediately
if (mocha.suite && mocha.suite.emit) {
const context = {}
mocha.suite.emit('pre-require', context, '', mocha)
// Also set globals immediately so they're available when ESM modules load
initMochaGlobals(context)
}
Mocha.Runner.prototype.uncaught = function (err) {
if (err) {
if (err.toString().indexOf('ECONNREFUSED') >= 0) {
// Handle ECONNREFUSED without dynamic import for now
err = new Error('Connection refused: ' + err.toString())
}
const fileMapping = container?.tsFileMapping?.()
if (fileMapping) {
fixErrorStack(err, fileMapping)
}
output.error(err)
output.print(err.stack)
process.exit(1)
}
output.error('Uncaught undefined exception')
process.exit(1)
}
// Override loadFiles to handle feature files
const originalLoadFiles = Mocha.prototype.loadFiles
mocha.loadFiles = function (fn) {
// load features
const featureFiles = this.files.filter(file => file.match(/\.feature$/))
if (featureFiles.length > 0) {
// Load translations for Gherkin features
loadTranslations().catch(() => {
// Ignore if translations can't be loaded
})
for (const file of featureFiles) {
const suite = gherkinParser(fs.readFileSync(file, 'utf8'), file)
this.suite.addSuite(suite)
}
// remove feature files
const jsFiles = this.files.filter(file => !file.match(/\.feature$/))
this.files = this.files.filter(file => !file.match(/\.feature$/))
// Load JavaScript test files using original loadFiles
if (jsFiles.length > 0) {
originalLoadFiles.call(this, fn)
}
// add ids for each test and check uniqueness
const dupes = []
let missingFeatureInFile = []
const seenTests = []
this.suite.eachTest(test => {
if (!test) {
return // Skip undefined tests
}
const name = test.fullTitle()
if (seenTests.includes(test.uid)) {
dupes.push(name)
}
seenTests.push(test.uid)
if (name.slice(0, name.indexOf(':')) === '') {
missingFeatureInFile.push(test.file)
}
})
if (dupes.length) {
// ideally this should be no-op and throw (breaking change)...
output.error(`Duplicate test names detected - Feature + Scenario name should be unique:\n${dupes.join('\n')}`)
}
if (missingFeatureInFile.length) {
missingFeatureInFile = [...new Set(missingFeatureInFile)]
output.error(`Missing Feature section in:\n${missingFeatureInFile.join('\n')}`)
}
} else {
// Use original for non-feature files
originalLoadFiles.call(this, fn)
}
}
const presetReporter = opts.reporter || config.reporter
// use standard reporter
if (!presetReporter) {
mocha.reporter(reporter, opts)
return mocha
}
// load custom reporter with options
const reporterOptions = Object.assign(config.reporterOptions || {})
if (opts.reporterOptions !== undefined) {
opts.reporterOptions.split(',').forEach(opt => {
const L = opt.split('=')
if (L.length > 2 || L.length === 0) {
throw new Error(`invalid reporter option '${opt}'`)
} else if (L.length === 2) {
reporterOptions[L[0]] = L[1]
} else {
reporterOptions[L[0]] = true
}
})
}
const attributes = Object.getOwnPropertyDescriptor(reporterOptions, 'codeceptjs-cli-reporter')
if (reporterOptions['codeceptjs-cli-reporter'] && attributes) {
Object.defineProperty(reporterOptions, 'codeceptjs/lib/mocha/cli', attributes)
delete reporterOptions['codeceptjs-cli-reporter']
}
// custom reporters
mocha.reporter(presetReporter, reporterOptions)
return mocha
}
}
export default MochaFactory