From e3c18fe436bdcc461746e2d650ea04234b151b0e Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Thu, 10 May 2018 00:20:39 +0300 Subject: [PATCH 1/7] Updated typescipt definitions. --- lib/command/definitions.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/command/definitions.js b/lib/command/definitions.js index 5b0767f7c..c893a8b92 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -21,14 +21,20 @@ interface ILocator { ios?: string; } -declare const actor: () => CodeceptJS.{{I}}; -declare const Feature: (string: string) => void; -declare const Scenario: (string: string, callback: ICodeceptCallback) => void; -declare const Before: (callback: ICodeceptCallback) => void; -declare const BeforeSuite: (callback: ICodeceptCallback) => void; -declare const After: (callback: ICodeceptCallback) => void; -declare const AfterSuite: (callback: ICodeceptCallback) => void; -declare const within: (selector: string, callback: Function) => void; +declare function actor(customSteps?: {}): CodeceptJS.{{I}}; +declare function Feature(string: string, opts?: {}): void; +declare function Scenario(string: string, callback: ICodeceptCallback): void; +declare function Scenario(string: string, opts: {}, callback: ICodeceptCallback): void; +declare function xScenario(string: string, callback: ICodeceptCallback): void; +declare function xScenario(string: string, opts: {}, callback: ICodeceptCallback): void; +declare function Data(data: any): any; +declare function xData(data: any): any; +declare function Before(callback: ICodeceptCallback): void; +declare function BeforeSuite(callback: ICodeceptCallback): void; +declare function After(callback: ICodeceptCallback): void; +declare function AfterSuite(callback: ICodeceptCallback): void; +declare function within(selector: string, callback: Function): void; +declare const codecept_helper: any; declare namespace CodeceptJS { export interface {{I}} { From 7d568ea9a64270de59b82e3fb0d7552fd0437898 Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Tue, 5 Jun 2018 11:22:43 +0300 Subject: [PATCH 2/7] Fixed test runner for nested test directories --- package.json | 2 +- test/unit/assert/equal_test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0fda72cb1..101b34828 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "json-server": "./node_modules/json-server/bin/index.js test/data/rest/db.json -p 8010 --watch -m test/data/rest/headers.js", "lint": "eslint bin/ examples/ lib/ test/ translations/", "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ --fix", - "test": "npm run lint && mocha test/unit && mocha test/runner" + "test": "npm run lint && mocha test/unit --recursive && mocha test/runner --recursive" }, "dependencies": { "chalk": "^1.1.3", diff --git a/test/unit/assert/equal_test.js b/test/unit/assert/equal_test.js index d1acdadff..0f8e5aa97 100644 --- a/test/unit/assert/equal_test.js +++ b/test/unit/assert/equal_test.js @@ -23,13 +23,13 @@ describe('equal assertion', () => { equal.params.expected = 'hello'; equal.params.actual = 'hi'; const err = equal.getFailedAssertion(); - err.inspect().should.equal("expected contents of webpage 'hello' to equal 'hi'"); + err.inspect().should.equal('expected contents of webpage "hello" to equal "hi"'); }); it('should provide nice negate error message', () => { equal.params.expected = 'hello'; equal.params.actual = 'hello'; const err = equal.getFailedNegation(); - err.inspect().should.equal("expected contents of webpage 'hello' not to equal 'hello'"); + err.inspect().should.equal('expected contents of webpage "hello" not to equal "hello"'); }); }); From 653bb5c70e7065c00d52b76de950f7ac80f0b753 Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Tue, 5 Jun 2018 11:22:43 +0300 Subject: [PATCH 3/7] Fixed test runner for nested test directories --- docs/advanced.md | 20 ++++++++++++++- lib/data/table.js | 4 +++ package.json | 2 +- test/unit/assert/equal_test.js | 4 +-- test/unit/data/table_test.js | 46 ++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/docs/advanced.md b/docs/advanced.md index ff6bba392..6321ca4ab 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -9,7 +9,7 @@ In this case, you need to create a datatable and fill it in with credentials. Then use `Data().Scenario` to include this data and generate multiple scenarios: ```js -// define data table inside a test or load from another module +// Define data table inside a test or load from another module let accounts = new DataTable(['login', 'password']); // accounts.add(['davert', '123456']); // adding records to a table accounts.add(['admin', '123456']); @@ -47,6 +47,24 @@ Current data set is appended to a test name in output: S Test Login | {"login":"admin","password":"23456"} ``` +```js +// You can filter your data table +Data(accounts.filter(account => account.login == 'admin') +.Scenario('Test Login', (I, current) => { + I.fillField('Username', current.login); + I.fillField('Password', current.password); + I.click('Sign In'); + I.see('Welcome '+ current.login); +}); +``` + +This will limit data sets accoring passed function: + +```sh +✓ Test Login | {"login":"admin","password":"123456"} +S Test Login | {"login":"admin","password":"23456"} +``` + Data sets can also be defined with array, generator, or a function. ```js diff --git a/lib/data/table.js b/lib/data/table.js index e72bd014b..5f7935633 100644 --- a/lib/data/table.js +++ b/lib/data/table.js @@ -30,6 +30,10 @@ class DataTable { }); this.rows.push({ skip: true, data: tempObj }); } + + filter(func) { + return this.rows.filter(row => func(row.data)); + } } module.exports = DataTable; diff --git a/package.json b/package.json index 0fda72cb1..101b34828 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "json-server": "./node_modules/json-server/bin/index.js test/data/rest/db.json -p 8010 --watch -m test/data/rest/headers.js", "lint": "eslint bin/ examples/ lib/ test/ translations/", "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ --fix", - "test": "npm run lint && mocha test/unit && mocha test/runner" + "test": "npm run lint && mocha test/unit --recursive && mocha test/runner --recursive" }, "dependencies": { "chalk": "^1.1.3", diff --git a/test/unit/assert/equal_test.js b/test/unit/assert/equal_test.js index d1acdadff..0f8e5aa97 100644 --- a/test/unit/assert/equal_test.js +++ b/test/unit/assert/equal_test.js @@ -23,13 +23,13 @@ describe('equal assertion', () => { equal.params.expected = 'hello'; equal.params.actual = 'hi'; const err = equal.getFailedAssertion(); - err.inspect().should.equal("expected contents of webpage 'hello' to equal 'hi'"); + err.inspect().should.equal('expected contents of webpage "hello" to equal "hi"'); }); it('should provide nice negate error message', () => { equal.params.expected = 'hello'; equal.params.actual = 'hello'; const err = equal.getFailedNegation(); - err.inspect().should.equal("expected contents of webpage 'hello' not to equal 'hello'"); + err.inspect().should.equal('expected contents of webpage "hello" not to equal "hello"'); }); }); diff --git a/test/unit/data/table_test.js b/test/unit/data/table_test.js index e605ad686..d909165e9 100644 --- a/test/unit/data/table_test.js +++ b/test/unit/data/table_test.js @@ -39,4 +39,50 @@ describe('DataTable', () => { const dataTable = new DataTable(data); assert.throws(() => dataTable.add(['Acid', 'Jazz', 'Singer'])); }); + + it('should filter an array', () => { + const data = ['login', 'password']; + const dataTable = new DataTable(data); + dataTable.add(['jon', 'snow']); + dataTable.add(['tyrion', 'lannister']); + dataTable.add(['jaime', 'lannister']); + + const expected = [{ + skip: false, + data: { + login: 'tyrion', + password: 'lannister', + }, + }, { + skip: false, + data: { + login: 'jaime', + password: 'lannister', + }, + }]; + assert.equal(JSON.stringify(dataTable.filter(row => row.password === 'lannister')), JSON.stringify(expected)); + }); + + it('should filter an array with skips', () => { + const data = ['login', 'password']; + const dataTable = new DataTable(data); + dataTable.add(['jon', 'snow']); + dataTable.xadd(['tyrion', 'lannister']); + dataTable.add(['jaime', 'lannister']); + + const expected = [{ + skip: true, + data: { + login: 'tyrion', + password: 'lannister', + }, + }, { + skip: false, + data: { + login: 'jaime', + password: 'lannister', + }, + }]; + assert.equal(JSON.stringify(dataTable.filter(row => row.password === 'lannister')), JSON.stringify(expected)); + }); }); From de9cc40e8e03b9d714599944c11390651b340640 Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Fri, 19 Oct 2018 12:13:12 +0300 Subject: [PATCH 4/7] Remove bootstrap call for list and definition --- lib/codecept.js | 26 ++++--- lib/command/definitions.js | 2 +- lib/command/gherkin/snippets.js | 124 +++++++++++++++++--------------- lib/command/gherkin/steps.js | 26 ++++--- lib/command/interactive.js | 27 ++++--- lib/command/list.js | 2 +- lib/command/run.js | 10 ++- 7 files changed, 124 insertions(+), 93 deletions(-) diff --git a/lib/codecept.js b/lib/codecept.js index 76c9a4f8d..81deaec8c 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -28,20 +28,20 @@ class Codecept { * Initialize CodeceptJS at specific directory. * If async initialization is required, pass callback as second parameter. * - * @param {*} dir - * @param {*} callback + * @param {string} dir + * @param {() => any} [callback] */ init(dir, callback) { this.initGlobals(dir); // initializing listeners Container.create(this.config, this.opts); - this.bootstrap(callback); + this.runHooks(callback); } /** * Creates global variables * - * @param {*} dir + * @param {string} dir */ initGlobals(dir) { global.codecept_dir = dir; @@ -66,12 +66,11 @@ class Codecept { } /** - * Executes hooks and bootstrap. - * If bootstrap is async, second parameter is required. + * Executes hooks. * - * @param {*} done + * @param {() => any} [done] */ - bootstrap(done) { + runHooks(done) { // default hooks runHook(require('./listener/steps')); runHook(require('./listener/config')); @@ -82,7 +81,16 @@ class Codecept { // custom hooks this.config.hooks.forEach(hook => runHook(hook)); - // bootstrap + if (done) done(); + } + + /** + * 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..e833ce771 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -162,7 +162,7 @@ module.exports = function (genPath, options) { const codecept = new Codecept(config, {}); codecept.init(testsPath, (err) => { if (err) { - output.error(`Error while running bootstrap file :${err}`); + output.error(`Error while running init :${err}`); return; } diff --git a/lib/command/gherkin/snippets.js b/lib/command/gherkin/snippets.js index 93fa8c138..cce2a7c59 100644 --- a/lib/command/gherkin/snippets.js +++ b/lib/command/gherkin/snippets.js @@ -21,6 +21,8 @@ module.exports = function (genPath, options) { const codecept = new Codecept(config, {}); codecept.init(testsPath, (err) => { + if (err) throw new Error(`Error while running init :${err}`); + if (!config.gherkin) { output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it'); process.exit(1); @@ -34,77 +36,81 @@ module.exports = function (genPath, options) { process.exit(1); } - 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`); + codecept.runBootstrap((err) => { + if (err) throw new Error(`Error while running bootstrap file :${err}`); - let newSteps = []; - - 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; + const files = []; + glob.sync(config.gherkin.features, { cwd: global.codecept_dir }).forEach((file) => { + if (!fsPath.isAbsolute(file)) { + file = fsPath.join(global.codecept_dir, 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); + files.push(fsPath.resolve(file)); + }); + output.print(`Loaded ${files.length} files`); + + let newSteps = []; + + 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; - }; + return 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 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)); + files.forEach(file => parseFile(file)); - let stepFile = config.gherkin.steps[0]; - if (!fsPath.isAbsolute(stepFile)) { - stepFile = fsPath.join(global.codecept_dir, stepFile); - } + 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..0ea8c1111 100644 --- a/lib/command/gherkin/steps.js +++ b/lib/command/gherkin/steps.js @@ -13,16 +13,22 @@ module.exports = function (genPath, options) { 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'); - } + if (err) throw new Error(`Error while running init :${err}`); + + codecept.runBootstrap((err) => { + if (err) throw new Error(`Error while running bootstrap file :${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'); + } + }); }); }; diff --git a/lib/command/interactive.js b/lib/command/interactive.js index 9c6363427..0cad4bb92 100644 --- a/lib/command/interactive.js +++ b/lib/command/interactive.js @@ -13,19 +13,26 @@ module.exports = function (path, options) { const codecept = new Codecept(config, options); codecept.init(testsPath, (err) => { if (err) { - output.error(`Error while running bootstrap file :${err}`); + output.error(`Error while running init :${err}`); return; } - if (options.verbose) output.level(3); + codecept.runBootstrap((err) => { + if (err) { + output.error(`Error while running bootstrap file :${err}`); + return; + } - output.print('String interactive shell for current suite...'); - recorder.start(); - event.emit(event.suite.before, {}); - event.emit(event.test.before); - require('../pause')(); - recorder.add(() => event.emit(event.test.after)); - recorder.add(() => event.emit(event.suite.after, {})); - recorder.add(() => codecept.teardown()); + if (options.verbose) output.level(3); + + output.print('String interactive shell for current suite...'); + recorder.start(); + event.emit(event.suite.before, {}); + event.emit(event.test.before); + require('../pause')(); + recorder.add(() => event.emit(event.test.after)); + recorder.add(() => event.emit(event.suite.after, {})); + recorder.add(() => codecept.teardown()); + }); }); }; diff --git a/lib/command/list.js b/lib/command/list.js index ca5c877c6..a3d21e2e0 100644 --- a/lib/command/list.js +++ b/lib/command/list.js @@ -12,7 +12,7 @@ module.exports = function (path) { const codecept = new Codecept(config, {}); codecept.init(testsPath, (err) => { if (err) { - output.error(`Error while running bootstrap file :${err}`); + output.error(`Error while running init :${err}`); return; } diff --git a/lib/command/run.js b/lib/command/run.js index bca890c06..b3e2230dd 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -33,9 +33,13 @@ module.exports = function (test, options) { try { codecept = new Codecept(config, options); codecept.init(testRoot, (err) => { - if (err) throw new Error(`Error while running bootstrap file :${err}`); - codecept.loadTests(); - codecept.run(test); + if (err) throw new Error(`Error while running init :${err}`); + + codecept.runBootstrap((err) => { + if (err) throw new Error(`Error while running bootstrap file :${err}`); + codecept.loadTests(); + codecept.run(test); + }); }); } catch (err) { output.print(''); From a27fd1d43d7ee6e1c0ae574fe33103f5b3964ff5 Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Fri, 19 Oct 2018 12:55:12 +0300 Subject: [PATCH 5/7] remove teardown from list and def --- lib/command/definitions.js | 2 -- lib/command/list.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/command/definitions.js b/lib/command/definitions.js index e833ce771..8b09ced32 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -206,8 +206,6 @@ module.exports = function (genPath, options) { 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/// ')); - - codecept.teardown(); }); }; diff --git a/lib/command/list.js b/lib/command/list.js index a3d21e2e0..e4b699c5c 100644 --- a/lib/command/list.js +++ b/lib/command/list.js @@ -38,7 +38,5 @@ module.exports = function (path) { } output.print('PS: Actions are retrieved from enabled helpers. '); output.print('Implement custom actions in your helper classes.'); - - codecept.teardown(); }); }; From fb29c721d28cb9afc4c6f57bab1a03ead10f40cc Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Tue, 23 Oct 2018 09:44:34 +0300 Subject: [PATCH 6/7] remove bootstrap from gherkin commands --- lib/command/gherkin/snippets.js | 122 +++++++++++++++----------------- lib/command/gherkin/steps.js | 24 +++---- 2 files changed, 69 insertions(+), 77 deletions(-) diff --git a/lib/command/gherkin/snippets.js b/lib/command/gherkin/snippets.js index cce2a7c59..bdd2e021b 100644 --- a/lib/command/gherkin/snippets.js +++ b/lib/command/gherkin/snippets.js @@ -36,81 +36,77 @@ module.exports = function (genPath, options) { process.exit(1); } - codecept.runBootstrap((err) => { - if (err) throw new Error(`Error while running bootstrap file :${err}`); - - 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 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`); - let newSteps = []; + let newSteps = []; - 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); - } + 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; } - return 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) }); - })); + 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); } - }; - - files.forEach(file => parseFile(file)); + } + 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)); - const snippets = newSteps - .filter((value, index, self) => self.indexOf(value) === index) - .map((step) => { - return ` + 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 ` ${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 0ea8c1111..c85c5e39a 100644 --- a/lib/command/gherkin/steps.js +++ b/lib/command/gherkin/steps.js @@ -15,20 +15,16 @@ module.exports = function (genPath, options) { codecept.init(testsPath, (err) => { if (err) throw new Error(`Error while running init :${err}`); - codecept.runBootstrap((err) => { - if (err) throw new Error(`Error while running bootstrap file :${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'); - } - }); + 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'); + } }); }; From 1de8ea7d1dbd6051364f3ee4760a273cc7b52925 Mon Sep 17 00:00:00 2001 From: Evgeny Lukoyanov Date: Tue, 23 Oct 2018 10:14:04 +0300 Subject: [PATCH 7/7] remove callback from codeceptjs.init() --- lib/codecept.js | 11 +-- lib/command/definitions.js | 85 +++++++++---------- lib/command/gherkin/snippets.js | 146 ++++++++++++++++---------------- lib/command/gherkin/steps.js | 24 +++--- lib/command/interactive.js | 31 +++---- lib/command/list.js | 49 +++++------ lib/command/run.js | 13 ++- 7 files changed, 167 insertions(+), 192 deletions(-) diff --git a/lib/codecept.js b/lib/codecept.js index 81deaec8c..22b769ebc 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -29,13 +29,12 @@ class Codecept { * If async initialization is required, pass callback as second parameter. * * @param {string} dir - * @param {() => any} [callback] */ - init(dir, callback) { + init(dir) { this.initGlobals(dir); // initializing listeners Container.create(this.config, this.opts); - this.runHooks(callback); + this.runHooks(); } /** @@ -67,10 +66,8 @@ class Codecept { /** * Executes hooks. - * - * @param {() => any} [done] */ - runHooks(done) { + runHooks() { // default hooks runHook(require('./listener/steps')); runHook(require('./listener/config')); @@ -80,8 +77,6 @@ class Codecept { // custom hooks this.config.hooks.forEach(hook => runHook(hook)); - - if (done) done(); } /** diff --git a/lib/command/definitions.js b/lib/command/definitions.js index 8b09ced32..501fdfa07 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -160,53 +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 init :${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); - } + 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); + } - 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); - } + 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); + } - 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/// ')); - }); + 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 bdd2e021b..6b735c4b8 100644 --- a/lib/command/gherkin/snippets.js +++ b/lib/command/gherkin/snippets.js @@ -20,93 +20,91 @@ module.exports = function (genPath, options) { if (!config) return; const codecept = new Codecept(config, {}); - codecept.init(testsPath, (err) => { - if (err) throw new Error(`Error while running init :${err}`); + codecept.init(testsPath); - 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); - } + 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); + } - 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 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`); - let newSteps = []; + let newSteps = []; - 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); - } + 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; } - return 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) }); - })); + 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); } - }; - - files.forEach(file => parseFile(file)); + } + 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 c85c5e39a..0a9f8173a 100644 --- a/lib/command/gherkin/steps.js +++ b/lib/command/gherkin/steps.js @@ -12,19 +12,17 @@ module.exports = function (genPath, options) { if (!config) return; const codecept = new Codecept(config, {}); - codecept.init(testsPath, (err) => { - if (err) throw new Error(`Error while running init :${err}`); + 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'); - } - }); + 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 0cad4bb92..5f3215255 100644 --- a/lib/command/interactive.js +++ b/lib/command/interactive.js @@ -11,28 +11,23 @@ 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 init :${err}`); + output.error(`Error while running bootstrap file :${err}`); return; } - codecept.runBootstrap((err) => { - if (err) { - output.error(`Error while running bootstrap file :${err}`); - return; - } - - if (options.verbose) output.level(3); + if (options.verbose) output.level(3); - output.print('String interactive shell for current suite...'); - recorder.start(); - event.emit(event.suite.before, {}); - event.emit(event.test.before); - require('../pause')(); - recorder.add(() => event.emit(event.test.after)); - recorder.add(() => event.emit(event.suite.after, {})); - recorder.add(() => codecept.teardown()); - }); + output.print('String interactive shell for current suite...'); + recorder.start(); + event.emit(event.suite.before, {}); + event.emit(event.test.before); + require('../pause')(); + recorder.add(() => event.emit(event.test.after)); + recorder.add(() => event.emit(event.suite.after, {})); + recorder.add(() => codecept.teardown()); }); }; diff --git a/lib/command/list.js b/lib/command/list.js index e4b699c5c..cd488fc29 100644 --- a/lib/command/list.js +++ b/lib/command/list.js @@ -10,33 +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 init :${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.'); - }); + 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 b3e2230dd..51a51a892 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -32,14 +32,13 @@ module.exports = function (test, options) { try { codecept = new Codecept(config, options); - codecept.init(testRoot, (err) => { - if (err) throw new Error(`Error while running init :${err}`); + codecept.init(testRoot); - codecept.runBootstrap((err) => { - if (err) throw new Error(`Error while running bootstrap file :${err}`); - codecept.loadTests(); - codecept.run(test); - }); + codecept.runBootstrap((err) => { + if (err) throw new Error(`Error while running bootstrap file :${err}`); + + codecept.loadTests(); + codecept.run(test); }); } catch (err) { output.print('');