All files / wdio-cucumber-framework/src index.js

98.25% Statements 56/57
92.31% Branches 24/26
100% Functions 14/14
98.25% Lines 56/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210                                      13x 13x 13x 13x 13x 13x 13x             3x 3x 3x 3x 3x 3x 3x   3x   3x 3x             3x   3x         3x             3x             2x 2x           2x       1x 1x     3x         3x 1x     2x                         3x 3x 1x 2x 1x   1x           1x 2x                       1x         1x 1x 2x         2x 2x   1x             1x 1x   1x 2x 2x                           2x 2x                         2x 2x         1x 1x                            
import * as Cucumber from 'cucumber'
import mockery from 'mockery'
import isGlob from 'is-glob'
import glob from 'glob'
import path from 'path'
 
import CucumberReporter from './reporter'
 
import Hookrunner from './hookRunner'
import { EventEmitter } from 'events'
 
import {
    executeHooksWithArgs, executeSync, executeAsync,
    runFnInFiberContext, hasWdioSyncSupport
} from '@wdio/config'
import { DEFAULT_OPTS } from './constants'
 
class CucumberAdapter {
    constructor (cid, config, specs, capabilities, reporter) {
        this.cwd = process.cwd()
        this.cid = cid
        this.specs = specs
        this.reporter = reporter
        this.capabilities = capabilities
        this.config = config
        this.cucumberOpts = Object.assign(DEFAULT_OPTS, config.cucumberOpts)
    }
 
    async run () {
        let runtimeError
        let result
 
        try {
            this.registerRequiredModules()
            Cucumber.supportCodeLibraryBuilder.reset(this.cwd)
            this.loadSpecFiles()
            this.wrapSteps()
            Cucumber.setDefaultTimeout(this.cucumberOpts.timeout)
            const supportCodeLibrary = Cucumber.supportCodeLibraryBuilder.finalize()
 
            const eventBroadcaster = new EventEmitter()
            // eslint-disable-next-line no-new
            new Hookrunner(eventBroadcaster, this.config)
            const reporterOptions = {
                capabilities: this.capabilities,
                ignoreUndefinedDefinitions: Boolean(this.cucumberOpts.ignoreUndefinedDefinitions),
                failAmbiguousDefinitions: Boolean(this.cucumberOpts.failAmbiguousDefinitions),
                tagsInTitle: Boolean(this.cucumberOpts.tagsInTitle)
            }
 
            this.cucumberReporter = new CucumberReporter(eventBroadcaster, reporterOptions, this.cid, this.specs, this.reporter)
 
            const pickleFilter = new Cucumber.PickleFilter({
                featurePaths: this.specs,
                names: this.cucumberOpts.name,
                tagExpression: this.cucumberOpts.tagExpression
            })
            const testCases = await Cucumber.getTestCasesFromFilesystem({
                cwd: this.cwd,
                eventBroadcaster,
                featurePaths: this.specs,
                order: this.cucumberOpts.order,
                pickleFilter
            })
            const runtime = new Cucumber.Runtime({
                eventBroadcaster,
                options: this.cucumberOpts,
                supportCodeLibrary,
                testCases
            })
 
            await executeHooksWithArgs(this.config.before, [this.capabilities, this.specs])
            result = await runtime.start() ? 0 : 1
 
            /**
             * if we ignore undefined definitions we trust the reporter
             * with the fail count
             */
            Iif (this.cucumberOpts.ignoreUndefinedDefinitions && result) {
                result = this.cucumberReporter.failedCount
            }
        } catch (e) {
            runtimeError = e
            result = 1
        }
 
        await executeHooksWithArgs(this.config.after, [runtimeError || result, this.capabilities, this.specs])
 
        /**
         * in case the spec has a runtime error throw after the wdio hook
         */
        if (runtimeError) {
            throw runtimeError
        }
 
        return result
    }
 
    /**
     * Transpilation https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#transpilation
     * Usage: `['module']`
     * we extend it a bit with ability to init and pass configuration to modules.
     * Pass an array with path to module and its configuration instead:
     * Usage: `[['module', {}]]`
     * Or pass your own function
     * Usage: `[() => { require('ts-node').register({ files: true }) }]`
     */
    registerRequiredModules () {
        this.cucumberOpts.requireModule.map(requiredModule => {
            if (Array.isArray(requiredModule)) {
                require(requiredModule[0])(requiredModule[1])
            } else if (typeof requiredModule === 'function') {
                requiredModule()
            } else {
                require(requiredModule)
            }
        })
    }
 
    requiredFiles () {
        return this.cucumberOpts.require.reduce(
            (files, requiredFile) => files.concat(isGlob(requiredFile)
                ? glob.sync(requiredFile)
                : [requiredFile]
            ),
            []
        )
    }
 
    loadSpecFiles () {
        // we use mockery to allow people to import 'our' cucumber even though their spec files are in their folders
        // because of that we don't have to attach anything to the global object, and the current cucumber spec files
        // should just work with no changes with this framework
        mockery.enable({
            useCleanCache: false,
            warnOnReplace: false,
            warnOnUnregistered: false
        })
        mockery.registerMock('cucumber', Cucumber)
        this.requiredFiles().forEach((codePath) => {
            const filepath = path.isAbsolute(codePath)
                ? codePath
                : path.join(process.cwd(), codePath)
 
            // This allows rerunning a stepDefinitions file
            delete require.cache[require.resolve(filepath)]
            require(filepath)
        })
        mockery.disable()
    }
 
    /**
     * wraps step definition code with sync/async runner with a retry option
     */
    wrapSteps () {
        const wrapStepSync = this.wrapStepSync
        const wrapStepAsync = this.wrapStepAsync
 
        Cucumber.setDefinitionFunctionWrapper((fn, options = {}) => {
            const retryTest = isFinite(options.retry) ? parseInt(options.retry, 10) : 0
            return fn.name === 'async' || !hasWdioSyncSupport
                ? wrapStepAsync(fn, retryTest)
                /* istanbul ignore next */
                : wrapStepSync(fn, retryTest)
        })
    }
 
    /**
     * wrap step definition to enable retry ability
     * @param  {Function} code       step definition
     * @param  {Number}   retryTest  amount of allowed repeats is case of a failure
     * @return {Function}            wrapped step definiton for sync WebdriverIO code
     */
    wrapStepSync (code, retryTest = 0) {
        return function (...args) {
            return runFnInFiberContext(
                executeSync.bind(this, code, retryTest, args),
            ).apply(this)
        }
    }
 
    /**
     * wrap step definition to enable retry ability
     * @param  {Function} code       step definitoon
     * @param  {Number}   retryTest  amount of allowed repeats is case of a failure
     * @return {Function}            wrapped step definiton for async WebdriverIO code
     */
    wrapStepAsync (code, retryTest = 0) {
        return function (...args) {
            return executeAsync.call(this, code, retryTest, args)
        }
    }
}
 
const _CucumberAdapter = CucumberAdapter
const adapterFactory = {}
 
/**
 * tested by smoke tests
 */
/* istanbul ignore next */
adapterFactory.run = async function (...args) {
    const adapter = new _CucumberAdapter(...args)
    const result = await adapter.run()
    return result
}
 
export default adapterFactory
export { CucumberAdapter, adapterFactory }