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

Issues: 997 - Adding beforeStep/afterStep hooks #1198

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/index.ts
Expand Up @@ -22,12 +22,14 @@ export { default as UsageFormatter } from './formatter/usage_formatter'
export { default as UsageJsonFormatter } from './formatter/usage_json_formatter'
export { formatterHelpers }

// Support Code Fuctions
// Support Code Functions
const { methods } = supportCodeLibraryBuilder
export const After = methods.After
export const AfterAll = methods.AfterAll
export const AfterStep = methods.AfterStep
export const Before = methods.Before
export const BeforeAll = methods.BeforeAll
export const BeforeStep = methods.BeforeStep
export const defineParameterType = methods.defineParameterType
export const defineStep = methods.defineStep
export const Given = methods.Given
Expand Down
34 changes: 34 additions & 0 deletions src/models/test_step_hook_definition.ts
@@ -0,0 +1,34 @@
import { PickleTagFilter } from '../pickle_filter'
import Definition, {
IDefinition,
IGetInvocationDataResponse,
IGetInvocationDataRequest,
IDefinitionParameters,
IHookDefinitionOptions,
} from './definition'
import { messages } from 'cucumber-messages'

export default class TestStepHookDefinition extends Definition
implements IDefinition {
private readonly pickleTagFilter: PickleTagFilter

constructor(data: IDefinitionParameters<IHookDefinitionOptions>) {
super(data)
this.pickleTagFilter = new PickleTagFilter(data.options.tags)
}

appliesToTestCase(pickle: messages.IPickle): boolean {
return this.pickleTagFilter.matchesAllTagExpressions(pickle)
}

async getInvocationParameters({
hookParameter,
}: IGetInvocationDataRequest): Promise<IGetInvocationDataResponse> {
return Promise.resolve({
getInvalidCodeLengthMessage: () =>
this.buildInvalidCodeLengthMessage('0 or 1', '2'),
parameters: [hookParameter],
validCodeLengths: [0, 1, 2],
})
}
}
105 changes: 103 additions & 2 deletions src/runtime/pickle_runner.ts
Expand Up @@ -2,14 +2,15 @@ import _ from 'lodash'
import { getAmbiguousStepException } from './helpers'
import AttachmentManager from './attachment_manager'
import StepRunner from './step_runner'
import { messages, IdGenerator } from 'cucumber-messages'
import { IdGenerator, messages } from 'cucumber-messages'
import { addDurations, getZeroDuration } from '../time'
import { EventEmitter } from 'events'
import {
ISupportCodeLibrary,
ITestCaseHookParameter,
} from '../support_code_library_builder/types'
import TestCaseHookDefinition from '../models/test_case_hook_definition'
import TestStepHookDefinition from '../models/test_step_hook_definition'
import StepDefinition from '../models/step_definition'
import { IDefinition } from '../models/definition'
import { doesNotHaveValue } from '../value_checker'
Expand All @@ -21,6 +22,7 @@ interface ITestStep {
isBeforeHook?: boolean
isHook: boolean
hookDefinition?: TestCaseHookDefinition
stepHookDefinition?: TestStepHookDefinition
pickleStep?: messages.Pickle.IPickleStep
stepDefinitions?: StepDefinition[]
}
Expand Down Expand Up @@ -171,6 +173,18 @@ export default class PickleRunner {
)
}

getBeforeStepHookDefinitions(): TestStepHookDefinition[] {
return this.supportCodeLibrary.beforeTestStepHookDefinitions.filter(
hookDefinition => hookDefinition.appliesToTestCase(this.pickle)
)
}

getAfterStepHookDefinitions(): TestStepHookDefinition[] {
return this.supportCodeLibrary.afterTestStepHookDefinitions.filter(
hookDefinition => hookDefinition.appliesToTestCase(this.pickle)
)
}

getStepDefinitions(
pickleStep: messages.Pickle.IPickleStep
): StepDefinition[] {
Expand Down Expand Up @@ -323,6 +337,21 @@ export default class PickleRunner {
return this.invokeStep(null, hookDefinition, hookParameter)
}

async runStepHook(
stepHookDefinition: TestStepHookDefinition
): Promise<messages.ITestResult> {
if (this.isSkippingSteps()) {
return messages.TestResult.fromObject({ status: Status.SKIPPED })
}
const hookParameter: ITestCaseHookParameter = {
gherkinDocument: this.gherkinDocument,
pickle: this.pickle,
testCaseStartedId: this.currentTestCaseStartedId,
}

return this.invokeStep(null, stepHookDefinition, hookParameter)
}

async runStep(testStep: ITestStep): Promise<messages.ITestResult> {
if (testStep.stepDefinitions.length === 0) {
return messages.TestResult.fromObject({ status: Status.UNDEFINED })
Expand All @@ -334,6 +363,78 @@ export default class PickleRunner {
} else if (this.isSkippingSteps()) {
return messages.TestResult.fromObject({ status: Status.SKIPPED })
}
return this.invokeStep(testStep.pickleStep, testStep.stepDefinitions[0])
let stepResult
let afterStepHooksResult
const beforeStepHooksResult = await this.runStepHooks(
this.getBeforeStepHookDefinitions()
)

if (beforeStepHooksResult.status !== Status.FAILED) {
stepResult = await this.invokeStep(
testStep.pickleStep,
testStep.stepDefinitions[0]
)
if (stepResult.status === Status.PASSED) {
afterStepHooksResult = await this.runStepHooks(
this.getAfterStepHookDefinitions()
)
}
}
let cumulatedStepResult = beforeStepHooksResult

if (stepResult !== undefined) {
cumulatedStepResult = stepResult
if (beforeStepHooksResult.duration !== null) {
cumulatedStepResult.duration = addDurations(
cumulatedStepResult.duration,
beforeStepHooksResult.duration
)
}
if (afterStepHooksResult !== undefined) {
if (afterStepHooksResult.duration !== null) {
cumulatedStepResult.duration = addDurations(
cumulatedStepResult.duration,
afterStepHooksResult.duration
)
}
if (this.shouldUpdateStatus(afterStepHooksResult)) {
cumulatedStepResult.status = afterStepHooksResult.status
}
if (afterStepHooksResult.message !== null) {
cumulatedStepResult.message = afterStepHooksResult.message
}
}
}

return cumulatedStepResult
}

async runStepHooks(
stepHooks: TestStepHookDefinition[]
): Promise<messages.ITestResult> {
const stepHooksResult = messages.TestResult.fromObject({
status:
this.result.status === Status.FAILED
? Status.SKIPPED
: this.result.status,
})

for (const stepHookDefinition of stepHooks) {
const stepHookResult = await this.runStepHook(stepHookDefinition)
if (this.shouldUpdateStatus(stepHookResult)) {
stepHooksResult.status = stepHookResult.status
this.result.status = stepHookResult.status
}
if (stepHookResult.message !== null) {
stepHooksResult.message = stepHookResult.message
}
if (stepHookResult.duration !== null) {
stepHooksResult.duration =
stepHooksResult.duration !== null
? addDurations(stepHooksResult.duration, stepHookResult.duration)
: stepHookResult.duration
}
}
return stepHooksResult
}
}
76 changes: 76 additions & 0 deletions src/support_code_library_builder/index.ts
Expand Up @@ -2,6 +2,7 @@ import _ from 'lodash'
import { buildParameterType, getDefinitionLineAndUri } from './build_helpers'
import { IdGenerator } from 'cucumber-messages'
import TestCaseHookDefinition from '../models/test_case_hook_definition'
import TestStepHookDefinition from '../models/test_step_hook_definition'
import TestRunHookDefinition from '../models/test_run_hook_definition'
import StepDefinition from '../models/step_definition'
import { formatLocation } from '../formatter/helpers'
Expand All @@ -19,7 +20,9 @@ import {
DefineStepPattern,
IDefineStepOptions,
IDefineTestCaseHookOptions,
IDefineTestStepHookOptions,
TestCaseHookFunction,
TestStepHookFunction,
IDefineTestRunHookOptions,
ISupportCodeLibrary,
IParameterTypeDefinition,
Expand All @@ -41,6 +44,13 @@ interface ITestCaseHookDefinitionConfig {
uri: string
}

interface ITestStepHookDefinitionConfig {
code: any
line: number
options: any
uri: string
}

interface ITestRunHookDefinitionConfig {
code: any
line: number
Expand All @@ -53,8 +63,10 @@ export class SupportCodeLibraryBuilder {

private afterTestCaseHookDefinitionConfigs: ITestCaseHookDefinitionConfig[]
private afterTestRunHookDefinitionConfigs: ITestRunHookDefinitionConfig[]
private afterTestStepHookDefinitionConfigs: ITestStepHookDefinitionConfig[]
private beforeTestCaseHookDefinitionConfigs: ITestCaseHookDefinitionConfig[]
private beforeTestRunHookDefinitionConfigs: ITestRunHookDefinitionConfig[]
private beforeTestStepHookDefinitionConfigs: ITestStepHookDefinitionConfig[]
private cwd: string
private defaultTimeout: number
private definitionFunctionWrapper: any
Expand All @@ -72,12 +84,18 @@ export class SupportCodeLibraryBuilder {
AfterAll: this.defineTestRunHook(
() => this.afterTestRunHookDefinitionConfigs
),
AfterStep: this.defineTestStepHook(
() => this.afterTestStepHookDefinitionConfigs
),
Before: this.defineTestCaseHook(
() => this.beforeTestCaseHookDefinitionConfigs
),
BeforeAll: this.defineTestRunHook(
() => this.beforeTestRunHookDefinitionConfigs
),
BeforeStep: this.defineTestStepHook(
() => this.beforeTestStepHookDefinitionConfigs
),
defineParameterType: this.defineParameterType.bind(this),
defineStep,
Given: defineStep,
Expand Down Expand Up @@ -155,6 +173,37 @@ export class SupportCodeLibraryBuilder {
}
}

defineTestStepHook(
getCollection: () => ITestStepHookDefinitionConfig[]
): (
options: string | IDefineTestStepHookOptions | TestStepHookFunction,
code?: TestStepHookFunction
) => void {
return (
options: string | IDefineTestStepHookOptions | TestStepHookFunction,
code?: TestStepHookFunction
) => {
if (typeof options === 'string') {
options = { tags: options }
} else if (typeof options === 'function') {
code = options
options = {}
}
const { line, uri } = getDefinitionLineAndUri(this.cwd)
validateArguments({
args: { code, options },
fnName: 'defineTestStepHook',
location: formatLocation({ line, uri }),
})
getCollection().push({
code,
line,
options,
uri,
})
}
}

defineTestRunHook(
getCollection: () => ITestRunHookDefinitionConfig[]
): (options: IDefineTestRunHookOptions | Function, code?: Function) => void {
Expand Down Expand Up @@ -215,6 +264,25 @@ export class SupportCodeLibraryBuilder {
})
}

buildTestStepHookDefinitions(
configs: ITestStepHookDefinitionConfig[]
): TestStepHookDefinition[] {
return configs.map(({ code, line, options, uri }) => {
const wrappedCode = this.wrapCode({
code,
wrapperOptions: options.wrapperOptions,
})
return new TestStepHookDefinition({
code: wrappedCode,
id: this.newId(),
line,
options,
unwrappedCode: code,
uri,
})
})
}

buildTestRunHookDefinitions(
configs: ITestRunHookDefinitionConfig[]
): TestRunHookDefinition[] {
Expand Down Expand Up @@ -279,12 +347,18 @@ export class SupportCodeLibraryBuilder {
afterTestRunHookDefinitions: this.buildTestRunHookDefinitions(
this.afterTestRunHookDefinitionConfigs
).reverse(),
afterTestStepHookDefinitions: this.buildTestStepHookDefinitions(
this.afterTestStepHookDefinitionConfigs
).reverse(),
beforeTestCaseHookDefinitions: this.buildTestCaseHookDefinitions(
this.beforeTestCaseHookDefinitionConfigs
),
beforeTestRunHookDefinitions: this.buildTestRunHookDefinitions(
this.beforeTestRunHookDefinitionConfigs
),
beforeTestStepHookDefinitions: this.buildTestStepHookDefinitions(
this.beforeTestStepHookDefinitionConfigs
),
defaultTimeout: this.defaultTimeout,
parameterTypeRegistry: this.parameterTypeRegistry,
stepDefinitions: this.buildStepDefinitions(),
Expand All @@ -297,8 +371,10 @@ export class SupportCodeLibraryBuilder {
this.newId = newId
this.afterTestCaseHookDefinitionConfigs = []
this.afterTestRunHookDefinitionConfigs = []
this.afterTestStepHookDefinitionConfigs = []
this.beforeTestCaseHookDefinitionConfigs = []
this.beforeTestRunHookDefinitionConfigs = []
this.beforeTestStepHookDefinitionConfigs = []
this.definitionFunctionWrapper = null
this.defaultTimeout = 5000
this.parameterTypeRegistry = new ParameterTypeRegistry()
Expand Down