Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@wdio/cucumber-framework

Package Overview
Dependencies
Maintainers
0
Versions
370
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wdio/cucumber-framework - npm Package Compare versions

Comparing version 9.0.0-alpha.426 to 9.0.0

929

build/cucumberFormatter.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc