@wdio/cucumber-framework
Advanced tools
Comparing version 9.0.0-alpha.426 to 9.0.0
@@ -1,424 +0,543 @@ | ||
import { Formatter, Status } from '@cucumber/cucumber'; | ||
import path from 'node:path'; | ||
import logger from '@wdio/logger'; | ||
const log = logger('CucumberFormatter'); | ||
import { addKeywordToStep, getFeatureId, formatMessage, getRule, getStepType, buildStepPayload, getScenarioDescription } from './utils.js'; | ||
export default class CucumberFormatter extends Formatter { | ||
_gherkinDocEvents = []; | ||
_hookEvent = []; | ||
_scenarios = []; | ||
_testCases = []; | ||
_currentTestCase; | ||
_currentPickle = {}; | ||
_suiteMap = new Map(); | ||
_pickleMap = new Map(); | ||
_currentDoc = { comments: [] }; | ||
_startedFeatures = []; | ||
reporter; | ||
cid; | ||
specs; | ||
eventEmitter; | ||
scenarioLevelReporter; | ||
tagsInTitle; | ||
ignoreUndefinedDefinitions; | ||
failAmbiguousDefinitions; | ||
failedCount = 0; | ||
_featureStart; | ||
_scenarioStart; | ||
_testStart; | ||
constructor(options) { | ||
super(options); | ||
let results = []; | ||
options.eventBroadcaster.on('envelope', (envelope) => { | ||
if (envelope.gherkinDocument) { | ||
this.onGherkinDocument({ ...envelope.gherkinDocument, ...(envelope.gherkinDocument.uri ? { uri: this.normalizeURI(envelope.gherkinDocument.uri) } : {}) }); | ||
// src/cucumberFormatter.ts | ||
import { Formatter, Status } from "@cucumber/cucumber"; | ||
import path2 from "node:path"; | ||
import logger2 from "@wdio/logger"; | ||
// src/utils.ts | ||
import path from "node:path"; | ||
import logger from "@wdio/logger"; | ||
import { isFunctionAsync } from "@wdio/utils"; | ||
var log = logger("@wdio/cucumber-framework:utils"); | ||
function createStepArgument({ argument }) { | ||
if (!argument) { | ||
return void 0; | ||
} | ||
if (argument.dataTable) { | ||
return { | ||
rows: argument.dataTable.rows?.map((row) => ({ | ||
cells: row.cells?.map((cell) => cell.value) | ||
})) | ||
}; | ||
} | ||
if (argument.docString) { | ||
return argument.docString.content; | ||
} | ||
return void 0; | ||
} | ||
function formatMessage({ payload = {} }) { | ||
const content = { ...payload }; | ||
if (payload.error && (payload.error.message || payload.error.stack)) { | ||
const { name, message, stack } = payload.error; | ||
content.error = { name, message, stack }; | ||
} | ||
if (payload.title && payload.parent) { | ||
content.fullTitle = `${payload.parent}: ${payload.title}`; | ||
} | ||
return content; | ||
} | ||
function getStepType(step) { | ||
return step.hookId ? "hook" /* hook */ : "test" /* test */; | ||
} | ||
function getFeatureId(uri, feature) { | ||
return `${path.basename(uri)}:${feature.location?.line}:${feature.location?.column}`; | ||
} | ||
function getTestStepTitle(keyword = "", text = "", type) { | ||
const title = !text && type.toLowerCase() !== "hook" ? "Undefined Step" : text; | ||
return `${keyword.trim()} ${title.trim()}`.trim(); | ||
} | ||
function buildStepPayload(uri, feature, scenario, step, params) { | ||
return { | ||
...params, | ||
uid: step.id, | ||
// @ts-ignore | ||
title: getTestStepTitle(step.keyword, step.text, params.type), | ||
parent: scenario.id, | ||
argument: createStepArgument(step), | ||
file: uri, | ||
tags: scenario.tags, | ||
featureName: feature.name, | ||
scenarioName: scenario.name | ||
}; | ||
} | ||
function getRule(feature, scenarioId) { | ||
const rules = feature.children?.filter((child) => Object.keys(child)[0] === "rule"); | ||
const rule = rules.find((rule2) => { | ||
const scenarioRule = rule2.rule?.children?.find((child) => child.scenario?.id === scenarioId); | ||
if (scenarioRule) { | ||
return rule2; | ||
} | ||
}); | ||
return rule?.rule?.name; | ||
} | ||
function addKeywordToStep(steps, feature) { | ||
return steps.map((step) => { | ||
if (step.astNodeIds && step.astNodeIds.length > 0 && feature.children) { | ||
const astNodeId = step.astNodeIds[0]; | ||
const rules = feature.children.filter((child) => Object.keys(child)[0] === "rule"); | ||
let featureChildren = feature.children.filter((child) => Object.keys(child)[0] !== "rule"); | ||
const rulesChildrens = rules.map((child) => child.rule?.children).flat(); | ||
featureChildren = featureChildren.concat(rulesChildrens); | ||
featureChildren.find( | ||
(child) => ( | ||
// @ts-ignore | ||
child[Object.keys(child)[0]].steps.find((featureScenarioStep) => { | ||
if (featureScenarioStep.id === astNodeId.toString()) { | ||
step.keyword = featureScenarioStep.keyword; | ||
} | ||
else if (envelope.testRunStarted) { | ||
this.onTestRunStarted(); | ||
} | ||
else if (envelope.pickle) { | ||
this.onPickleAccepted({ ...envelope.pickle, uri: this.normalizeURI(envelope.pickle.uri) }); | ||
} | ||
else if (envelope.testCase) { | ||
this.onTestCasePrepared(envelope.testCase); | ||
} | ||
else if (envelope.testCaseStarted) { | ||
results = []; | ||
this.onTestCaseStarted(envelope.testCaseStarted); | ||
} | ||
else if (envelope.testStepStarted) { | ||
this.onTestStepStarted(envelope.testStepStarted); | ||
} | ||
else if (envelope.testStepFinished) { | ||
results.push(envelope.testStepFinished.testStepResult); | ||
this.onTestStepFinished(envelope.testStepFinished); | ||
} | ||
else if (envelope.testCaseFinished) { | ||
/** | ||
* only store result if step isn't retried | ||
*/ | ||
if (envelope.testCaseFinished.willBeRetried) { | ||
return log.debug(`test case with id ${envelope.testCaseFinished.testCaseStartedId} will be retried, ignoring result`); | ||
} | ||
this.onTestCaseFinished(results); | ||
} | ||
else if (envelope.testRunFinished) { | ||
this.onTestRunFinished(); | ||
} | ||
else if (envelope.hook) { | ||
this.onHook(envelope.hook); | ||
} | ||
else { | ||
// do nothing for other envelopes | ||
} | ||
}); | ||
this.reporter = options.parsedArgvOptions._reporter; | ||
this.cid = options.parsedArgvOptions._cid; | ||
this.specs = options.parsedArgvOptions._specs; | ||
this.eventEmitter = options.parsedArgvOptions._eventEmitter; | ||
this.scenarioLevelReporter = options.parsedArgvOptions._scenarioLevelReporter; | ||
this.tagsInTitle = options.parsedArgvOptions._tagsInTitle; | ||
this.ignoreUndefinedDefinitions = options.parsedArgvOptions._ignoreUndefinedDefinitions; | ||
this.failAmbiguousDefinitions = options.parsedArgvOptions._failAmbiguousDefinitions; | ||
return; | ||
}) | ||
) | ||
); | ||
return step; | ||
} | ||
updateCurrentPickle(params) { | ||
this._currentPickle = params; | ||
this.eventEmitter.emit('getHookParams', params); | ||
return step; | ||
}); | ||
} | ||
function getScenarioDescription(feature, scenarioId) { | ||
const children = feature.children?.find((child) => child?.scenario?.id === scenarioId); | ||
return children?.scenario?.description || ""; | ||
} | ||
// src/cucumberFormatter.ts | ||
var log2 = logger2("CucumberFormatter"); | ||
var CucumberFormatter = class extends Formatter { | ||
_gherkinDocEvents = []; | ||
_hookEvent = []; | ||
_scenarios = []; | ||
_testCases = []; | ||
_currentTestCase; | ||
_currentPickle = {}; | ||
_suiteMap = /* @__PURE__ */ new Map(); | ||
_pickleMap = /* @__PURE__ */ new Map(); | ||
_currentDoc = { comments: [] }; | ||
_startedFeatures = []; | ||
reporter; | ||
cid; | ||
specs; | ||
eventEmitter; | ||
scenarioLevelReporter; | ||
tagsInTitle; | ||
ignoreUndefinedDefinitions; | ||
failAmbiguousDefinitions; | ||
failedCount = 0; | ||
_featureStart; | ||
_scenarioStart; | ||
_testStart; | ||
constructor(options) { | ||
super(options); | ||
let results = []; | ||
options.eventBroadcaster.on("envelope", (envelope) => { | ||
if (envelope.gherkinDocument) { | ||
this.onGherkinDocument({ ...envelope.gherkinDocument, ...envelope.gherkinDocument.uri ? { uri: this.normalizeURI(envelope.gherkinDocument.uri) } : {} }); | ||
} else if (envelope.testRunStarted) { | ||
this.onTestRunStarted(); | ||
} else if (envelope.pickle) { | ||
this.onPickleAccepted({ ...envelope.pickle, uri: this.normalizeURI(envelope.pickle.uri) }); | ||
} else if (envelope.testCase) { | ||
this.onTestCasePrepared(envelope.testCase); | ||
} else if (envelope.testCaseStarted) { | ||
results = []; | ||
this.onTestCaseStarted(envelope.testCaseStarted); | ||
} else if (envelope.testStepStarted) { | ||
this.onTestStepStarted(envelope.testStepStarted); | ||
} else if (envelope.testStepFinished) { | ||
results.push(envelope.testStepFinished.testStepResult); | ||
this.onTestStepFinished(envelope.testStepFinished); | ||
} else if (envelope.testCaseFinished) { | ||
if (envelope.testCaseFinished.willBeRetried) { | ||
return log2.debug( | ||
`test case with id ${envelope.testCaseFinished.testCaseStartedId} will be retried, ignoring result` | ||
); | ||
} | ||
this.onTestCaseFinished(results); | ||
} else if (envelope.testRunFinished) { | ||
this.onTestRunFinished(); | ||
} else if (envelope.hook) { | ||
this.onHook(envelope.hook); | ||
} else { | ||
} | ||
}); | ||
this.reporter = options.parsedArgvOptions._reporter; | ||
this.cid = options.parsedArgvOptions._cid; | ||
this.specs = options.parsedArgvOptions._specs; | ||
this.eventEmitter = options.parsedArgvOptions._eventEmitter; | ||
this.scenarioLevelReporter = options.parsedArgvOptions._scenarioLevelReporter; | ||
this.tagsInTitle = options.parsedArgvOptions._tagsInTitle; | ||
this.ignoreUndefinedDefinitions = options.parsedArgvOptions._ignoreUndefinedDefinitions; | ||
this.failAmbiguousDefinitions = options.parsedArgvOptions._failAmbiguousDefinitions; | ||
} | ||
updateCurrentPickle(params) { | ||
this._currentPickle = params; | ||
this.eventEmitter.emit("getHookParams", params); | ||
} | ||
normalizeURI(uri) { | ||
return path2.isAbsolute(uri) ? uri : path2.resolve(uri); | ||
} | ||
emit(event, payload) { | ||
const message = formatMessage({ payload }); | ||
message.cid = this.cid; | ||
message.specs = this.specs; | ||
message.uid = payload.uid; | ||
this.reporter.emit(event, message); | ||
} | ||
usesSpecGrouping() { | ||
return this._gherkinDocEvents.length > 1; | ||
} | ||
featureIsStarted(feature) { | ||
return this._startedFeatures.includes(feature); | ||
} | ||
getTitle(featureOrScenario) { | ||
const name = featureOrScenario.name; | ||
const tags = featureOrScenario.tags; | ||
if (!this.tagsInTitle || !tags || !tags.length) { | ||
return name; | ||
} | ||
normalizeURI(uri) { | ||
return path.isAbsolute(uri) ? uri : path.resolve(uri); | ||
return `${tags.map((tag) => tag.name).join(", ")}: ${name}`; | ||
} | ||
afterHook(uri, feature, scenario, step, result) { | ||
let error; | ||
if (result.message) { | ||
error = new Error(result.message.split("\n")[0]); | ||
error.stack = result.message; | ||
} | ||
emit(event, payload) { | ||
const message = formatMessage({ payload }); | ||
message.cid = this.cid; | ||
message.specs = this.specs; | ||
message.uid = payload.uid; | ||
this.reporter.emit(event, message); | ||
if (result.status === Status.FAILED) { | ||
this.failedCount++; | ||
} | ||
usesSpecGrouping() { | ||
return this._gherkinDocEvents.length > 1; | ||
const payload = buildStepPayload(uri, feature, scenario, step, { | ||
type: "hook", | ||
state: result.status, | ||
error, | ||
duration: Date.now() - this._testStart?.getTime() | ||
}); | ||
this.emit("hook:end", payload); | ||
} | ||
afterTest(uri, feature, scenario, step, result) { | ||
let state = "undefined"; | ||
switch (result.status) { | ||
case Status.FAILED: | ||
case Status.UNDEFINED: | ||
state = "fail"; | ||
break; | ||
case Status.PASSED: | ||
state = "pass"; | ||
break; | ||
case Status.PENDING: | ||
state = "pending"; | ||
break; | ||
case Status.SKIPPED: | ||
state = "skip"; | ||
break; | ||
case Status.AMBIGUOUS: | ||
state = "pending"; | ||
break; | ||
} | ||
featureIsStarted(feature) { | ||
return this._startedFeatures.includes(feature); | ||
} | ||
getTitle(featureOrScenario) { | ||
const name = featureOrScenario.name; | ||
const tags = featureOrScenario.tags; | ||
if (!this.tagsInTitle || !tags || !tags.length) { | ||
return name; | ||
let error = result.message ? new Error(result.message) : void 0; | ||
let title = step ? step?.text : this.getTitle(scenario); | ||
if (result.status === Status.UNDEFINED) { | ||
if (this.ignoreUndefinedDefinitions) { | ||
state = "pending"; | ||
title += " (undefined step)"; | ||
} else { | ||
this.failedCount++; | ||
const err = new Error( | ||
(step ? `Step "${title}" is not defined. ` : `Scenario ${title} has undefined steps. `) + "You can ignore this error by setting cucumberOpts.ignoreUndefinedDefinitions as true." | ||
); | ||
err.stack = `${err.message} | ||
at Feature(${uri}):1:1 | ||
`; | ||
const featChildren = feature.children?.find((c) => scenario.astNodeIds && c.scenario?.id === scenario.astNodeIds[0]); | ||
if (featChildren) { | ||
err.stack += ` at Scenario(${featChildren.scenario?.name}):${featChildren.scenario?.location?.line}:${featChildren.scenario?.location?.column} | ||
`; | ||
const featStep = featChildren.scenario?.steps?.find((s) => step.astNodeIds && s.id === step.astNodeIds[0]); | ||
if (featStep) { | ||
err.stack += ` at Step(${featStep.text}):${featStep.location?.line}:${featStep.location?.column} | ||
`; | ||
} | ||
} | ||
return `${tags.map((tag) => tag.name).join(', ')}: ${name}`; | ||
error = err; | ||
} | ||
} else if (result.status === Status.FAILED && !result.willBeRetried) { | ||
error = new Error(result.message?.split("\n")[0]); | ||
error.stack = result.message; | ||
this.failedCount++; | ||
} else if (result.status === Status.AMBIGUOUS && this.failAmbiguousDefinitions) { | ||
state = "fail"; | ||
this.failedCount++; | ||
error = new Error(result.message?.split("\n")[0]); | ||
error.stack = result.message; | ||
} else if (result.willBeRetried) { | ||
state = "retry"; | ||
} | ||
afterHook(uri, feature, scenario, step, result) { | ||
let error; | ||
if (result.message) { | ||
error = new Error(result.message.split('\n')[0]); | ||
error.stack = result.message; | ||
} | ||
if (result.status === Status.FAILED) { | ||
this.failedCount++; | ||
} | ||
const payload = buildStepPayload(uri, feature, scenario, step, { | ||
type: 'hook', | ||
state: result.status, | ||
error, | ||
duration: Date.now() - this._testStart?.getTime(), | ||
}); | ||
this.emit('hook:end', payload); | ||
const common = { | ||
title, | ||
state, | ||
error, | ||
duration: Date.now() - this._testStart?.getTime(), | ||
passed: ["pass", "skip"].includes(state), | ||
file: uri | ||
}; | ||
const payload = step ? buildStepPayload(uri, feature, scenario, step, { | ||
type: "step", | ||
...common | ||
}) : { | ||
type: "scenario", | ||
uid: scenario.id, | ||
parent: getFeatureId(uri, feature), | ||
tags: scenario.tags, | ||
...common | ||
}; | ||
this.emit("test:" + state, payload); | ||
} | ||
onGherkinDocument(gherkinDocEvent) { | ||
this.updateCurrentPickle({ | ||
uri: gherkinDocEvent.uri, | ||
feature: gherkinDocEvent.feature | ||
}); | ||
this._gherkinDocEvents.push(gherkinDocEvent); | ||
} | ||
onHook(hookEvent) { | ||
this._hookEvent.push(hookEvent); | ||
} | ||
onTestRunStarted() { | ||
if (this.usesSpecGrouping()) { | ||
return; | ||
} | ||
afterTest(uri, feature, scenario, step, result) { | ||
let state = 'undefined'; | ||
switch (result.status) { | ||
case Status.FAILED: | ||
case Status.UNDEFINED: | ||
state = 'fail'; | ||
break; | ||
case Status.PASSED: | ||
state = 'pass'; | ||
break; | ||
case Status.PENDING: | ||
state = 'pending'; | ||
break; | ||
case Status.SKIPPED: | ||
state = 'skip'; | ||
break; | ||
case Status.AMBIGUOUS: | ||
state = 'pending'; | ||
break; | ||
} | ||
let error = result.message ? new Error(result.message) : undefined; | ||
let title = step ? step?.text : this.getTitle(scenario); | ||
if (result.status === Status.UNDEFINED) { | ||
if (this.ignoreUndefinedDefinitions) { | ||
/** | ||
* mark test as pending | ||
*/ | ||
state = 'pending'; | ||
title += ' (undefined step)'; | ||
} | ||
else { | ||
/** | ||
* mark test as failed | ||
*/ | ||
this.failedCount++; | ||
const err = new Error((step ? `Step "${title}" is not defined. ` : `Scenario ${title} has undefined steps. `) + | ||
'You can ignore this error by setting cucumberOpts.ignoreUndefinedDefinitions as true.'); | ||
err.stack = `${err.message}\n\tat Feature(${uri}):1:1\n`; | ||
const featChildren = feature.children?.find(c => scenario.astNodeIds && c.scenario?.id === scenario.astNodeIds[0]); | ||
if (featChildren) { | ||
err.stack += `\tat Scenario(${featChildren.scenario?.name}):${featChildren.scenario?.location?.line}:${featChildren.scenario?.location?.column}\n`; | ||
const featStep = featChildren.scenario?.steps?.find(s => step.astNodeIds && s.id === step.astNodeIds[0]); | ||
if (featStep) { | ||
err.stack += `\tat Step(${featStep.text}):${featStep.location?.line}:${featStep.location?.column}\n`; | ||
} | ||
} | ||
error = err; | ||
} | ||
} | ||
else if (result.status === Status.FAILED && !result.willBeRetried) { | ||
error = new Error(result.message?.split('\n')[0]); | ||
error.stack = result.message; | ||
this.failedCount++; | ||
} | ||
else if (result.status === Status.AMBIGUOUS && this.failAmbiguousDefinitions) { | ||
state = 'fail'; | ||
this.failedCount++; | ||
error = new Error(result.message?.split('\n')[0]); | ||
error.stack = result.message; | ||
} | ||
else if (result.willBeRetried) { | ||
state = 'retry'; | ||
} | ||
const common = { | ||
title: title, | ||
state, | ||
error, | ||
duration: Date.now() - this._testStart?.getTime(), | ||
passed: ['pass', 'skip'].includes(state), | ||
file: uri, | ||
}; | ||
const payload = step | ||
? buildStepPayload(uri, feature, scenario, step, { | ||
type: 'step', | ||
...common, | ||
}) | ||
: { | ||
type: 'scenario', | ||
uid: scenario.id, | ||
parent: getFeatureId(uri, feature), | ||
tags: scenario.tags, | ||
...common, | ||
}; | ||
this.emit('test:' + state, payload); | ||
const doc = this._gherkinDocEvents[this._gherkinDocEvents.length - 1]; | ||
this._featureStart = /* @__PURE__ */ new Date(); | ||
const payload = { | ||
uid: getFeatureId(doc.uri, doc.feature), | ||
title: this.getTitle(doc.feature), | ||
type: "feature", | ||
file: doc.uri, | ||
tags: doc.feature?.tags, | ||
description: doc.feature?.description, | ||
keyword: doc.feature?.keyword | ||
}; | ||
this.emit("suite:start", payload); | ||
} | ||
onPickleAccepted(pickleEvent) { | ||
const id = this._suiteMap.size.toString(); | ||
this._suiteMap.set(pickleEvent.id, id); | ||
this._pickleMap.set(id, pickleEvent.astNodeIds[0]); | ||
const scenario = { ...pickleEvent, id }; | ||
this._scenarios.push(scenario); | ||
} | ||
onTestCasePrepared(testCase) { | ||
this._testCases.push(testCase); | ||
} | ||
onTestCaseStarted(testcase) { | ||
this._currentTestCase = testcase; | ||
const tc = this._testCases.find((tc2) => tc2.id === testcase.testCaseId); | ||
const scenario = this._scenarios.find( | ||
(sc) => sc.id === this._suiteMap.get(tc?.pickleId) | ||
); | ||
if (!scenario) { | ||
return; | ||
} | ||
onGherkinDocument(gherkinDocEvent) { | ||
this.updateCurrentPickle({ | ||
uri: gherkinDocEvent.uri, | ||
feature: gherkinDocEvent.feature, | ||
}); | ||
this._gherkinDocEvents.push(gherkinDocEvent); | ||
const doc = this._gherkinDocEvents.find( | ||
(gde) => gde.uri === scenario?.uri | ||
); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
if (this._currentDoc.uri && this._currentDoc.feature && this.usesSpecGrouping() && doc !== this._currentDoc && this.featureIsStarted(this._currentDoc.uri)) { | ||
const payload2 = { | ||
uid: getFeatureId( | ||
this._currentDoc.uri, | ||
this._currentDoc.feature | ||
), | ||
title: this.getTitle(this._currentDoc.feature), | ||
type: "feature", | ||
file: this._currentDoc.uri, | ||
duration: Date.now() - this._featureStart?.getTime(), | ||
tags: this._currentDoc.feature?.tags | ||
}; | ||
this.emit("suite:end", payload2); | ||
} | ||
onHook(hookEvent) { | ||
this._hookEvent.push(hookEvent); | ||
if (this.usesSpecGrouping() && doc && doc.uri && !this.featureIsStarted(doc.uri)) { | ||
const payload2 = { | ||
uid: getFeatureId(doc.uri, doc.feature), | ||
title: this.getTitle(doc.feature), | ||
type: "feature", | ||
file: doc.uri, | ||
tags: doc.feature?.tags, | ||
description: doc.feature?.description, | ||
keyword: doc.feature?.keyword | ||
}; | ||
this.emit("suite:start", payload2); | ||
this._currentDoc = doc; | ||
this._startedFeatures.push(doc.uri); | ||
} | ||
onTestRunStarted() { | ||
if (this.usesSpecGrouping()) { | ||
return; | ||
} | ||
const doc = this._gherkinDocEvents[this._gherkinDocEvents.length - 1]; | ||
this._featureStart = new Date(); | ||
const payload = { | ||
uid: getFeatureId(doc.uri, doc.feature), | ||
title: this.getTitle(doc.feature), | ||
type: 'feature', | ||
file: doc.uri, | ||
tags: doc.feature?.tags, | ||
description: doc.feature?.description, | ||
keyword: doc.feature?.keyword, | ||
}; | ||
this.emit('suite:start', payload); | ||
if (scenario.steps && feature) { | ||
scenario.steps = addKeywordToStep( | ||
scenario.steps, | ||
feature | ||
); | ||
} | ||
onPickleAccepted(pickleEvent) { | ||
const id = this._suiteMap.size.toString(); | ||
this._suiteMap.set(pickleEvent.id, id); | ||
this._pickleMap.set(id, pickleEvent.astNodeIds[0]); | ||
const scenario = { ...pickleEvent, id }; | ||
this._scenarios.push(scenario); | ||
this.updateCurrentPickle({ uri, feature, scenario }); | ||
const reporterScenario = scenario; | ||
reporterScenario.rule = getRule( | ||
doc?.feature, | ||
this._pickleMap.get(scenario.id) | ||
); | ||
this._scenarioStart = /* @__PURE__ */ new Date(); | ||
this._testStart = /* @__PURE__ */ new Date(); | ||
const payload = { | ||
uid: reporterScenario.id, | ||
title: this.getTitle(reporterScenario), | ||
parent: getFeatureId(scenario.uri, doc?.feature), | ||
type: "scenario", | ||
description: getScenarioDescription(doc?.feature, this._pickleMap.get(scenario.id)), | ||
file: scenario.uri, | ||
tags: reporterScenario.tags, | ||
rule: reporterScenario.rule | ||
}; | ||
this.emit(this.scenarioLevelReporter ? "test:start" : "suite:start", payload); | ||
} | ||
onTestStepStarted(testStepStartedEvent) { | ||
if (!this.scenarioLevelReporter) { | ||
const testcase = this._testCases.find( | ||
(testcase2) => this._currentTestCase && testcase2.id === this._currentTestCase.testCaseId | ||
); | ||
const scenario = this._scenarios.find( | ||
(sc) => sc.id === this._suiteMap.get(testcase?.pickleId) | ||
); | ||
const teststep = testcase?.testSteps?.find( | ||
(step2) => step2.id === testStepStartedEvent.testStepId | ||
); | ||
const hook = this._hookEvent.find( | ||
(h) => h.id === teststep?.hookId | ||
); | ||
const step = scenario?.steps?.find((s) => s.id === teststep?.pickleStepId) || { ...teststep, text: `${hook?.name || ""} ${hook?.tagExpression || ""}`.trim() }; | ||
const doc = this._gherkinDocEvents.find( | ||
(gde) => gde.uri === scenario?.uri | ||
); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
if (!step) { | ||
return; | ||
} | ||
this.updateCurrentPickle({ uri, feature, scenario, step }); | ||
this._testStart = /* @__PURE__ */ new Date(); | ||
const type = getStepType(step); | ||
const payload = buildStepPayload( | ||
uri, | ||
feature, | ||
scenario, | ||
step, | ||
{ type } | ||
); | ||
this.emit(`${type}:start`, payload); | ||
} | ||
onTestCasePrepared(testCase) { | ||
this._testCases.push(testCase); | ||
} | ||
onTestStepFinished(testStepFinishedEvent) { | ||
if (!this.scenarioLevelReporter) { | ||
const testcase = this._testCases.find( | ||
(testcase2) => testcase2.id === this._currentTestCase?.testCaseId | ||
); | ||
const scenario = this._scenarios.find( | ||
(sc) => sc.id === this._suiteMap.get(testcase?.pickleId) | ||
); | ||
const teststep = testcase?.testSteps?.find( | ||
(step2) => step2.id === testStepFinishedEvent.testStepId | ||
); | ||
const step = scenario?.steps?.find((s) => s.id === teststep?.pickleStepId) || teststep; | ||
const result = testStepFinishedEvent.testStepResult; | ||
const doc = this._gherkinDocEvents.find( | ||
(gde) => gde.uri === scenario?.uri | ||
); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
if (!step) { | ||
return; | ||
} | ||
delete this._currentPickle; | ||
const type = getStepType(step); | ||
if (type === "hook") { | ||
return this.afterHook( | ||
uri, | ||
feature, | ||
scenario, | ||
step, | ||
result | ||
); | ||
} | ||
return this.afterTest( | ||
uri, | ||
feature, | ||
scenario, | ||
step, | ||
result | ||
); | ||
} | ||
onTestCaseStarted(testcase) { | ||
this._currentTestCase = testcase; | ||
const tc = this._testCases.find((tc) => tc.id === testcase.testCaseId); | ||
const scenario = this._scenarios.find((sc) => sc.id === this._suiteMap.get(tc?.pickleId)); | ||
/* istanbul ignore if */ | ||
if (!scenario) { | ||
return; | ||
} | ||
const doc = this._gherkinDocEvents.find((gde) => gde.uri === scenario?.uri); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
if (this._currentDoc.uri && | ||
this._currentDoc.feature && | ||
this.usesSpecGrouping() && | ||
doc !== this._currentDoc && | ||
this.featureIsStarted(this._currentDoc.uri)) { | ||
const payload = { | ||
uid: getFeatureId(this._currentDoc.uri, this._currentDoc.feature), | ||
title: this.getTitle(this._currentDoc.feature), | ||
type: 'feature', | ||
file: this._currentDoc.uri, | ||
duration: Date.now() - this._featureStart?.getTime(), | ||
tags: this._currentDoc.feature?.tags, | ||
}; | ||
this.emit('suite:end', payload); | ||
} | ||
if (this.usesSpecGrouping() && | ||
doc && | ||
doc.uri && | ||
!this.featureIsStarted(doc.uri)) { | ||
const payload = { | ||
uid: getFeatureId(doc.uri, doc.feature), | ||
title: this.getTitle(doc.feature), | ||
type: 'feature', | ||
file: doc.uri, | ||
tags: doc.feature?.tags, | ||
description: doc.feature?.description, | ||
keyword: doc.feature?.keyword, | ||
}; | ||
this.emit('suite:start', payload); | ||
this._currentDoc = doc; | ||
this._startedFeatures.push(doc.uri); | ||
} | ||
/** | ||
* The reporters need to have the keywords, like `Given|When|Then`. They are NOT available | ||
* on the scenario, they ARE on the feature. | ||
* This will aad them | ||
*/ | ||
if (scenario.steps && feature) { | ||
scenario.steps = addKeywordToStep(scenario.steps, feature); | ||
} | ||
this.updateCurrentPickle({ uri, feature, scenario }); | ||
const reporterScenario = scenario; | ||
reporterScenario.rule = getRule(doc?.feature, this._pickleMap.get(scenario.id)); | ||
this._scenarioStart = new Date(); | ||
this._testStart = new Date(); | ||
const payload = { | ||
uid: reporterScenario.id, | ||
title: this.getTitle(reporterScenario), | ||
parent: getFeatureId(scenario.uri, doc?.feature), | ||
type: 'scenario', | ||
description: getScenarioDescription(doc?.feature, this._pickleMap.get(scenario.id)), | ||
file: scenario.uri, | ||
tags: reporterScenario.tags, | ||
rule: reporterScenario.rule, | ||
}; | ||
this.emit(this.scenarioLevelReporter ? 'test:start' : 'suite:start', payload); | ||
} | ||
onTestCaseFinished(results) { | ||
const tc = this._testCases.find( | ||
(tc2) => tc2.id === this._currentTestCase?.testCaseId | ||
); | ||
const scenario = this._scenarios.find( | ||
(sc) => sc.id === this._suiteMap.get(tc?.pickleId) | ||
); | ||
if (!scenario) { | ||
return; | ||
} | ||
onTestStepStarted(testStepStartedEvent) { | ||
if (!this.scenarioLevelReporter) { | ||
const testcase = this._testCases.find((testcase) => this._currentTestCase && | ||
testcase.id === this._currentTestCase.testCaseId); | ||
const scenario = this._scenarios.find((sc) => sc.id === this._suiteMap.get(testcase?.pickleId)); | ||
const teststep = testcase?.testSteps?.find((step) => step.id === testStepStartedEvent.testStepId); | ||
const hook = this._hookEvent.find((h) => h.id === teststep?.hookId); | ||
const step = scenario?.steps?.find((s) => s.id === teststep?.pickleStepId) || | ||
{ ...teststep, text: `${hook?.name || ''} ${hook?.tagExpression || ''}`.trim() }; | ||
const doc = this._gherkinDocEvents.find((gde) => gde.uri === scenario?.uri); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
/* istanbul ignore if */ | ||
if (!step) { | ||
return; | ||
} | ||
this.updateCurrentPickle({ uri, feature, scenario, step }); | ||
this._testStart = new Date(); | ||
const type = getStepType(step); | ||
const payload = buildStepPayload(uri, feature, scenario, step, { type }); | ||
this.emit(`${type}:start`, payload); | ||
} | ||
const finalResult = results.find((r) => r.status !== Status.PASSED) || results.pop(); | ||
const doc = this._gherkinDocEvents.find( | ||
(gde) => gde.uri === scenario?.uri | ||
); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
this.updateCurrentPickle({ uri, feature, scenario }); | ||
const payload = { | ||
uid: scenario.id, | ||
title: this.getTitle(scenario), | ||
parent: getFeatureId(doc?.uri, doc?.feature), | ||
type: "scenario", | ||
file: doc?.uri, | ||
duration: Date.now() - this._scenarioStart?.getTime(), | ||
tags: scenario.tags | ||
}; | ||
if (this.scenarioLevelReporter) { | ||
return this.afterTest(uri, feature, scenario, { id: scenario.id }, finalResult); | ||
} | ||
onTestStepFinished(testStepFinishedEvent) { | ||
if (!this.scenarioLevelReporter) { | ||
const testcase = this._testCases.find((testcase) => testcase.id === this._currentTestCase?.testCaseId); | ||
const scenario = this._scenarios.find((sc) => sc.id === this._suiteMap.get(testcase?.pickleId)); | ||
const teststep = testcase?.testSteps?.find((step) => step.id === testStepFinishedEvent.testStepId); | ||
const step = scenario?.steps?.find((s) => s.id === teststep?.pickleStepId) || | ||
teststep; | ||
const result = testStepFinishedEvent.testStepResult; | ||
const doc = this._gherkinDocEvents.find((gde) => gde.uri === scenario?.uri); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
/* istanbul ignore if */ | ||
if (!step) { | ||
return; | ||
} | ||
delete this._currentPickle; | ||
const type = getStepType(step); | ||
if (type === 'hook') { | ||
return this.afterHook(uri, feature, scenario, step, result); | ||
} | ||
return this.afterTest(uri, feature, scenario, step, result); | ||
} | ||
this.emit("suite:end", payload); | ||
} | ||
onTestRunFinished() { | ||
delete this._currentTestCase; | ||
this.eventEmitter.emit("getFailedCount", this.failedCount); | ||
if (this.usesSpecGrouping()) { | ||
const payload2 = { | ||
uid: getFeatureId( | ||
this._currentDoc.uri, | ||
this._currentDoc.feature | ||
), | ||
title: this.getTitle(this._currentDoc.feature), | ||
type: "feature", | ||
file: this._currentDoc.uri, | ||
duration: Date.now() - this._featureStart?.getTime(), | ||
tags: this._currentDoc.feature?.tags | ||
}; | ||
this.emit("suite:end", payload2); | ||
return; | ||
} | ||
onTestCaseFinished(results) { | ||
const tc = this._testCases.find((tc) => tc.id === this._currentTestCase?.testCaseId); | ||
const scenario = this._scenarios.find((sc) => sc.id === this._suiteMap.get(tc?.pickleId)); | ||
/* istanbul ignore if */ | ||
if (!scenario) { | ||
return; | ||
} | ||
/** | ||
* propagate the first non passing result or the last one | ||
*/ | ||
const finalResult = results.find((r) => r.status !== Status.PASSED) || results.pop(); | ||
const doc = this._gherkinDocEvents.find((gde) => gde.uri === scenario?.uri); | ||
const uri = doc?.uri; | ||
const feature = doc?.feature; | ||
this.updateCurrentPickle({ uri, feature, scenario }); | ||
const payload = { | ||
uid: scenario.id, | ||
title: this.getTitle(scenario), | ||
parent: getFeatureId(doc?.uri, doc?.feature), | ||
type: 'scenario', | ||
file: doc?.uri, | ||
duration: Date.now() - this._scenarioStart?.getTime(), | ||
tags: scenario.tags, | ||
}; | ||
if (this.scenarioLevelReporter) { | ||
return this.afterTest(uri, feature, scenario, { id: scenario.id }, finalResult); | ||
} | ||
this.emit('suite:end', payload); | ||
const gherkinDocEvent = this._gherkinDocEvents.pop(); | ||
if (!gherkinDocEvent) { | ||
return; | ||
} | ||
onTestRunFinished() { | ||
delete this._currentTestCase; | ||
this.eventEmitter.emit('getFailedCount', this.failedCount); | ||
if (this.usesSpecGrouping()) { | ||
const payload = { | ||
uid: getFeatureId(this._currentDoc.uri, this._currentDoc.feature), | ||
title: this.getTitle(this._currentDoc.feature), | ||
type: 'feature', | ||
file: this._currentDoc.uri, | ||
duration: Date.now() - this._featureStart?.getTime(), | ||
tags: this._currentDoc.feature?.tags, | ||
}; | ||
this.emit('suite:end', payload); | ||
return; | ||
} | ||
const gherkinDocEvent = this._gherkinDocEvents.pop(); // see .push() in `handleBeforeFeature()` | ||
/* istanbul ignore if */ | ||
if (!gherkinDocEvent) { | ||
return; | ||
} | ||
const payload = { | ||
uid: getFeatureId(gherkinDocEvent.uri, gherkinDocEvent.feature), | ||
title: this.getTitle(gherkinDocEvent.feature), | ||
type: 'feature', | ||
file: gherkinDocEvent.uri, | ||
duration: Date.now() - this._featureStart?.getTime(), | ||
tags: gherkinDocEvent.feature?.tags, | ||
}; | ||
this.emit('suite:end', payload); | ||
} | ||
} | ||
const payload = { | ||
uid: getFeatureId( | ||
gherkinDocEvent.uri, | ||
gherkinDocEvent.feature | ||
), | ||
title: this.getTitle(gherkinDocEvent.feature), | ||
type: "feature", | ||
file: gherkinDocEvent.uri, | ||
duration: Date.now() - this._featureStart?.getTime(), | ||
tags: gherkinDocEvent.feature?.tags | ||
}; | ||
this.emit("suite:end", payload); | ||
} | ||
}; | ||
export { | ||
CucumberFormatter as default | ||
}; |
@@ -1,422 +0,539 @@ | ||
import url from 'node:url'; | ||
import path from 'node:path'; | ||
import fs from 'node:fs'; | ||
import { readdir, readFile } from 'node:fs/promises'; | ||
import { createRequire } from 'node:module'; | ||
import { EventEmitter } from 'node:events'; | ||
import { Writable } from 'node:stream'; | ||
import isGlob from 'is-glob'; | ||
import { sync as globSync } from 'glob'; | ||
import logger from '@wdio/logger'; | ||
import { executeHooksWithArgs, testFnWrapper } from '@wdio/utils'; | ||
import { After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, Given, When, Then, setDefaultTimeout, setDefinitionFunctionWrapper, supportCodeLibraryBuilder, setWorldConstructor, defineParameterType, defineStep, DataTable, World, Status, } from '@cucumber/cucumber'; | ||
import Gherkin from '@cucumber/gherkin'; | ||
import { IdGenerator } from '@cucumber/messages'; | ||
import { loadConfiguration, loadSources, runCucumber } from '@cucumber/cucumber/api'; | ||
import { DEFAULT_OPTS } from './constants.js'; | ||
import { generateSkipTagsFromCapabilities, setUserHookNames } from './utils.js'; | ||
export const FILE_PROTOCOL = 'file://'; | ||
const uuidFn = IdGenerator.uuid(); | ||
const log = logger('@wdio/cucumber-framework'); | ||
const require = createRequire(import.meta.url); | ||
// src/index.ts | ||
import url from "node:url"; | ||
import path2 from "node:path"; | ||
import fs from "node:fs"; | ||
import { readdir, readFile } from "node:fs/promises"; | ||
import { createRequire } from "node:module"; | ||
import { EventEmitter } from "node:events"; | ||
import { Writable } from "node:stream"; | ||
import isGlob from "is-glob"; | ||
import { sync as globSync } from "glob"; | ||
import logger2 from "@wdio/logger"; | ||
import { executeHooksWithArgs, testFnWrapper } from "@wdio/utils"; | ||
import { | ||
After, | ||
AfterAll, | ||
AfterStep, | ||
Before, | ||
BeforeAll, | ||
BeforeStep, | ||
Given, | ||
When, | ||
Then, | ||
setDefaultTimeout, | ||
setDefinitionFunctionWrapper, | ||
supportCodeLibraryBuilder, | ||
setWorldConstructor, | ||
defineParameterType, | ||
defineStep, | ||
DataTable, | ||
World, | ||
Status | ||
} from "@cucumber/cucumber"; | ||
import Gherkin from "@cucumber/gherkin"; | ||
import { IdGenerator } from "@cucumber/messages"; | ||
import { loadConfiguration, loadSources, runCucumber } from "@cucumber/cucumber/api"; | ||
// src/constants.ts | ||
var DEFAULT_TIMEOUT = 6e4; | ||
var DEFAULT_OPTS = { | ||
paths: [], | ||
backtrace: false, | ||
dryRun: false, | ||
forceExit: false, | ||
failFast: false, | ||
format: [], | ||
formatOptions: {}, | ||
import: [], | ||
language: "en", | ||
name: [], | ||
order: "defined", | ||
publish: false, | ||
require: [], | ||
requireModule: [], | ||
retry: 0, | ||
strict: false, | ||
tags: "", | ||
worldParameters: {}, | ||
timeout: DEFAULT_TIMEOUT, | ||
scenarioLevelReporter: false, | ||
tagsInTitle: false, | ||
ignoreUndefinedDefinitions: false, | ||
failAmbiguousDefinitions: false, | ||
tagExpression: "", | ||
profiles: [] | ||
}; | ||
var CUCUMBER_HOOK_DEFINITION_TYPES = [ | ||
"beforeTestRunHookDefinitionConfigs", | ||
"beforeTestCaseHookDefinitionConfigs", | ||
"afterTestCaseHookDefinitionConfigs", | ||
"afterTestRunHookDefinitionConfigs" | ||
]; | ||
// src/utils.ts | ||
import path from "node:path"; | ||
import logger from "@wdio/logger"; | ||
import { isFunctionAsync } from "@wdio/utils"; | ||
var log = logger("@wdio/cucumber-framework:utils"); | ||
function generateSkipTagsFromCapabilities(capabilities, tags) { | ||
const generatedTags = []; | ||
const skipTag = /^@skip$|^@skip\((.*)\)$/; | ||
const match = (value, expr) => { | ||
if (Array.isArray(expr)) { | ||
return expr.indexOf(value) >= 0; | ||
} else if (expr instanceof RegExp) { | ||
return expr.test(value); | ||
} | ||
return (expr && ("" + expr).toLowerCase()) === (value && ("" + value).toLowerCase()); | ||
}; | ||
const parse = (skipExpr) => skipExpr.split(";").reduce((acc, splitItem) => { | ||
const pos = splitItem.indexOf("="); | ||
if (pos > 0) { | ||
try { | ||
acc[splitItem.substring(0, pos)] = (0, eval)( | ||
splitItem.substring(pos + 1) | ||
); | ||
} catch (err) { | ||
log.error(`Couldn't use tag "${splitItem}" for filtering because it is malformed`); | ||
} | ||
} | ||
return acc; | ||
}, {}); | ||
tags.flat(1).forEach((tag) => { | ||
const matched = tag.match(skipTag); | ||
if (matched) { | ||
const isSkip = [parse(matched[1] ?? "")].find((filter) => Object.keys(filter).every((key) => match(capabilities[key], filter[key]))); | ||
if (isSkip) { | ||
generatedTags.push(`(not ${tag.replace(/(\(|\))/g, "\\$1")})`); | ||
} | ||
} | ||
}); | ||
return generatedTags; | ||
} | ||
function setUserHookNames(options) { | ||
CUCUMBER_HOOK_DEFINITION_TYPES.forEach((hookName) => { | ||
options[hookName].forEach((testRunHookDefinition) => { | ||
const hookFn = testRunHookDefinition.code; | ||
if (!hookFn.name.startsWith("wdioHook")) { | ||
const userHookAsyncFn = async function(...args) { | ||
return hookFn.apply(this, args); | ||
}; | ||
const userHookFn = function(...args) { | ||
return hookFn.apply(this, args); | ||
}; | ||
testRunHookDefinition.code = isFunctionAsync(hookFn) ? userHookAsyncFn : userHookFn; | ||
} | ||
}); | ||
}); | ||
} | ||
// src/index.ts | ||
var FILE_PROTOCOL = "file://"; | ||
var uuidFn = IdGenerator.uuid(); | ||
var log2 = logger2("@wdio/cucumber-framework"); | ||
var require2 = createRequire(import.meta.url); | ||
var __dirname = path2.dirname(url.fileURLToPath(import.meta.url)); | ||
function getResultObject(world) { | ||
return { | ||
passed: world.result?.status === Status.PASSED || | ||
world.result?.status === Status.SKIPPED, | ||
error: world.result?.message, | ||
duration: world.result?.duration?.nanos / 1e6, // convert into ms | ||
}; | ||
return { | ||
passed: world.result?.status === Status.PASSED || world.result?.status === Status.SKIPPED, | ||
error: world.result?.message, | ||
duration: world.result?.duration?.nanos / 1e6 | ||
// convert into ms | ||
}; | ||
} | ||
class CucumberAdapter { | ||
_cid; | ||
_config; | ||
_specs; | ||
_capabilities; | ||
_reporter; | ||
_eventEmitter; | ||
_generateSkipTags; | ||
_cucumberFormatter; | ||
_cwd = process.cwd(); | ||
_newId = IdGenerator.incrementing(); | ||
_cucumberOpts; | ||
_hasTests = true; | ||
gherkinParser; | ||
constructor(_cid, _config, _specs, _capabilities, _reporter, _eventEmitter, _generateSkipTags = true, _cucumberFormatter = url.pathToFileURL(path.resolve(url.fileURLToPath(import.meta.url), '..', 'cucumberFormatter.js')).href) { | ||
this._cid = _cid; | ||
this._config = _config; | ||
this._specs = _specs; | ||
this._capabilities = _capabilities; | ||
this._reporter = _reporter; | ||
this._eventEmitter = _eventEmitter; | ||
this._generateSkipTags = _generateSkipTags; | ||
this._cucumberFormatter = _cucumberFormatter; | ||
this._eventEmitter = new EventEmitter(); | ||
this._cucumberOpts = Object.assign({}, DEFAULT_OPTS, this._config.cucumberOpts); | ||
/** | ||
* WebdriverIO doesn't support this Cucumber feature so we should let the user know | ||
*/ | ||
if (this._config.cucumberOpts?.parallel) { | ||
throw new Error('The option "parallel" is not supported by WebdriverIO'); | ||
} | ||
/** | ||
* Including the `cucumberFormatter` here allows you to use cucumber formatting in addition to other formatting options. | ||
*/ | ||
this._cucumberOpts.format.push([this._cucumberFormatter]); | ||
/** | ||
* formatting options used by custom cucumberFormatter | ||
* https://github.com/cucumber/cucumber-js/blob/3a945b1077d4539f8a363c955a0506e088ff4271/docs/formatters.md#options | ||
*/ | ||
this._cucumberOpts.formatOptions = { | ||
// We need to pass the user provided Formatter options | ||
// Example: JUnit formatter https://github.com/cucumber/cucumber-js/blob/3a945b1077d4539f8a363c955a0506e088ff4271/docs/formatters.md#junit | ||
// { junit: { suiteName: "MySuite" } } | ||
...(this._cucumberOpts.formatOptions ?? {}), | ||
// Our Cucumber Formatter options | ||
// Put last so that user does not override them | ||
_reporter: this._reporter, | ||
_cid: this._cid, | ||
_specs: this._specs, | ||
_eventEmitter: this._eventEmitter, | ||
_scenarioLevelReporter: this._cucumberOpts.scenarioLevelReporter, | ||
_tagsInTitle: this._cucumberOpts.tagsInTitle, | ||
_ignoreUndefinedDefinitions: this._cucumberOpts.ignoreUndefinedDefinitions, | ||
_failAmbiguousDefinitions: this._cucumberOpts.failAmbiguousDefinitions | ||
}; | ||
const builder = new Gherkin.AstBuilder(uuidFn); | ||
const matcher = new Gherkin.GherkinClassicTokenMatcher(this._cucumberOpts.language); | ||
this.gherkinParser = new Gherkin.Parser(builder, matcher); | ||
/** | ||
* as Cucumber doesn't support file:// formats yet we have to | ||
* remove it before adding it to Cucumber | ||
*/ | ||
this._specs = this._specs.map((spec) => spec.startsWith(FILE_PROTOCOL) ? url.fileURLToPath(spec) : spec); | ||
// backwards compatibility for tagExpression usage | ||
this._cucumberOpts.tags = this._cucumberOpts.tags || this._cucumberOpts.tagExpression; | ||
if (this._cucumberOpts.tagExpression) { | ||
log.warn("'tagExpression' is deprecated. Use 'tags' instead."); | ||
} | ||
var CucumberAdapter = class { | ||
constructor(_cid, _config, _specs, _capabilities, _reporter, _eventEmitter, _generateSkipTags = true, _cucumberFormatter = url.pathToFileURL(path2.resolve(__dirname, "cucumberFormatter.js")).href) { | ||
this._cid = _cid; | ||
this._config = _config; | ||
this._specs = _specs; | ||
this._capabilities = _capabilities; | ||
this._reporter = _reporter; | ||
this._eventEmitter = _eventEmitter; | ||
this._generateSkipTags = _generateSkipTags; | ||
this._cucumberFormatter = _cucumberFormatter; | ||
this._eventEmitter = new EventEmitter(); | ||
this._cucumberOpts = Object.assign( | ||
{}, | ||
DEFAULT_OPTS, | ||
this._config.cucumberOpts | ||
); | ||
if (this._config.cucumberOpts?.parallel) { | ||
throw new Error('The option "parallel" is not supported by WebdriverIO'); | ||
} | ||
readFiles(filePaths = []) { | ||
return filePaths.map((filePath) => { | ||
return Array.isArray(filePath) | ||
? filePath.map((file) => fs.readFileSync(path.resolve(file), 'utf8')) | ||
: fs.readFileSync(path.resolve(filePath), 'utf8'); | ||
}); | ||
this._cucumberOpts.format.push([this._cucumberFormatter]); | ||
this._cucumberOpts.formatOptions = { | ||
// We need to pass the user provided Formatter options | ||
// Example: JUnit formatter https://github.com/cucumber/cucumber-js/blob/3a945b1077d4539f8a363c955a0506e088ff4271/docs/formatters.md#junit | ||
// { junit: { suiteName: "MySuite" } } | ||
...this._cucumberOpts.formatOptions ?? {}, | ||
// Our Cucumber Formatter options | ||
// Put last so that user does not override them | ||
_reporter: this._reporter, | ||
_cid: this._cid, | ||
_specs: this._specs, | ||
_eventEmitter: this._eventEmitter, | ||
_scenarioLevelReporter: this._cucumberOpts.scenarioLevelReporter, | ||
_tagsInTitle: this._cucumberOpts.tagsInTitle, | ||
_ignoreUndefinedDefinitions: this._cucumberOpts.ignoreUndefinedDefinitions, | ||
_failAmbiguousDefinitions: this._cucumberOpts.failAmbiguousDefinitions | ||
}; | ||
const builder = new Gherkin.AstBuilder(uuidFn); | ||
const matcher = new Gherkin.GherkinClassicTokenMatcher( | ||
this._cucumberOpts.language | ||
); | ||
this.gherkinParser = new Gherkin.Parser(builder, matcher); | ||
this._specs = this._specs.map( | ||
(spec) => spec.startsWith(FILE_PROTOCOL) ? url.fileURLToPath(spec) : spec | ||
); | ||
this._cucumberOpts.tags = this._cucumberOpts.tags || this._cucumberOpts.tagExpression; | ||
if (this._cucumberOpts.tagExpression) { | ||
log2.warn("'tagExpression' is deprecated. Use 'tags' instead."); | ||
} | ||
getGherkinDocuments(files = []) { | ||
return this.readFiles(files).map((specContent, idx) => { | ||
const docs = [specContent].flat(1).map((content, ctIdx) => ({ | ||
...this.gherkinParser.parse(content), | ||
uri: Array.isArray(specContent) | ||
? files[idx][ctIdx] | ||
: files[idx], | ||
})); | ||
const [doc, ...etc] = docs; | ||
return etc.length ? docs : doc; | ||
}); | ||
} | ||
_cwd = process.cwd(); | ||
_newId = IdGenerator.incrementing(); | ||
_cucumberOpts; | ||
_hasTests = true; | ||
gherkinParser; | ||
readFiles(filePaths = []) { | ||
return filePaths.map((filePath) => { | ||
return Array.isArray(filePath) ? filePath.map( | ||
(file) => fs.readFileSync(path2.resolve(file), "utf8") | ||
) : fs.readFileSync(path2.resolve(filePath), "utf8"); | ||
}); | ||
} | ||
getGherkinDocuments(files = []) { | ||
return this.readFiles(files).map((specContent, idx) => { | ||
const docs = [specContent].flat(1).map( | ||
(content, ctIdx) => ({ | ||
...this.gherkinParser.parse(content), | ||
uri: Array.isArray(specContent) ? files[idx][ctIdx] : files[idx] | ||
}) | ||
); | ||
const [doc, ...etc] = docs; | ||
return etc.length ? docs : doc; | ||
}); | ||
} | ||
generateDynamicSkipTags() { | ||
return this.getGherkinDocuments([this._specs]).map((specDoc) => { | ||
const [doc] = [specDoc].flat(1); | ||
const pickles = Gherkin.compile(doc, "", uuidFn); | ||
const tags = pickles.map((pickle) => pickle.tags.map((tag) => tag.name)); | ||
const generatedTag = generateSkipTagsFromCapabilities(this._capabilities, tags); | ||
return generatedTag.length > 0 ? generatedTag.join(" and ") : []; | ||
}).flat(1); | ||
} | ||
async init() { | ||
if (this._generateSkipTags) { | ||
this._cucumberOpts.tags = this.generateDynamicSkipTags().concat(this._cucumberOpts.tags || []).join(" and "); | ||
} | ||
generateDynamicSkipTags() { | ||
return this.getGherkinDocuments([this._specs]) | ||
.map((specDoc) => { | ||
const [doc] = [specDoc].flat(1); | ||
const pickles = Gherkin.compile(doc, '', uuidFn); | ||
const tags = pickles.map((pickle) => pickle.tags.map((tag) => tag.name)); | ||
const generatedTag = generateSkipTagsFromCapabilities(this._capabilities, tags); | ||
return generatedTag.length > 0 ? generatedTag.join(' and ') : []; | ||
}).flat(1); | ||
const { plan } = await loadSources({ | ||
paths: this._specs, | ||
defaultDialect: this._cucumberOpts.language, | ||
order: this._cucumberOpts.order, | ||
names: this._cucumberOpts.name, | ||
tagExpression: this._cucumberOpts.tags | ||
}); | ||
this._specs = plan?.map((pl) => path2.resolve(pl.uri)); | ||
if (this._config.cucumberFeaturesWithLineNumbers?.length > 0) { | ||
this._specs = this._config.cucumberFeaturesWithLineNumbers; | ||
} | ||
async init() { | ||
if (this._generateSkipTags) { | ||
this._cucumberOpts.tags = this.generateDynamicSkipTags().concat(this._cucumberOpts.tags || []).join(' and '); | ||
this._specs = [...new Set(this._specs)]; | ||
this._cucumberOpts.paths = this._specs; | ||
this._hasTests = this._specs.length > 0; | ||
return this; | ||
} | ||
hasTests() { | ||
return this._hasTests; | ||
} | ||
async run() { | ||
let runtimeError; | ||
let result; | ||
let failedCount; | ||
let outStream; | ||
try { | ||
await this.registerRequiredModules(); | ||
supportCodeLibraryBuilder.reset(this._cwd, this._newId, { | ||
requireModules: this._cucumberOpts.requireModule, | ||
requirePaths: this._cucumberOpts.require, | ||
importPaths: this._cucumberOpts.import, | ||
loaders: [] | ||
}); | ||
this.addWdioHooks(this._config, supportCodeLibraryBuilder); | ||
await this.loadFiles(); | ||
this.wrapSteps(this._config); | ||
setUserHookNames(supportCodeLibraryBuilder); | ||
setDefaultTimeout(this._cucumberOpts.timeout); | ||
const supportCodeLibrary = supportCodeLibraryBuilder.finalize(); | ||
outStream = new Writable({ | ||
write(chunk, encoding, callback) { | ||
callback(); | ||
} | ||
// Filter the specs according to the tag expression | ||
// Some workers would only spawn to then skip the spec (Feature) file | ||
// Filtering at this stage can prevent the spawning of a massive number of workers | ||
const { plan } = await loadSources({ | ||
paths: this._specs, | ||
defaultDialect: this._cucumberOpts.language, | ||
order: this._cucumberOpts.order, | ||
names: this._cucumberOpts.name, | ||
tagExpression: this._cucumberOpts.tags, | ||
}); | ||
this._specs = plan?.map((pl) => path.resolve(pl.uri)); | ||
// Filter the specs according to line numbers | ||
if (this._config.cucumberFeaturesWithLineNumbers?.length > 0) { | ||
this._specs = this._config.cucumberFeaturesWithLineNumbers; | ||
} | ||
this._specs = [...new Set(this._specs)]; | ||
this._cucumberOpts.paths = this._specs; | ||
this._hasTests = this._specs.length > 0; | ||
return this; | ||
}); | ||
this._eventEmitter.on("getFailedCount", (payload) => { | ||
failedCount = payload; | ||
}); | ||
const environment = { | ||
cwd: this._cwd, | ||
stderr: outStream, | ||
stdout: outStream | ||
}; | ||
const { runConfiguration } = await loadConfiguration( | ||
{ profiles: this._cucumberOpts.profiles, provided: this._cucumberOpts }, | ||
environment | ||
); | ||
const { success } = await runCucumber( | ||
{ | ||
...runConfiguration, | ||
support: supportCodeLibrary || runConfiguration.support | ||
}, | ||
environment | ||
); | ||
result = success ? 0 : 1; | ||
if (this._cucumberOpts.ignoreUndefinedDefinitions && result) { | ||
result = failedCount; | ||
} | ||
} catch (err) { | ||
runtimeError = err; | ||
result = 1; | ||
} finally { | ||
outStream?.end(); | ||
} | ||
hasTests() { | ||
return this._hasTests; | ||
await executeHooksWithArgs("after", this._config.after, [ | ||
runtimeError || result, | ||
this._capabilities, | ||
this._specs | ||
]); | ||
if (runtimeError) { | ||
throw runtimeError; | ||
} | ||
async run() { | ||
let runtimeError; | ||
let result; | ||
let failedCount; | ||
let outStream; | ||
try { | ||
await this.registerRequiredModules(); | ||
supportCodeLibraryBuilder.reset(this._cwd, this._newId, { | ||
requireModules: this._cucumberOpts.requireModule, | ||
requirePaths: this._cucumberOpts.require, | ||
importPaths: this._cucumberOpts.import, | ||
loaders: [] | ||
}); | ||
this.addWdioHooks(this._config, supportCodeLibraryBuilder); | ||
await this.loadFiles(); | ||
this.wrapSteps(this._config); | ||
setUserHookNames(supportCodeLibraryBuilder); | ||
setDefaultTimeout(this._cucumberOpts.timeout); | ||
const supportCodeLibrary = supportCodeLibraryBuilder.finalize(); | ||
outStream = new Writable({ | ||
write(chunk, encoding, callback) { | ||
callback(); | ||
}, | ||
}); | ||
this._eventEmitter.on('getFailedCount', (payload) => { | ||
failedCount = payload; | ||
}); | ||
const environment = { | ||
cwd: this._cwd, | ||
stderr: outStream, | ||
stdout: outStream, | ||
}; | ||
const { runConfiguration } = await loadConfiguration({ profiles: this._cucumberOpts.profiles, provided: this._cucumberOpts }, environment); | ||
const { success } = await runCucumber({ | ||
...runConfiguration, | ||
support: supportCodeLibrary || runConfiguration.support, | ||
}, environment); | ||
result = success ? 0 : 1; | ||
/** | ||
* if we ignore undefined definitions we trust the reporter | ||
* with the fail count | ||
*/ | ||
if (this._cucumberOpts.ignoreUndefinedDefinitions && result) { | ||
result = failedCount; | ||
} | ||
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('@babel/register')({ ignore: [] }) }]` | ||
*/ | ||
registerRequiredModules() { | ||
return Promise.all( | ||
this._cucumberOpts.requireModule.map( | ||
async (requiredModule) => { | ||
if (Array.isArray(requiredModule)) { | ||
(await import(requiredModule[0])).default(requiredModule[1]); | ||
} else if (typeof requiredModule === "function") { | ||
requiredModule(); | ||
} else { | ||
await import(requiredModule); | ||
} | ||
} | ||
catch (err) { | ||
runtimeError = err; | ||
result = 1; | ||
} | ||
finally { | ||
outStream?.end(); | ||
} | ||
await executeHooksWithArgs('after', 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; | ||
) | ||
); | ||
} | ||
async loadFilesWithType(fileList) { | ||
return fileList.reduce( | ||
(files, file) => files.concat(isGlob(file) ? globSync(file) : [file]), | ||
[] | ||
); | ||
} | ||
async loadAndRefreshModule(modules) { | ||
const importedModules = []; | ||
for (const module of modules) { | ||
const filepath = path2.isAbsolute(module) ? module.startsWith(FILE_PROTOCOL) ? module : url.pathToFileURL(module).href : url.pathToFileURL(path2.join(process.cwd(), module)).href; | ||
const stepDefPath = url.pathToFileURL( | ||
require2.resolve(url.fileURLToPath(filepath)) | ||
).href; | ||
const cacheEntryToDelete = Object.keys(require2.cache).find( | ||
(u) => url.pathToFileURL(u).href === stepDefPath | ||
); | ||
if (cacheEntryToDelete) { | ||
delete require2.cache[cacheEntryToDelete]; | ||
} | ||
const importedModule = await import(filepath); | ||
importedModules.push(importedModule); | ||
} | ||
/** | ||
* 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('@babel/register')({ ignore: [] }) }]` | ||
*/ | ||
registerRequiredModules() { | ||
return Promise.all(this._cucumberOpts.requireModule.map(async (requiredModule) => { | ||
if (Array.isArray(requiredModule)) { | ||
(await import(requiredModule[0])).default(requiredModule[1]); | ||
} | ||
else if (typeof requiredModule === 'function') { | ||
requiredModule(); | ||
} | ||
else { | ||
await import(requiredModule); | ||
} | ||
})); | ||
} | ||
async loadFilesWithType(fileList) { | ||
return fileList.reduce((files, file) => files.concat(isGlob(file) ? globSync(file) : [file]), []); | ||
} | ||
async loadAndRefreshModule(modules) { | ||
const importedModules = []; | ||
for (const module of modules) { | ||
const filepath = path.isAbsolute(module) | ||
? module.startsWith(FILE_PROTOCOL) | ||
? module | ||
: url.pathToFileURL(module).href | ||
: url.pathToFileURL(path.join(process.cwd(), module)).href; | ||
// This allows rerunning a stepDefinitions file | ||
const stepDefPath = url.pathToFileURL(require.resolve(url.fileURLToPath(filepath))).href; | ||
const cacheEntryToDelete = Object.keys(require.cache).find((u) => url.pathToFileURL(u).href === stepDefPath); | ||
if (cacheEntryToDelete) { | ||
delete require.cache[cacheEntryToDelete]; | ||
} | ||
const importedModule = await import(filepath); | ||
importedModules.push(importedModule); | ||
return importedModules; | ||
} | ||
async loadFiles() { | ||
await Promise.all([ | ||
this.loadAndRefreshModule( | ||
await this.loadFilesWithType(this._cucumberOpts.require) | ||
), | ||
this.loadAndRefreshModule( | ||
await this.loadFilesWithType(this._cucumberOpts.import) | ||
) | ||
]); | ||
} | ||
/** | ||
* set `beforeFeature`, `afterFeature`, `beforeScenario`, `afterScenario`, 'beforeStep', 'afterStep' | ||
* @param {object} config config | ||
*/ | ||
addWdioHooks(config, supportCodeLibraryBuilder2) { | ||
const params = {}; | ||
this._eventEmitter.on("getHookParams", (payload) => { | ||
params.uri = payload.uri; | ||
params.feature = payload.feature; | ||
}); | ||
supportCodeLibraryBuilder2.methods.BeforeAll(async function wdioHookBeforeFeature() { | ||
await executeHooksWithArgs("beforeFeature", config.beforeFeature, [ | ||
params.uri, | ||
params.feature | ||
]); | ||
}); | ||
supportCodeLibraryBuilder2.methods.Before(async function wdioHookBeforeScenario(world) { | ||
await executeHooksWithArgs( | ||
"beforeScenario", | ||
config.beforeScenario, | ||
[world, this] | ||
); | ||
}); | ||
supportCodeLibraryBuilder2.methods.BeforeStep(async function wdioHookBeforeStep(world) { | ||
await executeHooksWithArgs("beforeStep", config.beforeStep, [ | ||
world.pickleStep, | ||
world.pickle, | ||
this | ||
]); | ||
}); | ||
supportCodeLibraryBuilder2.methods.AfterStep(async function wdioHookAfterStep(world) { | ||
await executeHooksWithArgs("afterStep", config.afterStep, [ | ||
world.pickleStep, | ||
world.pickle, | ||
getResultObject(world), | ||
this | ||
]); | ||
}); | ||
supportCodeLibraryBuilder2.methods.After(async function wdioHookAfterScenario(world) { | ||
await executeHooksWithArgs("afterScenario", config.afterScenario, [ | ||
world, | ||
getResultObject(world), | ||
this | ||
]); | ||
}); | ||
supportCodeLibraryBuilder2.methods.AfterAll(async function wdioHookAfterFeature() { | ||
await executeHooksWithArgs("afterFeature", config.afterFeature, [ | ||
params.uri, | ||
params.feature | ||
]); | ||
}); | ||
} | ||
/** | ||
* wraps step definition code with sync/async runner with a retry option | ||
* @param {object} config | ||
*/ | ||
wrapSteps(config) { | ||
const wrapStep = this.wrapStep; | ||
const cid = this._cid; | ||
let params; | ||
this._eventEmitter.on("getHookParams", (payload) => { | ||
params = payload; | ||
}); | ||
const getHookParams = () => params; | ||
setDefinitionFunctionWrapper( | ||
(fn, options = { retry: 0 }) => { | ||
if (fn.name.startsWith("wdioHook")) { | ||
return fn; | ||
} | ||
return importedModules; | ||
} | ||
async loadFiles() { | ||
await Promise.all([ | ||
this.loadAndRefreshModule(await this.loadFilesWithType(this._cucumberOpts.require)), | ||
this.loadAndRefreshModule(await this.loadFilesWithType(this._cucumberOpts.import)), | ||
]); | ||
} | ||
/** | ||
* set `beforeFeature`, `afterFeature`, `beforeScenario`, `afterScenario`, 'beforeStep', 'afterStep' | ||
* @param {object} config config | ||
*/ | ||
addWdioHooks(config, supportCodeLibraryBuilder) { | ||
const params = {}; | ||
this._eventEmitter.on('getHookParams', (payload) => { | ||
params.uri = payload.uri; | ||
params.feature = payload.feature; | ||
}); | ||
supportCodeLibraryBuilder.methods.BeforeAll(async function wdioHookBeforeFeature() { | ||
await executeHooksWithArgs('beforeFeature', config.beforeFeature, [ | ||
params.uri, | ||
params.feature, | ||
]); | ||
}); | ||
supportCodeLibraryBuilder.methods.Before(async function wdioHookBeforeScenario(world) { | ||
await executeHooksWithArgs('beforeScenario', config.beforeScenario, [world, this]); | ||
}); | ||
supportCodeLibraryBuilder.methods.BeforeStep(async function wdioHookBeforeStep(world) { | ||
await executeHooksWithArgs('beforeStep', config.beforeStep, [ | ||
world.pickleStep, | ||
world.pickle, | ||
this, | ||
]); | ||
}); | ||
supportCodeLibraryBuilder.methods.AfterStep(async function wdioHookAfterStep(world) { | ||
await executeHooksWithArgs('afterStep', config.afterStep, [ | ||
world.pickleStep, | ||
world.pickle, | ||
getResultObject(world), | ||
this, | ||
]); | ||
}); | ||
supportCodeLibraryBuilder.methods.After(async function wdioHookAfterScenario(world) { | ||
await executeHooksWithArgs('afterScenario', config.afterScenario, [ | ||
world, | ||
getResultObject(world), | ||
this, | ||
]); | ||
}); | ||
supportCodeLibraryBuilder.methods.AfterAll(async function wdioHookAfterFeature() { | ||
await executeHooksWithArgs('afterFeature', config.afterFeature, [ | ||
params.uri, | ||
params.feature, | ||
]); | ||
}); | ||
} | ||
/** | ||
* wraps step definition code with sync/async runner with a retry option | ||
* @param {object} config | ||
*/ | ||
wrapSteps(config) { | ||
const wrapStep = this.wrapStep; | ||
const cid = this._cid; | ||
let params; | ||
this._eventEmitter.on('getHookParams', (payload) => { | ||
params = payload; | ||
}); | ||
const getHookParams = () => params; | ||
setDefinitionFunctionWrapper((fn, options = { retry: 0 }) => { | ||
/** | ||
* hooks defined in wdio.conf are already wrapped | ||
*/ | ||
if (fn.name.startsWith('wdioHook')) { | ||
return fn; | ||
} | ||
/** | ||
* this flag is used to: | ||
* - avoid hook retry | ||
* - avoid wrap hooks with beforeStep and afterStep | ||
*/ | ||
const isStep = !fn.name.startsWith('userHook'); | ||
/** | ||
* Steps without wrapperOptions are returned promptly, avoiding failures when steps are defined with timeouts. | ||
* However, steps with set wrapperOptions have limitations in utilizing timeouts. | ||
*/ | ||
if (isStep && !options.retry) { | ||
return fn; | ||
} | ||
return wrapStep(fn, isStep, config, cid, options, getHookParams, this._cucumberOpts.timeout); | ||
}); | ||
} | ||
/** | ||
* wrap step definition to enable retry ability | ||
* @param {Function} code step definition | ||
* @param {boolean} isStep | ||
* @param {object} config | ||
* @param {string} cid cid | ||
* @param {StepDefinitionOptions} options | ||
* @param {Function} getHookParams step definition | ||
* @param {number} timeout the maximum time (in milliseconds) to wait for | ||
* @return {Function} wrapped step definition for sync WebdriverIO code | ||
*/ | ||
wrapStep(code, isStep, config, cid, options, getHookParams, timeout, hookName = undefined) { | ||
return function (...args) { | ||
const hookParams = getHookParams(); | ||
const retryTest = isStep && isFinite(options.retry) ? options.retry : 0; | ||
/** | ||
* wrap user step/hook with wdio before/after hooks | ||
*/ | ||
const beforeFn = config.beforeHook; | ||
const afterFn = config.afterHook; | ||
return testFnWrapper.call(this, isStep ? 'Step' : 'Hook', { specFn: code, specFnArgs: args }, { beforeFn: beforeFn, beforeFnArgs: (context) => [hookParams?.step, context] }, { afterFn: afterFn, afterFnArgs: (context) => [hookParams?.step, context] }, cid, retryTest, hookName, timeout); | ||
}; | ||
} | ||
} | ||
/** | ||
* Publishes a Cucumber report to a specified URL using NDJSON files from a directory. | ||
* @async | ||
* @param {string} cucumberMessageDir - The directory path that holds Cucumber NDJSON files. | ||
* @returns {Promise<void>} - A Promise that resolves when the report is successfully published. | ||
* @throws {Error} - Throws an error if there are issues with file reading or the publishing process. | ||
*/ | ||
const publishCucumberReport = async (cucumberMessageDir) => { | ||
const url = process.env.CUCUMBER_PUBLISH_REPORT_URL || 'https://messages.cucumber.io/api/reports'; | ||
const token = process.env.CUCUMBER_PUBLISH_REPORT_TOKEN; | ||
if (!token) { | ||
log.debug('Publishing reports are skipped because `CUCUMBER_PUBLISH_REPORT_TOKEN` environment variable value is not set.'); | ||
return; | ||
} | ||
const response = await fetch(url, { | ||
method: 'get', | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
const isStep = !fn.name.startsWith("userHook"); | ||
if (isStep && !options.retry) { | ||
return fn; | ||
} | ||
}); | ||
const location = response.headers.get('location'); | ||
const files = (await readdir(path.normalize(cucumberMessageDir))).filter((file) => path.extname(file) === '.ndjson'); | ||
const cucumberMessage = (await Promise.all(files.map((file) => readFile(path.normalize(path.join(cucumberMessageDir, file)), 'utf8')))).join(''); | ||
await fetch(location, { | ||
method: 'put', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: `${cucumberMessage}` | ||
}); | ||
return wrapStep(fn, isStep, config, cid, options, getHookParams, this._cucumberOpts.timeout); | ||
} | ||
); | ||
} | ||
/** | ||
* wrap step definition to enable retry ability | ||
* @param {Function} code step definition | ||
* @param {boolean} isStep | ||
* @param {object} config | ||
* @param {string} cid cid | ||
* @param {StepDefinitionOptions} options | ||
* @param {Function} getHookParams step definition | ||
* @param {number} timeout the maximum time (in milliseconds) to wait for | ||
* @return {Function} wrapped step definition for sync WebdriverIO code | ||
*/ | ||
wrapStep(code, isStep, config, cid, options, getHookParams, timeout, hookName = void 0) { | ||
return function(...args) { | ||
const hookParams = getHookParams(); | ||
const retryTest = isStep && isFinite(options.retry) ? options.retry : 0; | ||
const beforeFn = config.beforeHook; | ||
const afterFn = config.afterHook; | ||
return testFnWrapper.call( | ||
this, | ||
isStep ? "Step" : "Hook", | ||
{ specFn: code, specFnArgs: args }, | ||
{ beforeFn, beforeFnArgs: (context) => [hookParams?.step, context] }, | ||
{ afterFn, afterFnArgs: (context) => [hookParams?.step, context] }, | ||
cid, | ||
retryTest, | ||
hookName, | ||
timeout | ||
); | ||
}; | ||
} | ||
}; | ||
const _CucumberAdapter = CucumberAdapter; | ||
const adapterFactory = {}; | ||
/** | ||
* tested by smoke tests | ||
*/ | ||
/* istanbul ignore next */ | ||
adapterFactory.init = async function (...args) { | ||
// @ts-ignore just passing through args | ||
const adapter = new _CucumberAdapter(...args); | ||
const instance = await adapter.init(); | ||
return instance; | ||
var publishCucumberReport = async (cucumberMessageDir) => { | ||
const url2 = process.env.CUCUMBER_PUBLISH_REPORT_URL || "https://messages.cucumber.io/api/reports"; | ||
const token = process.env.CUCUMBER_PUBLISH_REPORT_TOKEN; | ||
if (!token) { | ||
log2.debug("Publishing reports are skipped because `CUCUMBER_PUBLISH_REPORT_TOKEN` environment variable value is not set."); | ||
return; | ||
} | ||
const response = await fetch(url2, { | ||
method: "get", | ||
headers: { | ||
Authorization: `Bearer ${token}` | ||
} | ||
}); | ||
const location = response.headers.get("location"); | ||
const files = (await readdir(path2.normalize(cucumberMessageDir))).filter((file) => path2.extname(file) === ".ndjson"); | ||
const cucumberMessage = (await Promise.all( | ||
files.map( | ||
(file) => readFile( | ||
path2.normalize(path2.join(cucumberMessageDir, file)), | ||
"utf8" | ||
) | ||
) | ||
)).join(""); | ||
await fetch(location, { | ||
method: "put", | ||
headers: { | ||
"Content-Type": "application/json" | ||
}, | ||
body: `${cucumberMessage}` | ||
}); | ||
}; | ||
export default adapterFactory; | ||
export { CucumberAdapter, adapterFactory, After, AfterAll, AfterStep, Before, BeforeAll, BeforeStep, Given, When, Then, DataTable, World, setDefaultTimeout, setDefinitionFunctionWrapper, setWorldConstructor, defineParameterType, defineStep, publishCucumberReport }; | ||
var _CucumberAdapter = CucumberAdapter; | ||
var adapterFactory = {}; | ||
adapterFactory.init = async function(...args) { | ||
const adapter = new _CucumberAdapter(...args); | ||
const instance = await adapter.init(); | ||
return instance; | ||
}; | ||
var src_default = adapterFactory; | ||
export { | ||
After, | ||
AfterAll, | ||
AfterStep, | ||
Before, | ||
BeforeAll, | ||
BeforeStep, | ||
CucumberAdapter, | ||
DataTable, | ||
FILE_PROTOCOL, | ||
Given, | ||
Then, | ||
When, | ||
World, | ||
adapterFactory, | ||
src_default as default, | ||
defineParameterType, | ||
defineStep, | ||
publishCucumberReport, | ||
setDefaultTimeout, | ||
setDefinitionFunctionWrapper, | ||
setWorldConstructor | ||
}; |
{ | ||
"name": "@wdio/cucumber-framework", | ||
"version": "9.0.0-alpha.426+d760644c4", | ||
"version": "9.0.0", | ||
"description": "A WebdriverIO plugin. Adapter for Cucumber.js testing framework.", | ||
@@ -28,11 +28,12 @@ "author": "Christian Bromann <mail@bromann.dev>", | ||
"exports": { | ||
".": [ | ||
{ | ||
"types": "./build/index.d.ts", | ||
"import": "./build/index.js", | ||
"require": "./build/index.cjs" | ||
}, | ||
"./build/index.cjs" | ||
], | ||
"./package.json": "./package.json" | ||
".": { | ||
"types": "./build/index.d.ts", | ||
"import": "./build/index.js", | ||
"requireSource": "./src/index.cts", | ||
"require": "./build/index.cjs" | ||
}, | ||
"./formatter": { | ||
"source": "./src/cucumberFormatter.ts", | ||
"import": "./build/cucumberFormatter.js" | ||
} | ||
}, | ||
@@ -46,6 +47,7 @@ "types": "./build/index.d.ts", | ||
"@types/node": "^20.1.0", | ||
"@wdio/logger": "9.0.0-alpha.426+d760644c4", | ||
"@wdio/types": "9.0.0-alpha.426+d760644c4", | ||
"@wdio/utils": "9.0.0-alpha.426+d760644c4", | ||
"@wdio/logger": "9.0.0", | ||
"@wdio/types": "9.0.0", | ||
"@wdio/utils": "9.0.0", | ||
"glob": "^10.2.2", | ||
"import-meta-resolve": "^4.1.0", | ||
"is-glob": "^4.0.0" | ||
@@ -59,3 +61,3 @@ }, | ||
}, | ||
"gitHead": "d760644c4c6e1ef910c0bee120cb422e25dbbe06" | ||
"gitHead": "957693463371a4cb329395dcdbce8fb0c930ab93" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1607
0
2
3
74984
10
18
+ Addedimport-meta-resolve@^4.1.0
+ Added@promptbook/utils@0.69.5(transitive)
+ Added@puppeteer/browsers@2.6.1(transitive)
+ Added@tootallnate/quickjs-emscripten@0.23.0(transitive)
+ Added@types/which@2.0.2(transitive)
+ Added@types/yauzl@2.10.3(transitive)
+ Added@wdio/logger@8.38.09.0.0(transitive)
+ Added@wdio/types@9.0.0(transitive)
+ Added@wdio/utils@9.0.0(transitive)
+ Added@zip.js/zip.js@2.7.54(transitive)
+ Addedagent-base@7.1.3(transitive)
+ Addedast-types@0.13.4(transitive)
+ Addedb4a@1.6.7(transitive)
+ Addedbare-events@2.5.0(transitive)
+ Addedbare-fs@2.3.5(transitive)
+ Addedbare-os@2.4.4(transitive)
+ Addedbare-path@2.1.3(transitive)
+ Addedbare-stream@2.6.1(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbasic-ftp@5.0.5(transitive)
+ Addedbuffer@5.7.1(transitive)
+ Addedbuffer-crc32@0.2.13(transitive)
+ Addedchalk@5.4.1(transitive)
+ Addedcliui@8.0.1(transitive)
+ Addedcommander@9.5.0(transitive)
+ Addeddata-uri-to-buffer@4.0.16.0.2(transitive)
+ Addeddecamelize@6.0.0(transitive)
+ Addeddeepmerge-ts@7.1.3(transitive)
+ Addeddegenerator@5.0.1(transitive)
+ Addededge-paths@3.0.5(transitive)
+ Addededgedriver@5.6.1(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedescodegen@2.1.0(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedestraverse@5.3.0(transitive)
+ Addedesutils@2.0.3(transitive)
+ Addedextract-zip@2.0.1(transitive)
+ Addedfast-fifo@1.3.2(transitive)
+ Addedfast-xml-parser@4.5.1(transitive)
+ Addedfd-slicer@1.1.0(transitive)
+ Addedfetch-blob@3.2.0(transitive)
+ Addedformdata-polyfill@4.0.10(transitive)
+ Addedgeckodriver@4.5.1(transitive)
+ Addedget-caller-file@2.0.5(transitive)
+ Addedget-port@7.1.0(transitive)
+ Addedget-stream@5.2.0(transitive)
+ Addedget-uri@6.0.4(transitive)
+ Addedhttp-proxy-agent@7.0.2(transitive)
+ Addedhttps-proxy-agent@7.0.6(transitive)
+ Addedieee754@1.2.1(transitive)
+ Addedimport-meta-resolve@4.1.0(transitive)
+ Addedip-address@9.0.5(transitive)
+ Addedisexe@3.1.1(transitive)
+ Addedjsbn@1.1.0(transitive)
+ Addedlocate-app@2.5.0(transitive)
+ Addedloglevel@1.9.2(transitive)
+ Addedloglevel-plugin-prefix@0.8.4(transitive)
+ Addedlru-cache@7.18.3(transitive)
+ Addednetmask@2.0.2(transitive)
+ Addednode-domexception@1.0.0(transitive)
+ Addednode-fetch@3.3.2(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpac-proxy-agent@7.1.0(transitive)
+ Addedpac-resolver@7.0.1(transitive)
+ Addedpend@1.2.0(transitive)
+ Addedproxy-agent@6.5.0(transitive)
+ Addedproxy-from-env@1.1.0(transitive)
+ Addedpump@3.0.2(transitive)
+ Addedqueue-tick@1.0.1(transitive)
+ Addedrequire-directory@2.1.1(transitive)
+ Addedsafaridriver@0.1.2(transitive)
+ Addedsemver@7.6.3(transitive)
+ Addedsmart-buffer@4.2.0(transitive)
+ Addedsocks@2.8.3(transitive)
+ Addedsocks-proxy-agent@8.0.5(transitive)
+ Addedspacetrim@0.11.59(transitive)
+ Addedsplit2@4.2.0(transitive)
+ Addedsprintf-js@1.1.3(transitive)
+ Addedstreamx@2.21.1(transitive)
+ Addedstrnum@1.0.5(transitive)
+ Addedtar-fs@3.0.6(transitive)
+ Addedtar-stream@3.1.7(transitive)
+ Addedtext-decoder@1.2.3(transitive)
+ Addedthrough@2.3.8(transitive)
+ Addedtype-fest@4.26.0(transitive)
+ Addedunbzip2-stream@1.4.3(transitive)
+ Addeduserhome@1.0.1(transitive)
+ Addedwait-port@1.1.0(transitive)
+ Addedweb-streams-polyfill@3.3.3(transitive)
+ Addedwhich@4.0.0(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedy18n@5.0.8(transitive)
+ Addedyargs@17.7.2(transitive)
+ Addedyargs-parser@21.1.1(transitive)
+ Addedyauzl@2.10.0(transitive)
Updated@wdio/logger@9.0.0
Updated@wdio/types@9.0.0
Updated@wdio/utils@9.0.0