Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions lib/codecept.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { existsSync, readFileSync } = require('fs')
const glob = require('glob')
const { globSync } = require('glob')
const fsPath = require('path')
const { resolve } = require('path')

Expand Down Expand Up @@ -168,15 +168,17 @@ class Codecept {
}

for (pattern of patterns) {
glob.sync(pattern, options).forEach(file => {
if (file.includes('node_modules')) return
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file)
}
if (!this.testFiles.includes(fsPath.resolve(file))) {
this.testFiles.push(fsPath.resolve(file))
}
})
if (pattern) {
globSync(pattern, options).forEach(file => {
if (file.includes('node_modules')) return
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file)
}
if (!this.testFiles.includes(fsPath.resolve(file))) {
this.testFiles.push(fsPath.resolve(file))
}
})
}
}
}

Expand Down
138 changes: 69 additions & 69 deletions lib/command/gherkin/snippets.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,113 @@
const escapeStringRegexp = require('escape-string-regexp');
const fs = require('fs');
const Gherkin = require('@cucumber/gherkin');
const Messages = require('@cucumber/messages');
const glob = require('glob');
const fsPath = require('path');
const escapeStringRegexp = require('escape-string-regexp')
const fs = require('fs')
const Gherkin = require('@cucumber/gherkin')
const Messages = require('@cucumber/messages')
const { globSync } = require('glob')
const fsPath = require('path')

const { getConfig, getTestRoot } = require('../utils');
const Codecept = require('../../codecept');
const output = require('../../output');
const { matchStep } = require('../../mocha/bdd');
const { getConfig, getTestRoot } = require('../utils')
const Codecept = require('../../codecept')
const output = require('../../output')
const { matchStep } = require('../../mocha/bdd')

const uuidFn = Messages.IdGenerator.uuid();
const builder = new Gherkin.AstBuilder(uuidFn);
const matcher = new Gherkin.GherkinClassicTokenMatcher();
const parser = new Gherkin.Parser(builder, matcher);
parser.stopAtFirstError = false;
const uuidFn = Messages.IdGenerator.uuid()
const builder = new Gherkin.AstBuilder(uuidFn)
const matcher = new Gherkin.GherkinClassicTokenMatcher()
const parser = new Gherkin.Parser(builder, matcher)
parser.stopAtFirstError = false

module.exports = function (genPath, options) {
const configFile = options.config || genPath;
const testsPath = getTestRoot(configFile);
const config = getConfig(configFile);
if (!config) return;
const configFile = options.config || genPath
const testsPath = getTestRoot(configFile)
const config = getConfig(configFile)
if (!config) return

const codecept = new Codecept(config, {});
codecept.init(testsPath);
const codecept = new Codecept(config, {})
codecept.init(testsPath)

if (!config.gherkin) {
output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it');
process.exit(1);
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);
output.error('No gherkin steps defined in config. Exiting')
process.exit(1)
}
if (!options.feature && !config.gherkin.features) {
output.error('No gherkin features defined in config. Exiting');
process.exit(1);
output.error('No gherkin features defined in config. Exiting')
process.exit(1)
}
if (options.path && !config.gherkin.steps.includes(options.path)) {
output.error(`You must include ${options.path} to the gherkin steps in your config file`);
process.exit(1);
output.error(`You must include ${options.path} to the gherkin steps in your config file`)
process.exit(1)
}

const files = [];
glob.sync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => {
const files = []
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => {
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file);
file = fsPath.join(global.codecept_dir, file)
}
files.push(fsPath.resolve(file));
});
output.print(`Loaded ${files.length} files`);
files.push(fsPath.resolve(file))
})
output.print(`Loaded ${files.length} files`)

const newSteps = new Map();
const newSteps = new Map()

