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

[#11473] - Refactor wdio-allure-reporter package for better hooks handling #12852

Merged
merged 7 commits into from
May 16, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/wdio-allure-reporter/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export const events = {
addAllureStep: 'allure:addAllureStep'
} as const

export const mochaEachHooks = ['"before each" hook', '"after each" hook'] as const
export const mochaAllHooks = ['"before all" hook', '"after all" hook'] as const
export const eachHooks = ['"before each" hook', '"after each" hook'] as const
export const allHooks = ['"before all" hook', '"after all" hook'] as const
export const linkPlaceholder = '{}'
290 changes: 187 additions & 103 deletions packages/wdio-allure-reporter/src/reporter.ts

Large diffs are not rendered by default.

27 changes: 20 additions & 7 deletions packages/wdio-allure-reporter/src/state.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { sep } from 'node:path'
import { AllureGroup, AllureTest, AllureStep } from 'allure-js-commons'
import { AllureGroup, AllureTest, AllureStep, ExecutableItemWrapper } from 'allure-js-commons'
import { findLast } from './utils.js'
import type { AllureStepableUnit } from './types.js'

export class AllureReporterState {
currentFile?: string
runningUnits: Array<AllureGroup | AllureTest | AllureStep> = []
runningUnits: Array<AllureGroup | AllureStepableUnit> = []
stats: {test: number, hooks: number, suites: number} = { test: 0, hooks: 0, suites: 0 }

get currentSuite(): AllureGroup | undefined {
return findLast(this.runningUnits, (unit) => unit instanceof AllureGroup) as AllureGroup | undefined
Expand All @@ -15,11 +17,15 @@ export class AllureReporterState {
}

get currentStep(): AllureStep | undefined {
return findLast(this.runningUnits, (unit) => unit instanceof AllureStep) as AllureStep | undefined
return findLast(this.runningUnits, (unit) => unit instanceof AllureStep,) as AllureStep | undefined
}

get currentAllureTestOrStep(): AllureTest | AllureStep | undefined {
return findLast(this.runningUnits, (unit) => unit instanceof AllureTest || unit instanceof AllureStep) as AllureTest | AllureStep | undefined
get currentHook(): ExecutableItemWrapper | undefined {
return findLast(this.runningUnits, (unit) => unit instanceof ExecutableItemWrapper) as ExecutableItemWrapper | undefined
}

get currentAllureStepableEntity(): AllureStepableUnit | undefined {
return findLast(this.runningUnits, (unit) => unit instanceof AllureTest || unit instanceof AllureStep || unit instanceof ExecutableItemWrapper) as AllureTest | AllureStep | ExecutableItemWrapper | undefined
}

get currentPackageLabel(): string | undefined {
Expand All @@ -30,11 +36,18 @@ export class AllureReporterState {
return this.currentFile.replaceAll(sep, '.')
}

push(unit: AllureGroup | AllureTest | AllureStep) {
push(unit: AllureGroup | AllureStepableUnit) {
if (unit instanceof AllureGroup) {
this.stats.suites++
} else if (unit instanceof AllureTest) {
this.stats.test++
} else {
this.stats.hooks++
}
this.runningUnits.push(unit)
}

pop(): AllureGroup | AllureTest | AllureStep | undefined {
pop(): AllureGroup | AllureStepableUnit | undefined {
return this.runningUnits.pop()
}
}
3 changes: 3 additions & 0 deletions packages/wdio-allure-reporter/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AllureStep, AllureTest, ExecutableItemWrapper } from 'allure-js-commons'
/**
* When you add a new option, please also update the docs at ./packages/wdio-allure-reporter/README.md
*/
Expand Down Expand Up @@ -143,6 +144,8 @@ export interface Attachment {
toXML(): string;
}

export type AllureStepableUnit = AllureTest | AllureStep | ExecutableItemWrapper

declare global {
namespace WebdriverIO {
interface ReporterOption extends AllureReporterOptions { }
Expand Down
91 changes: 78 additions & 13 deletions packages/wdio-allure-reporter/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import stripAnsi from 'strip-ansi'
import type { HookStats, TestStats, SuiteStats, CommandArgs, Tag } from '@wdio/reporter'
import type { Options } from '@wdio/types'
import type { Label, AllureTest, AllureGroup } from 'allure-js-commons'
import { Status as AllureStatus, md5, LabelName } from 'allure-js-commons'
import type { AllureGroup, AllureStep, AllureTest, ExecutableItemWrapper, FixtureResult, Label, TestResult } from 'allure-js-commons'
import { LabelName, md5, Stage, Status, Status as AllureStatus } from 'allure-js-commons'
import CompoundError from './compoundError.js'
import { mochaEachHooks, mochaAllHooks, linkPlaceholder } from './constants.js'
import { allHooks, eachHooks, linkPlaceholder } from './constants.js'

/**
* Get allure test status by TestStat object
Expand Down Expand Up @@ -38,6 +38,8 @@ export const getTestStatus = (
? AllureStatus.FAILED
: AllureStatus.BROKEN
}
} else if (test.errors) {
return AllureStatus.FAILED
}

return AllureStatus.BROKEN
Expand All @@ -52,31 +54,40 @@ export const isEmpty = (object: any) =>
!object || Object.keys(object).length === 0

/**
* Is mocha beforeEach hook
* Is mocha/jasmine beforeEach hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaBeforeEachHook = (title: string) =>
title.includes(mochaEachHooks[0])
export const isBeforeEachTypeHook = (title: string) =>
title.includes(eachHooks[0])

/**
* Is mocha beforeEach / afterEach hook
* Is mocha/jasmine beforeAll / beforeEach hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaEachHooks = (title: string) =>
mochaEachHooks.some((hook) => title.includes(hook))
export const isBeforeTypeHook = (title: string) =>
title.includes(allHooks[0]) || title.includes(eachHooks[0])

/**
* Is mocha beforeAll / afterAll hook
* Is mocha/jasmine beforeEach / afterEach hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isMochaAllHooks = (title: string) =>
mochaAllHooks.some((hook) => title.includes(hook))
export const isEachTypeHooks = (title: string) =>
eachHooks.some((hook) => title.includes(hook))

/**
* Is mocha/jasmine beforeAll / afterAll hook
* @param title {String} - hook title
* @returns {boolean}
* @private
*/
export const isAllTypeHooks = (title: string) =>
allHooks.some((hook) => title.includes(hook))

/**
* Properly format error from different test runners
Expand All @@ -87,7 +98,7 @@ export const isMochaAllHooks = (title: string) =>
export const getErrorFromFailedTest = (
test: TestStats | HookStats
): Error | CompoundError | undefined => {
if (test.errors && Array.isArray(test.errors)) {
if (test.errors && Array.isArray(test.errors) && test.errors.length) {
for (let i = 0; i < test.errors.length; i += 1) {
if (test.errors[i].message) {
test.errors[i].message = stripAnsi(test.errors[i].message)
Expand All @@ -113,6 +124,60 @@ export const getErrorFromFailedTest = (
return test.error
}

/**
* Update the hook information with the new one, it could be use when a hook ends
* @param {HookStats} newHookStats - New information that will be applied to current hook info
* @param {ExecutableItemWrapper} hookElement - hook element registered in the report
* @param {AllureStep} hookRootStep - root hook step
*
* @private
*/
export const getHookStatus = (newHookStats: HookStats, hookElement: ExecutableItemWrapper, hookRootStep: AllureStep) => {
// stage to finish for all hook.
hookElement.stage = hookRootStep.stage =
Stage.FINISHED
// set error detail information
const formattedError = getErrorFromFailedTest(newHookStats)
hookElement.detailsMessage = hookRootStep.detailsMessage = formattedError?.message
hookElement.detailsTrace = hookRootStep.detailsTrace = formattedError?.stack

let finalStatus = Status.PASSED
// set status to hook root step
const hookSteps = hookRootStep.wrappedItem.steps
if (Array.isArray(hookSteps) && hookSteps.length) {
const statusPriority = {
[Status.FAILED]: 0,
[Status.BROKEN]: 1,
[Status.SKIPPED]: 2,
[Status.PASSED]: 3,
}
let stepStatus = Status.PASSED
for (const step of hookSteps) {
if (step.status && statusPriority[step.status] < statusPriority[finalStatus]) {
stepStatus = step.status
}
}
finalStatus = stepStatus === Status.FAILED? Status.BROKEN : stepStatus
} else if (newHookStats.error || (Array.isArray(newHookStats.errors) && newHookStats.errors.length)){
finalStatus = Status.BROKEN
}

hookElement.status = hookRootStep.status = finalStatus
}

export const cleanCucumberHooks = (hook: FixtureResult | TestResult) => {
const currentStep = hook.steps[hook.steps.length - 1]
if (
currentStep &&
currentStep.steps.length === 0 &&
currentStep.attachments.length === 0 &&
hook.attachments.length === 0 &&
currentStep.status === Status.PASSED
) {
hook.steps.pop()
}
}

/**
* Substitute task id to link template
* @param {string} template - link template
Expand Down