UNPKG

codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

200 lines (180 loc) 6.55 kB
const Gherkin = require('@cucumber/gherkin') const Messages = require('@cucumber/messages') const { Context, Suite } = require('mocha') const debug = require('debug')('codeceptjs:bdd') const { enhanceMochaSuite } = require('./suite') const { createTest } = require('./test') const { matchStep } = require('./bdd') const event = require('../event') const { injected, setup, teardown, suiteSetup, suiteTeardown } = require('./asyncWrapper') const Step = require('../step') const DataTableArgument = require('../data/dataTableArgument') const transform = require('../transform') 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 = (text, file) => { const ast = parser.parse(text) let currentLanguage if (ast.feature) { currentLanguage = getTranslation(ast.feature.language) } if (!ast.feature) { throw new Error(`No 'Features' available in Gherkin '${file}' provided!`) } const suite = new Suite(ast.feature.name, new Context()) enhanceMochaSuite(suite) const tags = ast.feature.tags.map(t => t.name) suite.title = `${suite.title} ${tags.join(' ')}`.trim() suite.tags = tags || [] suite.comment = ast.feature.description suite.feature = ast.feature suite.file = file suite.timeout(0) suite.beforeEach('codeceptjs.before', () => setup(suite)) suite.afterEach('codeceptjs.after', () => teardown(suite)) suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite)) suite.afterAll('codeceptjs.afterSuite', () => suiteTeardown(suite)) const runSteps = async steps => { for (const step of steps) { const metaStep = new Step.MetaStep(null, step.text) metaStep.actor = step.keyword.trim() let helperStep const setMetaStep = step => { helperStep = step if (step.metaStep) { if (step.metaStep === metaStep) { return } setMetaStep(step.metaStep) return } step.metaStep = metaStep } const fn = matchStep(step.text) if (step.dataTable) { fn.params.push({ ...step.dataTable, parse: () => new DataTableArgument(step.dataTable), }) metaStep.comment = `\n${transformTable(step.dataTable)}` } if (step.docString) { fn.params.push(step.docString) metaStep.comment = `\n"""\n${step.docString.content}\n"""` } step.startTime = Date.now() step.match = fn.line event.emit(event.bddStep.before, step) event.emit(event.bddStep.started, metaStep) event.dispatcher.prependListener(event.step.before, setMetaStep) try { debug(`Step '${step.text}' started...`) await fn(...fn.params) debug('Step passed') step.status = 'passed' } catch (err) { debug(`Step failed: ${err?.message}`) step.status = 'failed' step.err = err throw err } finally { step.endTime = Date.now() event.dispatcher.removeListener(event.step.before, setMetaStep) } event.emit(event.bddStep.finished, metaStep) event.emit(event.bddStep.after, step) } } for (const child of ast.feature.children) { if (child.background) { suite.beforeEach( 'Before', injected(async () => runSteps(child.background.steps), suite, 'before'), ) continue } if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline === child.scenario.keyword : child.scenario.keyword === 'Scenario Outline')) { for (const examples of child.scenario.examples) { const fields = examples.tableHeader.cells.map(c => c.value) for (const example of examples.tableBody) { let exampleSteps = [...child.scenario.steps] const current = {} for (const index in example.cells) { const placeholder = fields[index] const value = transform('gherkin.examples', example.cells[index].value) example.cells[index].value = value current[placeholder] = value exampleSteps = exampleSteps.map(step => { step = { ...step } step.text = step.text.split(`<${placeholder}>`).join(value) return step }) } const tags = child.scenario.tags.map(t => t.name).concat(examples.tags.map(t => t.name)) let title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim() for (const [key, value] of Object.entries(current)) { if (title.includes(`<${key}>`)) { title = title.replace(JSON.stringify(current), '').replace(`<${key}>`, value) } } const test = createTest(title, async () => runSteps(addExampleInTable(exampleSteps, current))) test.addToSuite(suite) test.tags = suite.tags.concat(tags) test.file = file } } continue } if (child.scenario) { const tags = child.scenario.tags.map(t => t.name) const title = `${child.scenario.name} ${tags.join(' ')}`.trim() const test = createTest(title, async () => runSteps(child.scenario.steps)) test.addToSuite(suite) test.tags = suite.tags.concat(tags) test.file = file } } return suite } function transformTable(table) { let str = '' for (const id in table.rows) { const cells = table.rows[id].cells str += cells .map(c => c.value) .map(c => c.padEnd(15)) .join(' | ') str += '\n' } return str } function addExampleInTable(exampleSteps, placeholders) { const steps = JSON.parse(JSON.stringify(exampleSteps)) for (const placeholder in placeholders) { steps.map(step => { step = { ...step } if (step.dataTable) { for (const id in step.dataTable.rows) { const cells = step.dataTable.rows[id].cells cells.map(c => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder]))) } } return step }) } return steps } function getTranslation(language) { const translations = Object.keys(require('../../translations')) for (const availableTranslation of translations) { if (!language) { break } if (availableTranslation.includes(language)) { return require('../../translations')[availableTranslation] } } }