diff --git a/lib/codecept.js b/lib/codecept.js index 76c9a4f8d..22b769ebc 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -28,20 +28,19 @@ class Codecept { * Initialize CodeceptJS at specific directory. * If async initialization is required, pass callback as second parameter. * - * @param {*} dir - * @param {*} callback + * @param {string} dir */ - init(dir, callback) { + init(dir) { this.initGlobals(dir); // initializing listeners Container.create(this.config, this.opts); - this.bootstrap(callback); + this.runHooks(); } /** * Creates global variables * - * @param {*} dir + * @param {string} dir */ initGlobals(dir) { global.codecept_dir = dir; @@ -66,12 +65,9 @@ class Codecept { } /** - * Executes hooks and bootstrap. - * If bootstrap is async, second parameter is required. - * - * @param {*} done + * Executes hooks. */ - bootstrap(done) { + runHooks() { // default hooks runHook(require('./listener/steps')); runHook(require('./listener/config')); @@ -81,8 +77,15 @@ class Codecept { // custom hooks this.config.hooks.forEach(hook => runHook(hook)); + } - // bootstrap + /** + * Executes bootstrap. + * If bootstrap is async, second parameter is required. + * + * @param {() => any} [done] + */ + runBootstrap(done) { runHook(this.config.bootstrap, done, 'bootstrap'); } diff --git a/lib/command/definitions.js b/lib/command/definitions.js index 557b3cab1..501fdfa07 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -160,55 +160,48 @@ module.exports = function (genPath, options) { if (!config) return; const codecept = new Codecept(config, {}); - codecept.init(testsPath, (err) => { - if (err) { - output.error(`Error while running bootstrap file :${err}`); - return; - } - - const helpers = container.helpers(); - const suppportI = container.support('I'); - const translations = container.translation(); - let methods = []; - const actions = []; - for (const name in helpers) { - const helper = helpers[name]; - methods = addAllMethodsInObject(helper, actions, methods, translations); - } - methods = addAllNamesInObject(suppportI, actions, methods); - - const supports = container.support(); // return all support objects - const exportPageObjects = []; - const callbackParams = []; - for (const name in supports) { - if (name === 'I') continue; - callbackParams.push(`${name}:any`); - const pageObject = supports[name]; - const pageMethods = addAllMethodsInObject(pageObject, {}, []); - let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join('')); - pageObjectExport = pageObjectExport.replace('{{name}}', name); - exportPageObjects.push(pageObjectExport); - } - - let definitionsTemplate = template.replace('{{methods}}', methods.join('')); - definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n')); - if (callbackParams.length > 0) { - definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${callbackParams.join(', ')}`); - } else { - definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', ''); - } - if (translations) { - definitionsTemplate = definitionsTemplate.replace(/\{\{I\}\}/g, translations.I); - } + codecept.init(testsPath); + + const helpers = container.helpers(); + const suppportI = container.support('I'); + const translations = container.translation(); + let methods = []; + const actions = []; + for (const name in helpers) { + const helper = helpers[name]; + methods = addAllMethodsInObject(helper, actions, methods, translations); + } + methods = addAllNamesInObject(suppportI, actions, methods); + + const supports = container.support(); // return all support objects + const exportPageObjects = []; + const callbackParams = []; + for (const name in supports) { + if (name === 'I') continue; + callbackParams.push(`${name}:any`); + const pageObject = supports[name]; + const pageMethods = addAllMethodsInObject(pageObject, {}, []); + let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join('')); + pageObjectExport = pageObjectExport.replace('{{name}}', name); + exportPageObjects.push(pageObjectExport); + } - fs.writeFileSync(path.join(testsPath, 'steps.d.ts'), definitionsTemplate); - output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs'); - output.print('Definitions were generated in steps.d.ts'); - output.print('Load them by adding at the top of a test file:'); - output.print(output.colors.grey('\n/// ')); + let definitionsTemplate = template.replace('{{methods}}', methods.join('')); + definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n')); + if (callbackParams.length > 0) { + definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${callbackParams.join(', ')}`); + } else { + definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', ''); + } + if (translations) { + definitionsTemplate = definitionsTemplate.replace(/\{\{I\}\}/g, translations.I); + } - codecept.teardown(); - }); + fs.writeFileSync(path.join(testsPath, 'steps.d.ts'), definitionsTemplate); + output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs'); + output.print('Definitions were generated in steps.d.ts'); + output.print('Load them by adding at the top of a test file:'); + output.print(output.colors.grey('\n/// ')); }; function addAllMethodsInObject(supportObj, actions, methods, translations) { diff --git a/lib/command/gherkin/snippets.js b/lib/command/gherkin/snippets.js index 93fa8c138..6b735c4b8 100644 --- a/lib/command/gherkin/snippets.js +++ b/lib/command/gherkin/snippets.js @@ -20,91 +20,91 @@ module.exports = function (genPath, options) { if (!config) return; const codecept = new Codecept(config, {}); - codecept.init(testsPath, (err) => { - if (!config.gherkin) { - output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it'); - process.exit(1); - } - if (!config.gherkin.steps || !config.gherkin.steps[0]) { - output.error('No gherkin steps defined in config. Exiting'); - process.exit(1); - } - if (!config.gherkin.features) { - output.error('No gherkin features defined in config. Exiting'); - process.exit(1); - } + codecept.init(testsPath); - const files = []; - glob.sync(config.gherkin.features, { cwd: global.codecept_dir }).forEach((file) => { - if (!fsPath.isAbsolute(file)) { - file = fsPath.join(global.codecept_dir, file); - } - files.push(fsPath.resolve(file)); - }); - output.print(`Loaded ${files.length} files`); + if (!config.gherkin) { + output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it'); + process.exit(1); + } + if (!config.gherkin.steps || !config.gherkin.steps[0]) { + output.error('No gherkin steps defined in config. Exiting'); + process.exit(1); + } + if (!config.gherkin.features) { + output.error('No gherkin features defined in config. Exiting'); + process.exit(1); + } - let newSteps = []; + const files = []; + glob.sync(config.gherkin.features, { cwd: global.codecept_dir }).forEach((file) => { + if (!fsPath.isAbsolute(file)) { + file = fsPath.join(global.codecept_dir, file); + } + files.push(fsPath.resolve(file)); + }); + output.print(`Loaded ${files.length} files`); - const parseSteps = (steps) => { - const newSteps = []; - let currentKeyword = ''; - for (const step of steps) { - if (step.keyword.trim() === 'And') { - if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`); - step.keyword = currentKeyword; - } - currentKeyword = step.keyword; - try { - matchStep(step.text); - } catch (err) { - let stepLine = step.text - .replace(/\"(.*?)\"/g, '{string}') - .replace(/(\d+\.\d+)/, '{float}') - .replace(/ (\d+) /, ' {int} '); - stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location }); - newSteps.push(stepLine); - } - } - return newSteps; - }; + let newSteps = []; - const parseFile = (file) => { - const ast = parser.parse(fs.readFileSync(file).toString()); - for (const child of ast.feature.children) { - if (child.type === 'ScenarioOutline') continue; // skip scenario outline - newSteps = newSteps.concat(parseSteps(child.steps).map((step) => { - return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) }); - })); + const parseSteps = (steps) => { + const newSteps = []; + let currentKeyword = ''; + for (const step of steps) { + if (step.keyword.trim() === 'And') { + if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`); + step.keyword = currentKeyword; } - }; - - files.forEach(file => parseFile(file)); + currentKeyword = step.keyword; + try { + matchStep(step.text); + } catch (err) { + let stepLine = step.text + .replace(/\"(.*?)\"/g, '{string}') + .replace(/(\d+\.\d+)/, '{float}') + .replace(/ (\d+) /, ' {int} '); + stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location }); + newSteps.push(stepLine); + } + } + return newSteps; + }; - let stepFile = config.gherkin.steps[0]; - if (!fsPath.isAbsolute(stepFile)) { - stepFile = fsPath.join(global.codecept_dir, stepFile); + const parseFile = (file) => { + const ast = parser.parse(fs.readFileSync(file).toString()); + for (const child of ast.feature.children) { + if (child.type === 'ScenarioOutline') continue; // skip scenario outline + newSteps = newSteps.concat(parseSteps(child.steps).map((step) => { + return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) }); + })); } + }; + + files.forEach(file => parseFile(file)); + + let stepFile = config.gherkin.steps[0]; + if (!fsPath.isAbsolute(stepFile)) { + stepFile = fsPath.join(global.codecept_dir, stepFile); + } - const snippets = newSteps - .filter((value, index, self) => self.indexOf(value) === index) - .map((step) => { - return ` + const snippets = newSteps + .filter((value, index, self) => self.indexOf(value) === index) + .map((step) => { + return ` ${step.type}('${step}', () => { // From "${step.file}" ${JSON.stringify(step.location)} throw new Error('Not implemented yet'); });`; - }); + }); - if (!snippets.length) { - output.print('No new snippets found'); - return; - } - output.success(`Snippets generated: ${snippets.length}`); - output.print(snippets.join('\n')); + if (!snippets.length) { + output.print('No new snippets found'); + return; + } + output.success(`Snippets generated: ${snippets.length}`); + output.print(snippets.join('\n')); - if (!options.dryRun) { - output.success(`Snippets added to ${output.colors.bold(stepFile)}`); - fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n'); // eslint-disable-line - } - }); + if (!options.dryRun) { + output.success(`Snippets added to ${output.colors.bold(stepFile)}`); + fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n'); // eslint-disable-line + } }; diff --git a/lib/command/gherkin/steps.js b/lib/command/gherkin/steps.js index 12904c411..0a9f8173a 100644 --- a/lib/command/gherkin/steps.js +++ b/lib/command/gherkin/steps.js @@ -12,17 +12,17 @@ module.exports = function (genPath, options) { if (!config) return; const codecept = new Codecept(config, {}); - codecept.init(testsPath, (err) => { - output.print('Gherkin Step definitions:'); - output.print(); - const steps = getSteps(); - for (const step of Object.keys(steps)) { - output.print(` ${output.colors.bold(step)} \n => ${output.colors.green(steps[step].line || '')}`); - } - output.print(); - if (!Object.keys(steps).length) { - output.error('No Gherkin steps defined'); - } - }); + codecept.init(testsPath); + + output.print('Gherkin Step definitions:'); + output.print(); + const steps = getSteps(); + for (const step of Object.keys(steps)) { + output.print(` ${output.colors.bold(step)} \n => ${output.colors.green(steps[step].line || '')}`); + } + output.print(); + if (!Object.keys(steps).length) { + output.error('No Gherkin steps defined'); + } }; diff --git a/lib/command/interactive.js b/lib/command/interactive.js index 9c6363427..5f3215255 100644 --- a/lib/command/interactive.js +++ b/lib/command/interactive.js @@ -11,7 +11,9 @@ module.exports = function (path, options) { const testsPath = getTestRoot(path); const config = getConfig(testsPath); const codecept = new Codecept(config, options); - codecept.init(testsPath, (err) => { + codecept.init(testsPath); + + codecept.runBootstrap((err) => { if (err) { output.error(`Error while running bootstrap file :${err}`); return; diff --git a/lib/command/list.js b/lib/command/list.js index ca5c877c6..cd488fc29 100644 --- a/lib/command/list.js +++ b/lib/command/list.js @@ -10,35 +10,28 @@ module.exports = function (path) { const testsPath = getTestRoot(path); const config = getConfig(testsPath); const codecept = new Codecept(config, {}); - codecept.init(testsPath, (err) => { - if (err) { - output.error(`Error while running bootstrap file :${err}`); - return; - } + codecept.init(testsPath); - output.print('List of test actions: -- '); - const helpers = container.helpers(); - const supportI = container.support('I'); - const actions = []; - for (const name in helpers) { - const helper = helpers[name]; - methodsOfObject(helper).forEach((action) => { - const params = getParamsToString(helper[action]); - actions[action] = 1; - output.print(` ${output.colors.grey(name)} I.${output.colors.bold(action)}(${params})`); - }); - } - for (const name in supportI) { - if (actions[name]) { - continue; - } - const actor = supportI[name]; - const params = getParamsToString(actor); - output.print(` I.${output.colors.bold(name)}(${params})`); + output.print('List of test actions: -- '); + const helpers = container.helpers(); + const supportI = container.support('I'); + const actions = []; + for (const name in helpers) { + const helper = helpers[name]; + methodsOfObject(helper).forEach((action) => { + const params = getParamsToString(helper[action]); + actions[action] = 1; + output.print(` ${output.colors.grey(name)} I.${output.colors.bold(action)}(${params})`); + }); + } + for (const name in supportI) { + if (actions[name]) { + continue; } - output.print('PS: Actions are retrieved from enabled helpers. '); - output.print('Implement custom actions in your helper classes.'); - - codecept.teardown(); - }); + const actor = supportI[name]; + const params = getParamsToString(actor); + output.print(` I.${output.colors.bold(name)}(${params})`); + } + output.print('PS: Actions are retrieved from enabled helpers. '); + output.print('Implement custom actions in your helper classes.'); }; diff --git a/lib/command/run.js b/lib/command/run.js index bca890c06..51a51a892 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -32,8 +32,11 @@ module.exports = function (test, options) { try { codecept = new Codecept(config, options); - codecept.init(testRoot, (err) => { + codecept.init(testRoot); + + codecept.runBootstrap((err) => { if (err) throw new Error(`Error while running bootstrap file :${err}`); + codecept.loadTests(); codecept.run(test); });