Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add step data and context to hooks #4545

Merged
merged 8 commits into from
Sep 30, 2019
12 changes: 7 additions & 5 deletions docs/ConfigurationFile.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,17 @@ exports.config = {
},
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
* beforeEach in Mocha).
* stepData and world are Cucumber framework specific
*/
beforeHook: function (test, context) {
beforeHook: function (test, context/*, stepData, world*/) {
},
/**
* Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
* afterEach in Mocha)
* stepData and world are Cucumber framework specific
*/
afterHook: function (test, context, { error, result, duration, passed }) {
afterHook: function (test, context, { error, result, duration, passed }/*, stepData, world*/) {
},
/**
* Function to be executed before a test (in Mocha/Jasmine) starts.
Expand Down Expand Up @@ -346,9 +348,9 @@ exports.config = {
},
beforeScenario: function (uri, feature, scenario, sourceLocation) {
},
beforeStep: function (uri, feature) {
beforeStep: function (uri, feature, stepData, context) {
},
afterStep: function (uri, feature, { error, result, duration, passed }) {
afterStep: function (uri, feature, { error, result, duration, passed }, stepData, context) {
},
afterScenario: function (uri, feature, scenario, result, sourceLocation) {
},
Expand Down
10 changes: 6 additions & 4 deletions examples/pageobject/wdio.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,14 @@ exports.config = {
//
// Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
// beforeEach in Mocha)
// beforeHook: function (test, context) {
// stepData and world are Cucumber framework specific
// beforeHook: function (test, context, stepData, world) {
// },
//
// Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
// afterEach in Mocha)
// afterHook: function (test, context, { error, result, duration, passed }) {
// stepData and world are Cucumber framework specific
// afterHook: function (test, context, { error, result, duration, passed }, stepData, world) {
// },
//
// Function to be executed before a test (in Mocha/Jasmine) starts.
Expand Down Expand Up @@ -146,11 +148,11 @@ exports.config = {
// },
//
// Runs before a Cucumber Step
// beforeStep: function (uri, feature) {
// beforeStep: function (uri, feature, stepData, context) {
// },
//
// Runs after a Cucumber Step
// afterStep: function (uri, feature, { error, result, duration, passed }) {
// afterStep: function (uri, feature, { error, result, duration, passed }, stepData, context) {
// },
//
// Gets executed after all tests are done. You still have access to all global variables from
Expand Down
10 changes: 6 additions & 4 deletions examples/wdio.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,16 @@ exports.config = {
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
* stepData and world are Cucumber framework specific
*/
beforeHook: function (test, context) {
beforeHook: function (test, context/*, stepData, world*/) {
},
/**
* Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
* afterEach in Mocha)
* stepData and world are Cucumber framework specific
*/
afterHook: function (test, context, { error, result, duration, passed }) {
afterHook: function (test, context, { error, result, duration, passed }/*, stepData, world*/) {
},
/**
* Function to be executed before a test (in Mocha/Jasmine) starts.
Expand Down Expand Up @@ -315,9 +317,9 @@ exports.config = {
},
beforeScenario: function (uri, feature, scenario, sourceLocation) {
},
beforeStep: function (uri, feature) {
beforeStep: function (uri, feature, stepData, context) {
},
afterStep: function (uri, feature, { error, result, duration, passed }) {
afterStep: function (uri, feature, { error, result, duration, passed }, stepData, context) {
},
afterScenario: function (uri, feature, scenario, result, sourceLocation) {
},
Expand Down
4 changes: 2 additions & 2 deletions examples/wdio/custom-service/my.custom.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ module.exports = class CustomService {
console.log('execute beforeScenario(uri, feature, scenario, sourceLocation)')
}
beforeStep () {
console.log('execute beforeStep(uri, feature)')
console.log('execute beforeStep(uri, feature, stepData, context)')
}
afterStep () {
console.log('execute afterStep(uri, feature, { error, result, duration, passed })')
console.log('execute afterStep(uri, feature, { error, result, duration, passed }, stepData, context)')
}
afterScenario () {
console.log('execute afterScenario(uri, feature, scenario, result, sourceLocation)')
Expand Down
4 changes: 2 additions & 2 deletions packages/wdio-cli/src/templates/wdio.conf.tpl.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,12 @@ exports.config = {
/**
* Runs before a Cucumber step
*/
// beforeStep: function (uri, feature) {
// beforeStep: function (uri, feature, stepData, context) {
// },
/**
* Runs after a Cucumber step
*/
// afterStep: function (uri, feature, { error, result, duration, passed }) {
// afterStep: function (uri, feature, { error, result, duration, passed }, stepData, context) {
// },
/**
* Runs after a Cucumber scenario
Expand Down
4 changes: 2 additions & 2 deletions packages/wdio-cucumber-framework/cucumber-framework.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ interface CucumberHookObject {
interface CucumberHookFunctions {
beforeFeature?(uri: string, feature: CucumberHookObject, scenarios: CucumberHookObject[]): void;
beforeScenario?(uri: string, feature: CucumberHookObject, scenario: CucumberHookObject, sourceLocation: SourceLocation): void;
beforeStep?(uri: string, feature: CucumberHookObject): void;
afterStep?(uri: string, feature: CucumberHookObject, result: { error?: any, result?: any, passed: boolean, duration: number }): void;
beforeStep?(uri: string, feature: CucumberHookObject, stepData?: any, context?: any): void;
afterStep?(uri: string, feature: CucumberHookObject, result: { error?: any, result?: any, passed: boolean, duration: number }, stepData?: any, context?: any): void;
afterScenario?(uri: string, feature: CucumberHookObject, scenario: CucumberHookObject, result: CucumberHookResult, sourceLocation: SourceLocation): void;
afterFeature?(uri: string, feature: CucumberHookObject, scenarios: CucumberHookObject[]): void;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/wdio-cucumber-framework/src/cucumberEventListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default class CucumberEventListener extends EventEmitter {
gherkinDocEvents = []
acceptedPickles = []
currentPickle = null
currentStep = null
testCasePreparedEvents = []

constructor (eventBroadcaster) {
Expand Down Expand Up @@ -106,6 +107,10 @@ export default class CucumberEventListener extends EventEmitter {
const scenario = feature.children.find((child) => compareScenarioLineWithSourceLine(child, sourceLocation))
const step = getStepFromFeature(feature, this.currentPickle, testStepStartedEvent.index, sourceLocation)

if (step.type === 'Step') {
this.currentStep = { uri, feature, scenario, step, sourceLocation }
}

this.emit('before-step', uri, feature, scenario, step, sourceLocation)
}

Expand Down Expand Up @@ -184,6 +189,7 @@ export default class CucumberEventListener extends EventEmitter {
this.emit('after-scenario', uri, feature, scenario, result, sourceLocation)

this.currentPickle = null
this.currentStep = null
}

// testRunFinishedEvent = {
Expand All @@ -197,4 +203,8 @@ export default class CucumberEventListener extends EventEmitter {

this.emit('after-feature', uri, feature)
}

getCurrentStep () {
return this.currentStep
}
}
26 changes: 18 additions & 8 deletions packages/wdio-cucumber-framework/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ class CucumberAdapter {

this.cucumberReporter = new CucumberReporter(eventBroadcaster, reporterOptions, this.cid, this.specs, this.reporter)

/**
* gets current step data: `{ uri, feature, scenario, step, sourceLocation }`
* or `null` for some hooks.
*
* @return {object|null}
*/
this.getCurrentStep = ::this.cucumberReporter.eventListener.getCurrentStep

const pickleFilter = new Cucumber.PickleFilter({
featurePaths: this.specs,
names: this.cucumberOpts.name,
Expand Down Expand Up @@ -185,6 +193,7 @@ class CucumberAdapter {
wrapSteps (config) {
const wrapStep = this.wrapStep
const cid = this.cid
const getCurrentStep = () => this.getCurrentStep()

Cucumber.setDefinitionFunctionWrapper((fn, options = {}) => {
/**
Expand All @@ -202,20 +211,21 @@ class CucumberAdapter {
const isStep = !fn.name.startsWith('userHook')

const retryTest = isStep && isFinite(options.retry) ? parseInt(options.retry, 10) : 0
return wrapStep(fn, retryTest, isStep, config, cid)
return wrapStep(fn, retryTest, isStep, config, cid, getCurrentStep)
})
}

/**
* wrap step definition to enable retry ability
* @param {Function} code step definitoon
* @param {Number} retryTest amount of allowed repeats is case of a failure
* @param {Function} code step definitoon
* @param {Number} retryTest amount of allowed repeats is case of a failure
* @param {boolean} isStep
* @param {object} config
* @param {string} cid cid
* @return {Function} wrapped step definiton for sync WebdriverIO code
* @param {string} cid cid
* @param {Function} getCurrentStep step definitoon
* @return {Function} wrapped step definiton for sync WebdriverIO code
*/
wrapStep (code, retryTest = 0, isStep, config, cid) {
wrapStep (code, retryTest = 0, isStep, config, cid, getCurrentStep) {
return function (...args) {
/**
* wrap user step/hook with wdio before/after hooks
Expand All @@ -226,8 +236,8 @@ class CucumberAdapter {
return testFnWrapper.call(this,
isStep ? 'Step' : 'Hook',
{ specFn: code, specFnArgs: args },
{ beforeFn, beforeFnArgs: () => [uri, feature] },
{ afterFn, afterFnArgs: () => [uri, feature] },
{ beforeFn, beforeFnArgs: (context) => [uri, feature, getCurrentStep(), context] },
{ afterFn, afterFnArgs: (context) => [uri, feature, getCurrentStep(), context] },
cid,
retryTest)
}
Expand Down
22 changes: 16 additions & 6 deletions packages/wdio-cucumber-framework/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,22 @@ export function compareScenarioLineWithSourceLine(scenario, sourceLocation) {

export function getStepFromFeature(feature, pickle, stepIndex, sourceLocation) {
let combinedSteps = []
feature.children.forEach((child) => {
if (child.type.indexOf('Scenario') > -1 && !compareScenarioLineWithSourceLine(child, sourceLocation)) {
return
}
combinedSteps = combinedSteps.concat(child.steps)
})
const background = feature.children.find((child) => child.type === 'Background')
feature.children
.filter((child) => child.type !== 'Background' && !(child.type.indexOf('Scenario') > -1 && !compareScenarioLineWithSourceLine(child, sourceLocation)))
.forEach((child) => { combinedSteps = combinedSteps.concat(child.steps) })

/**
* all the hooks are executed before `Background` step(s).
* Example:
* from: [Background steps, wdioHookBeforeScenario, userHooks, steps, userHooks, wdioHookAfterScenario]
* to: [wdioHookBeforeScenario, userHooks, Background steps, steps, userHooks, wdioHookAfterScenario]
*/
const firstStepIdx = combinedSteps.indexOf(combinedSteps.find((step) => step.type === 'Step'))
if (background) {
combinedSteps = [...combinedSteps.slice(0, firstStepIdx), ...background.steps, ...combinedSteps.slice(firstStepIdx)]
}

const targetStep = combinedSteps[stepIndex]

if (targetStep.type === 'Step') {
Expand Down
18 changes: 9 additions & 9 deletions packages/wdio-cucumber-framework/tests/adapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,14 @@ describe('wrapSteps', () => {
const wrappedFunction = jest.fn()

functionWrapper(wrappedFunction)
expect(adapter.wrapStep).toBeCalledWith(expect.any(Function), 0, true, adapter.config, '0-2')
expect(adapter.wrapStep).toBeCalledWith(expect.any(Function), 0, true, adapter.config, '0-2', expect.any(Function))
})

test('should use passed arguments', () => {
const wrappedFunction = jest.fn()

functionWrapper(wrappedFunction, { retry: 123 })
expect(adapter.wrapStep).toBeCalledWith(expect.any(Function), 123, true, adapter.config, '0-2')
expect(adapter.wrapStep).toBeCalledWith(expect.any(Function), 123, true, adapter.config, '0-2', expect.any(Function))
})

test('should not wrap wdio hooks', () => {
Expand All @@ -164,7 +164,7 @@ describe('wrapSteps', () => {
const userHookFn = () => { }

functionWrapper(userHookFn)
expect(adapter.wrapStep).toBeCalledWith(expect.any(Function), 0, false, adapter.config, '0-2')
expect(adapter.wrapStep).toBeCalledWith(expect.any(Function), 0, false, adapter.config, '0-2', expect.any(Function))
})

afterEach(() => {
Expand All @@ -191,29 +191,29 @@ describe('wrapStep', () => {
test('should be proper type for Step', () => {
const adapter = adapterFactory()

const fn = adapter.wrapStep('specFn', 3, true, adapter.config, 'cid')
const fn = adapter.wrapStep('specFn', 3, true, adapter.config, 'cid', jest.fn().mockImplementation(() => 'getCurrentStep'))
fn(1, 2)

expect(testFnWrapper).toBeCalledWith(...fnWrapperArgs('Step', 3))

const beforeFnArgs = testFnWrapper.mock.calls[0][2].beforeFnArgs
expect(beforeFnArgs()).toEqual(['uri', 'feature'])
expect(beforeFnArgs('context')).toEqual(['uri', 'feature', 'getCurrentStep', 'context'])
const afterFnArgs = testFnWrapper.mock.calls[0][3].afterFnArgs
expect(afterFnArgs()).toEqual(['uri', 'feature'])
expect(afterFnArgs('context')).toEqual(['uri', 'feature', 'getCurrentStep', 'context'])
})

test('should be proper type for Hook', () => {
const adapter = adapterFactory()

const fn = adapter.wrapStep('specFn', undefined, false, adapter.config, 'cid')
const fn = adapter.wrapStep('specFn', undefined, false, adapter.config, 'cid', jest.fn().mockImplementation(() => 'getCurrentStep'))
fn(1, 2)

expect(testFnWrapper).toBeCalledWith(...fnWrapperArgs('Hook', 0))

const beforeFnArgs = testFnWrapper.mock.calls[0][2].beforeFnArgs
expect(beforeFnArgs()).toEqual(['uri', 'feature'])
expect(beforeFnArgs('context')).toEqual(['uri', 'feature', 'getCurrentStep', 'context'])
const afterFnArgs = testFnWrapper.mock.calls[0][3].afterFnArgs
expect(afterFnArgs()).toEqual(['uri', 'feature'])
expect(afterFnArgs('context')).toEqual(['uri', 'feature', 'getCurrentStep', 'context'])
})
})

Expand Down
10 changes: 10 additions & 0 deletions packages/wdio-cucumber-framework/tests/eventListener.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,13 @@ test('onTestCaseFinished', () => {
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(uri, feature, scenario, result, sourceLocation)
})

test('getCurrentStep', () => {
const eventBroadcaster = new EventEmitter()
const listener = new CucumberEventListener(eventBroadcaster)

expect(listener.getCurrentStep()).toBeNull()

listener.currentStep = 'foobar'
expect(listener.getCurrentStep()).toBe('foobar')
})
Loading