const parseSteps = steps => {
const newSteps = [];
let currentKeyword = '';
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;
if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`)
step.keyword = currentKeyword
}
currentKeyword = step.keyword;
currentKeyword = step.keyword
try {
matchStep(step.text);
matchStep(step.text)
} catch (err) {
let stepLine;
let stepLine
if (/[{}()/]/.test(step.text)) {
stepLine = escapeStringRegexp(step.text)
.replace(/\//g, '\\/')
.replace(/\"(.*?)\"/g, '"(.*?)"')
.replace(/(\d+\\\.\d+)/, '(\\d+\\.\\d+)')
.replace(/ (\d+) /, ' (\\d+) ');
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true });
.replace(/ (\d+) /, ' (\\d+) ')
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true })
} else {
stepLine = step.text
.replace(/\"(.*?)\"/g, '{string}')
.replace(/(\d+\.\d+)/, '{float}')
.replace(/ (\d+) /, ' {int} ');
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false });
.replace(/ (\d+) /, ' {int} ')
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false })
}
newSteps.push(stepLine);
newSteps.push(stepLine)
}
}
return newSteps;
};
return newSteps
}

const parseFile = file => {
const ast = parser.parse(fs.readFileSync(file).toString());
const ast = parser.parse(fs.readFileSync(file).toString())
for (const child of ast.feature.children) {
if (child.scenario.keyword === 'Scenario Outline') continue; // skip scenario outline
if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline
parseSteps(child.scenario.steps)
.map(step => {
return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) });
return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) })
})
.map(step => newSteps.set(`${step.type}(${step})`, step));
.map(step => newSteps.set(`${step.type}(${step})`, step))
}
};
}

files.forEach(file => parseFile(file));
files.forEach(file => parseFile(file))

let stepFile = options.path || config.gherkin.steps[0];
let stepFile = options.path || config.gherkin.steps[0]
if (!fs.existsSync(stepFile)) {
output.error(`Please enter a valid step file path ${stepFile}`);
process.exit(1);
output.error(`Please enter a valid step file path ${stepFile}`)
process.exit(1)
}

if (!fsPath.isAbsolute(stepFile)) {
stepFile = fsPath.join(global.codecept_dir, stepFile);
stepFile = fsPath.join(global.codecept_dir, stepFile)
}

const snippets = [...newSteps.values()]
Expand All @@ -117,18 +117,18 @@ module.exports = function (genPath, options) {
${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => {
// From "${step.file}" ${JSON.stringify(step.location)}
throw new Error('Not implemented yet');
});`;
});
});`
})

if (!snippets.length) {
output.print('No new snippets found');
return;
output.print('No new snippets found')
return
}
output.success(`Snippets generated: ${snippets.length}`);
output.print(snippets.join('\n'));
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');
output.success(`Snippets added to ${output.colors.bold(stepFile)}`)
fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n')
}
};
}
93 changes: 48 additions & 45 deletions lib/command/run-multiple/chunk.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,94 @@
const glob = require('glob');
const path = require('path');
const fs = require('fs');
const { globSync } = require('glob')
const path = require('path')
const fs = require('fs')

/**
* Splits a list to (n) parts, defined via the size argument.
*/
const splitFiles = (list, size) => {
const sets = [];
const chunks = list.length / size;
let i = 0;
const sets = []
const chunks = list.length / size
let i = 0

while (i < chunks) {
sets[i] = list.splice(0, size);
i++;
sets[i] = list.splice(0, size)
i++
}

return sets;
};
return sets
}

/**
* Executes a glob pattern and pushes the results to a list.
*/
const findFiles = (pattern) => {
const files = [];
const findFiles = pattern => {
const files = []

glob.sync(pattern).forEach((file) => {
files.push(path.resolve(file));
});
globSync(pattern).forEach(file => {
files.push(path.resolve(file))
})

return files;
};
return files
}

/**
* Joins a list of files to a valid glob pattern
*/
const flattenFiles = (list) => {
const pattern = list.join(',');
return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern;
};
const flattenFiles = list => {
const pattern = list.join(',')
return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern
}

/**
* Greps a file by its content, checks if Scenario or Feature text'
* matches the grep text.
*/
const grepFile = (file, grep) => {
const contents = fs.readFileSync(file, 'utf8');
const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g'); // <- How future proof/solid is this?
return !!pattern.exec(contents);
};
const contents = fs.readFileSync(file, 'utf8')
const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g') // <- How future proof/solid is this?
return !!pattern.exec(contents)
}

const mapFileFormats = (files) => {
const mapFileFormats = files => {
return {
gherkin: files.filter(file => file.match(/\.feature$/)),
js: files.filter(file => file.match(/\.t|js$/)),
};
};
}
}

/**
* Creates a list of chunks incl. configuration by either dividing a list of scenario
* files by the passed number or executing a usder deifned function to perform
* the splitting.
*/
const createChunks = (config, patterns = []) => {
const files = patterns.filter(pattern => !!pattern).map((pattern) => {
return findFiles(pattern).filter((file) => {
return config.grep ? grepFile(file, config.grep) : true;
});
}).reduce((acc, val) => acc.concat(val), []);
const files = patterns
.filter(pattern => !!pattern)
.map(pattern => {
return findFiles(pattern).filter(file => {
return config.grep ? grepFile(file, config.grep) : true
})
})
.reduce((acc, val) => acc.concat(val), [])

let chunks = [];
let chunks = []
if (typeof config.chunks === 'function') {
chunks = config.chunks.call(this, files);
chunks = config.chunks.call(this, files)
} else if (typeof config.chunks === 'number' || typeof config.chunks === 'string') {
chunks = splitFiles(files, Math.ceil(files.length / config.chunks));
chunks = splitFiles(files, Math.ceil(files.length / config.chunks))
} else {
throw new Error('chunks is neither a finite number or a valid function');
throw new Error('chunks is neither a finite number or a valid function')
}

const chunkConfig = { ...config };
delete chunkConfig.chunks;
const chunkConfig = { ...config }
delete chunkConfig.chunks

return chunks.map((chunkFiles) => {
const { js, gherkin } = mapFileFormats(chunkFiles);
return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } };
});
};
return chunks.map(chunkFiles => {
const { js, gherkin } = mapFileFormats(chunkFiles)
return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } }
})
}

module.exports = {
createChunks,
};
}
4 changes: 2 additions & 2 deletions lib/container.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const glob = require('glob')
const { globSync } = require('glob')
const path = require('path')
const debug = require('debug')('codeceptjs:container')
const { MetaStep } = require('./step')
Expand Down Expand Up @@ -464,7 +464,7 @@ function loadGherkinSteps(paths) {
} else {
const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : ''
if (folderPath !== '') {
glob.sync(folderPath).forEach(file => {
globSync(folderPath).forEach(file => {
loadSupportObject(file, `Step Definition from ${file}`)
})
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"figures": "3.2.0",
"fn-args": "4.0.0",
"fs-extra": "11.3.0",
"glob": "^11.0.1",
"glob": ">=9.0.0 <12",
"fuse.js": "^7.0.0",
"html-minifier-terser": "7.2.0",
"inquirer": "6.5.2",
Expand Down