@wdio/allure-reporter
Version:
A WebdriverIO reporter plugin to create Allure Test Reports
1,535 lines (1,524 loc) • 66.2 kB
JavaScript
// src/reporter.ts
import process3 from "node:process";
import { createHash } from "node:crypto";
import path3 from "node:path";
import { stringify } from "csv-stringify/sync";
import WDIOReporter from "@wdio/reporter";
import {
ContentType as AllureContentType,
description as description2,
descriptionHtml as descriptionHtml2,
label as label2,
LabelName,
Stage as AllureStage2,
Status as AllureStatusEnum
} from "allure-js-commons";
import { getMessageAndTraceFromError } from "allure-js-commons/sdk";
import { FileSystemWriter, getEnvironmentLabels, getSuiteLabels, ReporterRuntime, includedInTestPlan as includedInTestPlan2, parseTestPlan as parseTestPlan2 } from "allure-js-commons/sdk/reporter";
import { setGlobalTestRuntime } from "allure-js-commons/sdk/runtime";
// src/WdioTestRuntime.ts
import { MessageTestRuntime } from "allure-js-commons/sdk/runtime";
// src/constants.ts
var events = {
runtimeMessage: "allure:runtimeMessage",
addTestInfo: "allure:addTestInfo",
startStep: "allure:startStep",
endStep: "allure:endStep",
addLabel: "allure:addLabel",
addLink: "allure:addLink",
addFeature: "allure:addFeature",
addStory: "allure:addStory",
addEpic: "allure:addEpic",
addSuite: "allure:addSuite",
addSubSuite: "allure:addSubSuite",
addParentSuite: "allure:addParentSuite",
addOwner: "allure:addOwner",
addSeverity: "allure:addSeverity",
addTag: "allure:addTag",
addIssue: "allure:addIssue",
addAllureId: "allure:addAllureId",
addTestId: "allure:addTestId",
addDescription: "allure:addDescription",
addAttachment: "allure:addAttachment",
addStep: "allure:addStep",
addArgument: "allure:addArgument",
addAllureStep: "allure:addAllureStep"
};
var DEFAULT_CID = "default";
// src/WdioTestRuntime.ts
var WdioTestRuntime = class extends MessageTestRuntime {
async sendMessage(message) {
process.emit(events.runtimeMessage, message);
return Promise.resolve();
}
};
// src/utils.ts
import stripAnsi from "strip-ansi";
import { Status, Status as AllureStatus } from "allure-js-commons";
// src/compoundError.ts
function indentAll(lines) {
return lines.split("\n").map((x) => " " + x).join("\n");
}
var CompoundError = class extends Error {
innerErrors;
constructor(...innerErrors) {
const message = ["CompoundError: One or more errors occurred. ---\n"].concat(innerErrors.map(
(x) => x.message && x.stack?.includes(x.message) ? x.stack ? `${indentAll(x.stack)}
--- End of stack trace ---
` : "" : (x.message ? ` ${x.message}
--- End of error message ---
` : "") + (x.stack ? `${indentAll(x.stack)}
--- End of stack trace ---
` : "")
)).join("\n");
super(message);
this.innerErrors = innerErrors;
}
};
// src/utils.ts
import { fileURLToPath } from "node:url";
import path from "node:path";
import process2 from "node:process";
var getTestStatus = (test, config) => {
if (config && config.framework === "jasmine") {
return AllureStatus.FAILED;
}
if (test.error) {
if (test.error.message) {
const message = test.error.message.trim().toLowerCase();
return message.startsWith("assertionerror") || message.includes("expect") ? AllureStatus.FAILED : AllureStatus.BROKEN;
}
if (test.error.stack) {
const stackTrace = test.error.stack.trim().toLowerCase();
return stackTrace.startsWith("assertionerror") || stackTrace.includes("expect") ? AllureStatus.FAILED : AllureStatus.BROKEN;
}
} else if (test.errors) {
return AllureStatus.FAILED;
}
return AllureStatus.BROKEN;
};
var getErrorFromFailedTest = (test) => {
if (test.errors && Array.isArray(test.errors) && test.errors.length) {
for (let i = 0; i < test.errors.length; i += 1) {
if (test.errors[i].message) {
test.errors[i].message = stripAnsi(test.errors[i].message);
}
if (test.errors[i].stack) {
test.errors[i].stack = stripAnsi(test.errors[i].stack);
}
}
return test.errors.length === 1 ? test.errors[0] : new CompoundError(...test.errors);
}
if (test.error) {
if (test.error.message) {
test.error.message = stripAnsi(test.error.message);
}
if (test.error.stack) {
test.error.stack = stripAnsi(test.error.stack);
}
}
return test.error;
};
var getStatusDetailsFromFailedTest = (test) => {
const error = getErrorFromFailedTest(test);
if (!error) {
return void 0;
}
return {
message: error.message,
trace: error.stack
};
};
var findLast = (arr, predicate) => {
let result;
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i])) {
result = arr[i];
break;
}
}
return result;
};
var findLastIndex = (arr, predicate) => {
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i])) {
return i;
}
}
return -1;
};
var isScreenshotCommand = (command) => {
const isScrenshotEndpoint = /\/session\/[^/]*(\/element\/[^/]*)?\/screenshot/;
return (
// WebDriver protocol
command.endpoint && isScrenshotEndpoint.test(command.endpoint) || // DevTools protocol
command.command === "takeScreenshot"
);
};
var convertSuiteTagsToLabels = (tags) => {
if (!tags) {
return [];
}
return tags.reduce((acc, tag2) => {
const label3 = tag2.name.replace(/[@]/, "").split("=");
if (label3.length === 2) {
return acc.concat({ name: label3[0], value: label3[1] });
}
return acc;
}, []);
};
var last = (arr) => arr[arr.length - 1];
var getCid = () => {
const cid = process2.env.WDIO_WORKER;
return cid ?? DEFAULT_CID;
};
var toPosix = (p) => p.replace(/[\\/]+/g, "/");
var fromUrlish = (p) => {
if (!p) {
return p;
}
if (p.startsWith("file:")) {
const cleaned = dropPosSuffix(p);
try {
return fileURLToPath(cleaned);
} catch {
return cleaned.replace(/^file:\/*/, "");
}
}
return p;
};
var dropPosSuffix = (p) => p.replace(/:(\d+)(?::\d+)?$/, "");
var absPosix = (p) => {
const s = fromUrlish(p);
const abs = path.isAbsolute(s) ? s : path.resolve(s);
return toPosix(abs);
};
var relNoSlash = (p) => {
if (!p) {
return "";
}
const rel = toPosix(path.relative(process2.cwd(), absPosix(p)));
return rel.replace(/^\.\/+/, "");
};
var toFullName = (file, title) => `${relNoSlash(file)}#${title}`;
function isObject(value) {
return typeof value === "object" && value !== null;
}
function isEmptyObject(value) {
return isObject(value) && Object.keys(value).length === 0;
}
var toPackageLabel = (p) => {
if (!p) {
return "";
}
const fsPath = fromUrlish(p);
const noPos = fsPath.split(":")[0];
const rel = relNoSlash(noPos);
return rel.replace(/\//g, ".");
};
var toPackageLabelCucumber = (p) => {
if (!p) {
return "";
}
let fsPath = p;
if (p.startsWith("file:")) {
const cleaned = dropPosSuffix(p);
try {
fsPath = fileURLToPath(cleaned);
} catch {
fsPath = cleaned.replace(/^file:\/*/, "");
}
}
const rel = relNoSlash(fsPath);
return rel.replace(/\//g, ".");
};
// src/state.ts
import { Stage as AllureStage } from "allure-js-commons";
var AllureReportState = class {
constructor(allureRuntime) {
this.allureRuntime = allureRuntime;
}
_scopesStack = [];
_executablesStack = [];
_fixturesStack = [];
_currentTestUuid;
_currentTestName;
messages = [];
_pendingHookMessages = [];
_isCapturingPendingHook = false;
_isRuntimeMessage(message) {
const wdioSpecificTypes = [
"allure:suite:start",
"allure:suite:end",
"allure:test:start",
"allure:test:end",
"allure:test:info",
"allure:hook:start",
"allure:hook:end"
];
return !wdioSpecificTypes.includes(message.type);
}
_openSteps = 0;
_openHookSteps = /* @__PURE__ */ new Map();
_hookMeta = /* @__PURE__ */ new Map();
_incHookSteps = (uuid) => this._openHookSteps.set(uuid, (this._openHookSteps.get(uuid) ?? 0) + 1);
_decHookSteps = (uuid) => this._openHookSteps.set(uuid, Math.max(0, (this._openHookSteps.get(uuid) ?? 0) - 1));
async _closeOpenedHookSteps(uuid, status, stop, statusDetails) {
let n = this._openHookSteps.get(uuid) ?? 0;
while (n > 0) {
this.allureRuntime.applyRuntimeMessages(uuid, [{
type: "step_stop",
data: { status, stop: stop ?? Date.now(), statusDetails }
}]);
n--;
}
this._openHookSteps.set(uuid, 0);
}
async _closeOpenedSteps(status, stop, statusDetails) {
if (!this._currentTestUuid) {
return;
}
while (this._openSteps > 0) {
this.allureRuntime.applyRuntimeMessages(this._currentTestUuid, [{
type: "step_stop",
data: { status, stop: stop ?? Date.now(), statusDetails }
}]);
this._openSteps--;
}
}
get hasPendingSuite() {
const s = findLastIndex(this.messages, ({ type }) => type === "allure:suite:start");
const e = findLastIndex(this.messages, ({ type }) => type === "allure:suite:end");
return s > e;
}
get hasPendingTest() {
const s = findLastIndex(this.messages, ({ type }) => type === "allure:test:start");
const e = findLastIndex(this.messages, ({ type }) => type === "allure:test:end");
return s > e;
}
get hasPendingStep() {
const s = findLastIndex(this.messages, ({ type }) => type === "step_start");
const e = findLastIndex(this.messages, ({ type }) => type === "step_stop");
return s > e;
}
get hasPendingHook() {
const s = findLastIndex(this.messages, ({ type }) => type === "allure:hook:start");
const e = findLastIndex(this.messages, ({ type }) => type === "allure:hook:end");
return s > e;
}
get currentFeature() {
const m = findLast(
this.messages,
({ type, data }) => type === "allure:suite:start" && Boolean(data.feature)
);
return m?.data?.name;
}
async _openScope() {
const scopeUuid = this.allureRuntime.startScope();
this._scopesStack.push(scopeUuid);
}
async _closeScope() {
const scopeUuid = this._scopesStack.pop();
if (scopeUuid) {
await this.allureRuntime.writeScope(scopeUuid);
}
}
async _writeLastTest() {
if (!this._currentTestUuid) {
return;
}
await this.allureRuntime.writeTest(this._currentTestUuid);
this._currentTestUuid = void 0;
}
async _startSuite() {
if (this._currentTestUuid) {
await this._writeLastTest();
}
await this._openScope();
}
async _endSuite(write = false) {
await this._closeScope();
if (write) {
await this._writeLastTest();
}
}
async _startTest(message) {
if (this._currentTestUuid) {
await this._writeLastTest();
}
await this._openScope();
const { name, start } = message.data;
const testUuid = this.allureRuntime.startTest(
{
name,
start
},
[...this._scopesStack]
);
this._executablesStack.push(testUuid);
this._currentTestUuid = testUuid;
this._currentTestName = name;
this._openSteps = 0;
if (this._pendingHookMessages.length > 0 && !this.currentFeature) {
await this._attachPendingHookToCurrentTest("before", name, "");
await this._attachPendingHookToCurrentTest("before", name, "each");
}
}
_addTestInfo(message) {
const { fullName } = message.data;
const testUuid = last(this._executablesStack);
if (!testUuid) {
return;
}
this.allureRuntime.updateTest(testUuid, (r) => {
r.fullName = fullName;
});
}
async _endTest(message) {
const { status, stage, stop, duration, statusDetails } = message.data;
const testUuid = this._executablesStack.pop();
if (!testUuid) {
return;
}
await this._closeOpenedSteps(status, stop, statusDetails);
await this._closeScope();
this.allureRuntime.updateTest(testUuid, (r) => {
r.status = status;
if (stage) {
r.stage = stage;
}
if (statusDetails) {
r.statusDetails = statusDetails;
}
});
this.allureRuntime.stopTest(testUuid, { stop, duration });
}
async _startHook(message) {
const { name, type, start } = message.data;
if (/after all/i.test(name) && this._currentTestUuid) {
await this._writeLastTest();
}
if (!this._currentTestUuid) {
this._pendingHookMessages.push(message);
this._isCapturingPendingHook = true;
return;
}
const testScopeUuid = this._scopesStack[this._scopesStack.length - 1];
if (testScopeUuid) {
const hookUuid = this.allureRuntime.startFixture(testScopeUuid, type, { name, start });
if (hookUuid) {
this._fixturesStack.push(hookUuid);
this._openHookSteps.set(hookUuid, 0);
this._hookMeta.set(hookUuid, { name, type });
}
}
}
async _endHook(message) {
const { status, statusDetails, duration, stop } = message.data;
if (!this._currentTestUuid) {
this._pendingHookMessages.push(message);
this._isCapturingPendingHook = false;
return;
}
const hookUuid = this._fixturesStack.pop();
if (!hookUuid) {
return;
}
await this._closeOpenedHookSteps(hookUuid, status, stop, statusDetails);
this.allureRuntime.updateFixture(hookUuid, (r) => {
r.status = status;
if (statusDetails) {
r.statusDetails = statusDetails;
}
});
this.allureRuntime.stopFixture(hookUuid, { stop, duration });
const meta = this._hookMeta.get(hookUuid);
const testUuid = this._currentTestUuid;
if (meta && testUuid) {
this.allureRuntime.updateTest(testUuid, (r) => {
const result = r;
const fixtures = Array.isArray(result.fixtures) ? result.fixtures : [];
fixtures.push({ name: meta.name, type: meta.type, status, stage: AllureStage.FINISHED });
result.fixtures = fixtures;
});
}
}
pushRuntimeMessage(message) {
this.messages.push(message);
}
async processRuntimeMessage() {
for (let i = 0; i < this.messages.length; i++) {
const message = this.messages[i];
const lastMessage = i === this.messages.length - 1;
switch (message.type) {
case "allure:suite:start":
await this._startSuite();
continue;
case "allure:suite:end":
await this._endSuite(lastMessage);
continue;
case "allure:test:start":
await this._startTest(message);
continue;
case "allure:test:info":
this._addTestInfo(message);
continue;
case "allure:test:end":
await this._endTest(message);
if (this._pendingHookMessages.length > 0 && !this.currentFeature) {
await this._attachPendingHookToCurrentTest("after", this._currentTestName ?? "unknown test", "each");
await this._attachPendingHookToCurrentTest("after", this._currentTestName ?? "unknown test", "");
}
continue;
case "allure:hook:start":
if (!this._currentTestUuid) {
this._pendingHookMessages.push(message);
this._isCapturingPendingHook = true;
continue;
}
await this._startHook(message);
continue;
case "allure:hook:end":
if (!this._currentTestUuid) {
this._pendingHookMessages.push(message);
this._isCapturingPendingHook = false;
continue;
}
if (this._fixturesStack.length === 0 && this._pendingHookMessages.length > 0 && !this.currentFeature) {
this._pendingHookMessages.push(message);
this._isCapturingPendingHook = false;
const testName = this._currentTestName ?? "unknown test";
await this._attachPendingHookToCurrentTest("before", testName, "");
await this._attachPendingHookToCurrentTest("before", testName, "each");
await this._attachPendingHookToCurrentTest("after", testName, "each");
await this._attachPendingHookToCurrentTest("after", testName, "");
continue;
}
await this._endHook(message);
continue;
default:
break;
}
const hookUuid = this._fixturesStack.at(-1);
const target = hookUuid ?? this._currentTestUuid;
if (!target) {
if (this._isCapturingPendingHook) {
this._pendingHookMessages.push(message);
}
continue;
}
if (message.type === "step_start") {
if (hookUuid) {
this._incHookSteps(hookUuid);
} else {
this._openSteps++;
}
}
if (message.type === "step_stop") {
if (hookUuid) {
this._decHookSteps(hookUuid);
} else {
this._openSteps = Math.max(0, this._openSteps - 1);
}
}
if (this._isRuntimeMessage(message)) {
this.allureRuntime.applyRuntimeMessages(target, [message]);
}
}
if (this._currentTestUuid) {
await this._writeLastTest();
}
while (this._scopesStack.length > 0) {
await this._closeScope();
}
}
async _attachPendingHookToCurrentTest(kind, testName, scope = "each") {
if (!this._currentTestUuid) {
return;
}
const startIdx = this._pendingHookMessages.findIndex(
(m) => m.type === "allure:hook:start" && m.data.type === kind && typeof m.data.name === "string" && (scope === "each" ? /each/i.test(m.data.name) : /all/i.test(m.data.name))
);
if (startIdx === -1) {
return;
}
const endIdx = this._pendingHookMessages.findIndex((m, idx) => idx > startIdx && m.type === "allure:hook:end");
if (endIdx === -1) {
return;
}
const startMsg = this._pendingHookMessages[startIdx];
await this._startHook({
type: "allure:hook:start",
data: {
name: String(startMsg.data.name || ""),
type: kind,
start: startMsg.data.start
}
});
const currentHookUuid = this._fixturesStack.at(-1);
if (currentHookUuid) {
for (let i = startIdx + 1; i < endIdx; i++) {
const msg = this._pendingHookMessages[i];
if (msg.type === "step_start") {
this._incHookSteps(currentHookUuid);
}
if (msg.type === "step_stop") {
this._decHookSteps(currentHookUuid);
}
if (this._isRuntimeMessage(msg)) {
this.allureRuntime.applyRuntimeMessages(currentHookUuid, [msg]);
}
}
}
const endMsg = this._pendingHookMessages[endIdx];
await this._endHook(endMsg);
this._pendingHookMessages.splice(startIdx, endIdx - startIdx + 1);
}
};
// src/testplan.ts
import path2 from "node:path";
import { includedInTestPlan, parseTestPlan } from "allure-js-commons/sdk/reporter";
import { fileURLToPath as fileURLToPath2 } from "node:url";
var toPosixPath = (p) => {
try {
if (p.startsWith("file:")) {
p = fileURLToPath2(p);
}
} catch {
}
const abs = path2.isAbsolute(p) ? p : path2.resolve(p);
return abs.replace(/\\/g, "/");
};
var toRelPosixPath = (p) => {
let abs = toPosixPath(p);
const cwd = process.cwd().replace(/\\/g, "/");
if (abs.startsWith(cwd)) {
abs = abs.slice(cwd.length);
}
if (!abs.startsWith("/")) {
abs = "/" + abs;
}
return abs;
};
function applyTestPlanLabel(plan, push, args) {
if (!plan) {
return;
}
const loose = (() => {
if (args?.file && Array.isArray(args?.testPath)) {
const a = args;
return { file: a.file, fullTitle: a.testPath.map(String).join(" ") };
}
return args;
})();
const { file, fullTitle, fullName } = loose;
const base = fullName || fullTitle || "";
const suiteDotTitle = (() => {
if (!base) {
return base;
}
const parts = base.split(" ");
if (parts.length < 2) {
return base;
}
const last2 = parts.pop();
const suite2 = parts.join(" ");
return suite2 ? `${suite2}.${last2}` : last2;
})();
const candidates = [
base,
suiteDotTitle,
file ? `${toRelPosixPath(file)}#${base}` : "",
file ? `${toRelPosixPath(file)}#${suiteDotTitle}` : "",
file ? `${toPosixPath(file)}#${base}` : "",
file ? `${toPosixPath(file)}#${suiteDotTitle}` : "",
file ? `${path2.basename(file)}#${base}` : "",
file ? `${path2.basename(file)}#${suiteDotTitle}` : ""
].filter(Boolean);
for (const c of candidates) {
if (includedInTestPlan(plan, { fullName: c })) {
return;
}
}
}
var kWrapped = /* @__PURE__ */ Symbol("allure_mocha_wrapped");
function installBddTestPlanFilter(plan) {
const globalBdd = globalThis;
const suiteStack = [];
const decide = (title) => {
const suites = [...suiteStack];
const fullSuiteTitle = suites.join(" ");
const leafSuite = suites[suites.length - 1] || "";
const titleCandidates = [
suites.length ? `${fullSuiteTitle} ${title}` : title,
suites.length ? `${fullSuiteTitle}.${title}` : title,
leafSuite ? `${leafSuite} ${title}` : title,
leafSuite ? `${leafSuite}.${title}` : title
];
const detectCurrentFile = () => {
const st = String(new Error().stack || "");
const lines = st.split("\n").map((l) => l.trim());
const cwd = process.cwd().replace(/\\/g, "/");
for (const ln of lines) {
const m = ln.match(/(?:\(|\s)(file:\/\/[^):]+|[A-Za-z]:[^):]+|\/[^^):]+):\d+:\d+\)?$/);
let p = (m?.[1] || "").trim();
if (!p) {
continue;
}
p = p.split("?")[0];
p = toPosixPath(p);
if (!/\.(?:m?js|c?ts)$/i.test(p)) {
continue;
}
if (p.includes("node_modules")) {
continue;
}
if (p.includes("wdio-allure-reporter")) {
continue;
}
if (!p.startsWith(cwd) && !p.startsWith("/test/")) {
continue;
}
return p;
}
return void 0;
};
const file = detectCurrentFile();
const fileVariants = [];
if (file) {
const absolutePath = toPosixPath(file);
const relativeWithSlash = toRelPosixPath(file);
const relativeNoSlash = relativeWithSlash.startsWith("/") ? relativeWithSlash.slice(1) : relativeWithSlash;
const baseName = path2.basename(absolutePath);
fileVariants.push(absolutePath, relativeWithSlash, relativeNoSlash, baseName);
}
const withFiles = [];
for (const candidateTitle of titleCandidates) {
withFiles.push(candidateTitle);
for (const variant of fileVariants) {
withFiles.push(`${variant}#${candidateTitle}`);
}
}
const candidates = withFiles;
for (const t of candidates) {
if (includedInTestPlan(plan, { fullName: t })) {
return true;
}
}
return false;
};
const wrapSuite = (original) => {
if (original[kWrapped]) {
return original;
}
const wrapped = ((title, fn) => {
if (typeof fn !== "function") {
return original(title);
}
return original(title, function() {
suiteStack.push(title);
try {
fn.call(this);
} finally {
suiteStack.pop();
}
});
});
wrapped.only = original.only;
wrapped.skip = original.skip?.bind(original);
Object.defineProperty(wrapped, kWrapped, { value: true });
return wrapped;
};
const wrapIt = (original) => {
if (original[kWrapped]) {
return original;
}
const wrapped = ((title, fn) => {
const allow = decide(title);
if (!allow) {
return void 0;
}
return original(title, fn);
});
wrapped.only = ((title, fn) => {
const only = original.only;
if (!only) {
return original(title, fn);
}
if (!decide(title)) {
return void 0;
}
return only(title, fn);
});
wrapped.skip = original.skip?.bind(original);
Object.defineProperty(wrapped, kWrapped, { value: true });
return wrapped;
};
const install = (name, kind) => {
const current = globalBdd[name];
const apply = kind === "suite" ? wrapSuite : wrapIt;
if (typeof current === "function") {
;
globalBdd[name] = apply(current);
return;
}
Object.defineProperty(globalBdd, name, {
configurable: true,
enumerable: true,
get() {
return void 0;
},
set(v) {
const wrapped = typeof v === "function" ? apply(v) : v;
Object.defineProperty(globalBdd, name, {
value: wrapped,
writable: true,
configurable: true,
enumerable: true
});
}
});
};
install("describe", "suite");
install("context", "suite");
install("it", "test");
install("specify", "test");
}
function autoInstallMochaFilter() {
const envPlan = process.env.ALLURE_TESTPLAN_PATH;
const isMocha = String(process.env.WDIO_FRAMEWORK || "").toLowerCase().includes("mocha");
if (!envPlan || envPlan === "undefined" || envPlan === "null" || !isMocha) {
return;
}
const plan = parseTestPlan();
if (plan) {
installBddTestPlanFilter(plan);
}
}
autoInstallMochaFilter();
// src/common/api.ts
import {
allureId,
attachment as allureAttachment,
ContentType,
description,
descriptionHtml,
epic,
feature,
historyId,
issue,
label,
link,
owner,
parameter,
parentSuite,
severity,
Status as Status2,
step as allureStep,
story,
subSuite,
suite,
tag,
testCaseId,
tms
} from "allure-js-commons";
var tellReporter = (event, msg = {}) => {
process.emit(event, msg);
};
function addLabel(name, value) {
label(name, value);
}
async function addLink(url, name, type) {
await link(url, name, type);
}
async function addAllureId(id) {
await allureId(id);
}
async function addFeature(featureName) {
await feature(featureName);
}
async function addSeverity(value) {
await severity(value);
}
async function addIssue(id) {
await issue(id);
}
async function addTestId(testId) {
await tms(testId);
}
async function addStory(storyName) {
await story(storyName);
}
async function addSuite(suiteName) {
await suite(suiteName);
}
async function addParentSuite(suiteName) {
await parentSuite(suiteName);
}
async function addSubSuite(suiteName) {
await subSuite(suiteName);
}
async function addEpic(epicName) {
await epic(epicName);
}
async function addOwner(ownerName) {
await owner(ownerName);
}
async function addTag(tagName) {
await tag(tagName);
}
async function addEnvironment(name, value) {
console.warn("\u26A0\uFE0F addEnvironment is deprecated and has no longer any functionality. Use reportedEnvironmentVars in wdio config instead. Read more in https://webdriver.io/docs/allure-reporter.");
}
async function addDescription(descriptionText, descriptionType) {
if (descriptionType === "html") {
await descriptionHtml(descriptionText);
return;
}
await description(descriptionText);
}
async function addAttachment(name, content, type) {
if (content instanceof Buffer) {
await allureAttachment(name, content, type);
return;
}
const contentString = typeof content === "string" ? content : JSON.stringify(content);
await allureAttachment(name, contentString, type);
}
async function addArgument(name, value) {
await parameter(name, value);
}
async function addHistoryId(id) {
await historyId(id);
}
async function addTestCaseId(id) {
await testCaseId(id);
}
async function startStep(title) {
tellReporter(events.startStep, title);
}
async function endStep(status = Status2.PASSED) {
if (!Object.values(Status2).includes(status)) {
throw new Error(`Step status must be ${Object.values(Status2).join(" or ")}. You tried to set "${status}"`);
}
tellReporter(events.endStep, status);
}
async function addStep(title, attachment = void 0, status = Status2.PASSED) {
if (!Object.values(Status2).includes(status)) {
throw new Error(`Step status must be ${Object.values(Status2).join(" or ")}. You tried to set "${status}"`);
}
tellReporter(events.startStep, title);
if (attachment?.content) {
await allureAttachment(
attachment.name || "Attachment",
Buffer.from(attachment.content, "utf8"),
attachment.type || ContentType.TEXT
);
}
tellReporter(events.endStep, status);
}
async function step(name, body) {
return allureStep(name, body);
}
// src/reporter.ts
function isRecord(v) {
return typeof v === "object" && v !== null;
}
function getStringField(obj, key) {
if (!isRecord(obj)) {
return void 0;
}
const v = obj[key];
return typeof v === "string" ? v : void 0;
}
function getType(obj) {
return getStringField(obj, "type");
}
function getKeyword(obj) {
return getStringField(obj, "keyword");
}
function getParentType(obj) {
return getStringField(obj, "parent") ?? getStringField(obj, "type");
}
function getTitle(obj) {
return getStringField(obj, "title");
}
function isFeatureFilePath(file) {
return typeof file === "string" && /\.feature$/i.test(file.replace(/\\/g, "/"));
}
function hasCucumberKeywordInTitle(title) {
if (!title) {
return false;
}
return /^(Given|When|Then|And|But)\b/.test(title);
}
var AllureReporter = class _AllureReporter extends WDIOReporter {
_allureRuntime;
_capabilities;
_isMultiremote;
_config;
_options;
_consoleOutput = "";
_originalStdoutWrite;
_isFlushing = false;
_cid;
_testPlan;
_suiteStartedDepthByCid = /* @__PURE__ */ new Map();
_currentLeafTitleByCid = /* @__PURE__ */ new Map();
_tpSkipByCid = /* @__PURE__ */ new Map();
_linkTemplates;
_suiteStackByCid = /* @__PURE__ */ new Map();
_suiteStack = (cid) => this._suiteStackByCid.get(cid) ?? this._suiteStackByCid.set(cid, []).get(cid);
_pkgByCid = /* @__PURE__ */ new Map();
_cukeScenarioActiveByCid = /* @__PURE__ */ new Map();
allureStatesByCid = /* @__PURE__ */ new Map();
static getTimeOrNow(d) {
return d instanceof Date ? d.getTime() : Date.now();
}
get isSynchronised() {
return !this._isFlushing;
}
constructor(options) {
const { outputDir, resultsDir, ...rest } = options;
const normalizeTpl = (tpl) => tpl ? tpl.replace(/\{\}/g, "%s") : tpl;
const links = {
...rest.links,
...options.issueLinkTemplate ? { issue: { urlTemplate: normalizeTpl(options.issueLinkTemplate) } } : {},
...options.tmsLinkTemplate ? { tms: { urlTemplate: normalizeTpl(options.tmsLinkTemplate) } } : {}
};
super({ ...rest, outputDir });
this._linkTemplates = links;
this._originalStdoutWrite = process3.stdout.write.bind(process3.stdout);
this._allureRuntime = new ReporterRuntime({
...rest,
links,
environmentInfo: options.reportedEnvironmentVars,
writer: new FileSystemWriter({
resultsDir: outputDir || resultsDir || "allure-results"
})
});
this._capabilities = {};
this._options = options;
{
const envPlan = process3.env.ALLURE_TESTPLAN_PATH;
if (envPlan && envPlan !== "undefined" && envPlan !== "null") {
try {
this._testPlan = parseTestPlan2();
} catch {
this._testPlan = void 0;
}
}
if (this._testPlan) {
installBddTestPlanFilter(this._testPlan);
}
}
this._registerListeners();
if (options.addConsoleLogs) {
const self = this;
process3.stdout.write = function(chunk, encoding, cb) {
const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
if (!str.includes("mwebdriver")) {
self._consoleOutput += str;
}
if (typeof encoding === "function") {
return self._originalStdoutWrite(chunk, void 0, encoding);
}
return self._originalStdoutWrite(chunk, encoding, cb);
};
}
}
_ensureState(cid) {
if (!this.allureStatesByCid.has(cid)) {
this.allureStatesByCid.set(cid, new AllureReportState(this._allureRuntime));
}
return this.allureStatesByCid.get(cid);
}
_pushRuntimeMessage(message) {
const cid = this._currentCid();
const state = this._ensureState(cid);
state.pushRuntimeMessage(message);
}
_attachFile(payload) {
const { name, content, contentType } = payload;
this._pushRuntimeMessage({
type: "attachment_content",
data: {
name,
content: Buffer.from(content).toString("base64"),
contentType,
encoding: "base64"
}
});
}
_currentCid() {
return this._cid || DEFAULT_CID;
}
_attachLogs() {
if (!this._consoleOutput) {
return;
}
this._attachFile({
name: "Console Logs",
content: Buffer.from(`.........Console Logs.........
${this._consoleOutput}`, "utf8"),
contentType: AllureContentType.TEXT
});
this._consoleOutput = "";
}
_attachJSON(payload) {
const { name, json } = payload;
const content = typeof json === "string" ? json : JSON.stringify(json, null, 2);
this._attachFile({
name,
content: Buffer.from(String(content), "utf8"),
contentType: AllureContentType.JSON
});
}
_attachScreenshot(payload) {
const { name, content } = payload;
this._attachFile({ name, content, contentType: AllureContentType.PNG });
}
_handleCucumberStepStart(t) {
if (!this._hasPendingTest) {
return;
}
const start = _AllureReporter.getTimeOrNow(t.start);
this._startStep({ name: t.title, start });
const arg = t.argument;
const dataTable = Array.isArray(arg?.rows) ? arg.rows.map((row) => row.cells) : void 0;
if (dataTable?.length) {
this._attachFile({
name: "Data Table",
content: Buffer.from(stringify(dataTable), "utf8"),
contentType: AllureContentType.CSV
});
}
}
_handleCucumberStepEnd(t, status, details) {
this._attachLogs();
const stop = _AllureReporter.getTimeOrNow(t.end);
this._endStep({ status, stop, statusDetails: details });
}
_startSuite(payload) {
this._pushRuntimeMessage({ type: "allure:suite:start", data: payload });
}
_endSuite() {
this._pushRuntimeMessage({ type: "allure:suite:end", data: {} });
}
_startTest(payload) {
this._pushRuntimeMessage({ type: "allure:test:start", data: payload });
this._setTestParameters();
}
_endTest(payload) {
this._pushRuntimeMessage({ type: "allure:test:end", data: payload });
}
_skipTest() {
this._endTest({ status: AllureStatusEnum.SKIPPED, stage: AllureStage2.PENDING, stop: Date.now() });
}
_startStep(payload) {
this._pushRuntimeMessage({ type: "step_start", data: payload });
}
_endStep(payload) {
this._pushRuntimeMessage({ type: "step_stop", data: payload });
}
_startHook(payload) {
this._pushRuntimeMessage({ type: "allure:hook:start", data: payload });
}
_endHook(payload) {
const { status, statusDetails, stop = Date.now(), duration } = payload;
this._pushRuntimeMessage({ type: "allure:hook:end", data: { status, statusDetails, stop, duration } });
}
/**
* Stable key from current capabilities (browser/device + version) for hash.
* Must NOT include cid. Used to make historyId unique per environment.
*/
_getCapabilityKey() {
if (this._isMultiremote) {
return "multiremote";
}
const capsUnknown = this._capabilities;
const browserName = getStringField(capsUnknown, "browserName");
const device = getStringField(capsUnknown, "device");
const desired = (() => {
const maybe = capsUnknown?.["desired"];
return isRecord(maybe) ? maybe : void 0;
})();
const deviceName = getStringField(desired, "deviceName") || getStringField(desired, "appium:deviceName") || getStringField(capsUnknown, "deviceName") || getStringField(capsUnknown, "appium:deviceName");
let targetName = device || browserName || deviceName || "";
const desiredPlatformVersion = getStringField(desired, "appium:platformVersion");
if (desired && deviceName && desiredPlatformVersion) {
targetName = `${device || deviceName} ${desiredPlatformVersion}`;
}
const version = getStringField(capsUnknown, "os_version") || getStringField(capsUnknown, "osVersion") || getStringField(capsUnknown, "browserVersion") || getStringField(capsUnknown, "version") || getStringField(capsUnknown, "appium:platformVersion") || "";
return version ? `${targetName}-${version}`.trim() : targetName.trim();
}
/**
* Emits historyId (and testCaseId) from full title and capability key (browser/device from test run).
* Must NOT include cid or file path: cid varies between runs; user asked for no filesystem.
*/
_emitHistoryIdsFrom(fullTitleForHash) {
const capKey = this._getCapabilityKey();
const input = capKey ? `${fullTitleForHash}#${capKey}` : fullTitleForHash;
const legacy = this._md5(input);
this._pushRuntimeMessage({ type: "metadata", data: { historyId: legacy, testCaseId: legacy } });
}
_setTestParameters() {
const cid = getCid();
if (!this._isMultiremote) {
const capsUnknown = this._capabilities;
const browserName = getStringField(capsUnknown, "browserName");
const device = getStringField(capsUnknown, "device");
const desired = (() => {
const maybe = capsUnknown?.["desired"];
return isRecord(maybe) ? maybe : void 0;
})();
const deviceName = getStringField(desired, "deviceName") || getStringField(desired, "appium:deviceName") || getStringField(capsUnknown, "deviceName") || getStringField(capsUnknown, "appium:deviceName");
let targetName = device || browserName || deviceName || cid;
const desiredPlatformVersion = getStringField(desired, "appium:platformVersion");
if (desired && deviceName && desiredPlatformVersion) {
targetName = `${device || deviceName} ${desiredPlatformVersion}`;
}
const browserstackVersion = getStringField(capsUnknown, "os_version") || getStringField(capsUnknown, "osVersion");
const version = browserstackVersion || getStringField(capsUnknown, "browserVersion") || getStringField(capsUnknown, "version") || getStringField(capsUnknown, "appium:platformVersion") || "";
const paramName = deviceName || device ? "device" : "browser";
const paramValue = version ? `${targetName}-${version}` : targetName || "";
if (paramValue) {
this._pushRuntimeMessage({
type: "metadata",
data: { parameters: [{ name: paramName, value: paramValue }] }
});
}
} else {
this._pushRuntimeMessage({
type: "metadata",
data: { parameters: [{ name: "isMultiremote", value: "true" }] }
});
}
const st = this._ensureState(this._currentCid());
const feat = st.currentFeature;
if (feat) {
this._pushRuntimeMessage({
type: "metadata",
data: { labels: [{ name: LabelName.FEATURE, value: feat }] }
});
}
}
_registerListeners() {
setGlobalTestRuntime(new WdioTestRuntime());
process3.on(events.addTestInfo, (payload) => {
const { file, testPath, cid = DEFAULT_CID } = payload;
if (file) {
this._pkgByCid.set(cid, absPosix(file));
}
const fileStr = (file || "").replace(/\\/g, "/");
if (/\.feature$/i.test(fileStr)) {
const ft = Array.isArray(testPath) ? testPath.map(String).join(" ") : "";
const fullName = `${relNoSlash(file)}#${ft}`;
this._pushRuntimeMessage({ type: "allure:test:info", data: { fullName, fullTitle: ft } });
applyTestPlanLabel(this._testPlan, (m) => this._pushRuntimeMessage(m), {
file,
testPath
});
const suitePath = [...this._suiteStack(cid)];
const pkg = toPackageLabelCucumber(file || this._pkgByCid.get(cid) || "");
const labels = [
...getSuiteLabels(suitePath),
...pkg ? [{ name: LabelName.PACKAGE, value: pkg }] : [],
{ name: LabelName.LANGUAGE, value: "javascript" },
{ name: LabelName.FRAMEWORK, value: "wdio" },
{ name: LabelName.THREAD, value: cid },
...getEnvironmentLabels()
];
this._pushRuntimeMessage({ type: "metadata", data: { labels } });
if (cid !== DEFAULT_CID) {
this._pushRuntimeMessage({
type: "metadata",
data: { labels: [{ name: LabelName.THREAD, value: cid }] }
});
}
}
});
process3.on(events.startStep, (name) => {
if (this._tpSkipActive(this._currentCid())) {
return;
}
this._pushRuntimeMessage({ type: "step_start", data: { name, start: Date.now() } });
});
process3.on(
events.endStep,
(arg) => {
if (this._tpSkipActive(this._currentCid())) {
return;
}
const payload = typeof arg === "string" ? { status: arg } : arg;
this._pushRuntimeMessage({ type: "step_stop", data: { ...payload, stop: Date.now() } });
}
);
process3.on(events.runtimeMessage, (payload) => {
if (this._tpSkipActive(this._currentCid())) {
return;
}
this._pushRuntimeMessage(payload);
});
}
onRunnerStart(runner) {
this._cid = runner.cid;
this._ensureState(runner.cid);
this._config = runner.config;
this._capabilities = runner.capabilities;
this._isMultiremote = runner.isMultiremote || false;
const specs = runner.specs || [];
if (specs.length) {
this._pkgByCid.set(runner.cid, absPosix(specs[0]));
}
}
async onRunnerEnd(_runner) {
this._isFlushing = true;
try {
for (const [cid, state] of this.allureStatesByCid) {
await state.processRuntimeMessage();
this.allureStatesByCid.delete(cid);
}
} finally {
if (this._options.addConsoleLogs) {
process3.stdout.write = this._originalStdoutWrite;
}
this._isFlushing = false;
}
this._allureRuntime.writeEnvironmentInfo();
}
onSuiteStart(suite2) {
const cid = this._currentCid();
switch (suite2.type) {
case "feature": {
this._cukeScenarioActiveByCid.delete(cid);
this._suiteStack(cid).push(suite2.title);
this._startSuite({ name: suite2.title, feature: true });
const featureFile = suite2.file;
if (isFeatureFilePath(featureFile)) {
this._pkgByCid.set(cid, absPosix(featureFile));
}
break;
}
case "scenario": {
this._cukeScenarioActiveByCid.set(cid, true);
this._ensureSuitesStarted(cid);
const mustSkip = this._decideCucumberSkip(cid, suite2.title);
const start = _AllureReporter.getTimeOrNow(suite2.start);
const uuid = suite2.uid;
this._consoleOutput = "";
this._startTest({ name: suite2.title, start, uuid });
this._currentLeafTitleByCid.set(cid, suite2.title);
const fullTitleForHash = this._mochaFullTitle(cid, suite2.title);
this._emitHistoryIdsFrom(fullTitleForHash);
const fullName = toFullName(this._pkgByCid.get(cid), fullTitleForHash);
this._pushRuntimeMessage({ type: "allure:test:info", data: { fullName, fullTitle: fullTitleForHash } });
applyTestPlanLabel(this._testPlan, (m) => this._pushRuntimeMessage(m), {
fullTitle: fullTitleForHash,
file: this._pkgByCid.get(cid)
});
this._emitBaseLabels(cid);
convertSuiteTagsToLabels(suite2?.tags || []).forEach((lbl) => {
switch (lbl.name) {
case "issue":
label2("issue", lbl.value);
break;
case "testId":
label2("testId", lbl.value);
break;
default:
label2(lbl.name, lbl.value);
}
});
if (suite2.description) {
description2(suite2.description);
}
if (mustSkip) {
this._tpSkipByCid.set(cid, true);
this._pushRuntimeMessage({
type: "attachment_content",
data: {
name: "allure-skip",
content: Buffer.from("allure-skip").toString("base64"),
contentType: "application/vnd.allure.skipcucumber+json",
encoding: "base64"
}
});
this._pushRuntimeMessage({ type: "metadata", data: { labels: [{ name: "ALLURE_TESTPLAN_SKIP", value: "true" }] } });
this._attachLogs();
this._endTest({ status: AllureStatusEnum.SKIPPED, stage: AllureStage2.PENDING, stop: Date.now() });
return;
}
break;
}
default: {
this._suiteStack(cid).push(suite2.title);
this._startSuite({ name: suite2.title });
}
}
}
onSuiteEnd(suite2) {
const isScenario = suite2.type === "scenario";
const cid = this._currentCid();
if (isScenario && this._tpSkipActive(cid)) {
this._tpSkipByCid.delete(cid);
this._cukeScenarioActiveByCid.delete(cid);
return;
}
if (!isScenario) {
const stack = this._suiteStack(cid);
const depthBeforePop = stack.length;
const startedDepth = this._suiteStartedDepthByCid.get(cid) ?? 0;
if (!this._tpActive()) {
this._endSuite();
stack.pop();
return;
}
if (startedDepth >= depthBeforePop) {
this._endSuite();
this._suiteStartedDepthByCid.set(cid, startedDepth - 1);
}
if (stack.length) {
this._endSuite();
stack.pop();
}
return;
}
this._cukeScenarioActiveByCid.delete(cid);
suite2.hooks = suite2.hooks.map((h) => {
h.state = h.state || AllureStatusEnum.PASSED;
return h;
});
const suiteChildren = [...suite2.tests, ...suite2.hooks];
const isSkipped = suite2.tests.every((t) => [AllureStatusEnum.SKIPPED].includes(t.state)) && suite2.hooks.every((h) => [AllureStatusEnum.PASSED, AllureStatusEnum.SKIPPED].includes(h.state));
if (isSkipped) {
this._attachLogs();
this._endTest({
status: AllureStatusEnum.SKIPPED,
stage: AllureStage2.PENDING,
stop: _AllureReporter.getTimeOrNow(suite2.end)
});
return;
}
const failed = suiteChildren.find((i) => i.state === AllureStatusEnum.FAILED);
if (failed) {
const status = getTestStatus(failed);
const error = getErrorFromFailedTest(failed);
this._attachLogs();
this._endTest({
stage: AllureStage2.FINISHED,
status,
statusDetails: error ? { message: error.message, trace: error.stack } : void 0,
stop: _AllureReporter.getTimeOrNow(suite2.end)
});
return;
}
this._attachLogs();
this._endTest({
stage: AllureStage2.FINISHED,
status: AllureStatusEnum.PASSED,
stop: _AllureReporter.getTimeOrNow(suite2.end)
});
}
onSuiteRetry(_suite) {
this._pushRuntimeMessage({
type: "metadata",
data: { labels: [{ name: LabelName.TAG, value: "retried" }] }
});
}
_inCucumberStepMode(exec) {
const kind = getType(exec);
const keyword = getKeyword(exec);
const parentType = getParentType(exec);
const file = exec.file;
const title = getTitle(exec);
const hasCucumberKeyword = hasCucumberKeywordInTitle(title);
const isFeatureFile = isFeatureFilePath(file);
if (kind === "hook" || kind === "feature" || kind === "scenario") {
return false;
}
if (kind === "step") {
return true;
}
if (keyword) {
return true;
}
if (parentType === "scenario") {
return true;
}
if (isFeatureFile) {
return true;
}
if (hasCucumberKeyword) {
return true;
}
return false;
}
onTestStart(test) {
this._consoleOutput = "";
const fullTitle = test.fullTitle;
const file = test.file;
applyTestPlanLabel(this._testPlan, (m) => this._pushRuntimeMessage(m), { file, fullTitle });
if (this._inCucumberStepMode(test)) {
this._handleCucumberStepStart(test);
return;
}
const cid = this._currentCid();
this._ensureSuitesStarted(cid);
const start = _AllureReporter.getTimeOrNow(test.start);
const uuid = test.uid;
const ft = test.fullTitle || this._mochaFullTitle(cid, test.title);
this._startTest({ name: test.title, start, uuid });
const testCaseTitle = ft || fullTitle;
if (testCaseTitle) {
this._emitHistoryIdsFrom(testCaseTitle);
}
const fullName = toFullName(this._pkgByCid.get(cid), fullTitle || test.title);
this._pushRuntimeMessage({ type: "allure:test:info", data: { fullName } });
const suitePath = [...this._suiteStack(cid)];
const pkg = isFeatureFilePath(this._pkgByCid.get(cid)) ? toPackageLabelCucumber(this._pkgByCid.get(cid) || "") : toPackageLabel(this._pkgByCid.get(cid) || "");
const labels = [
...getSuiteLabels(suitePath),
...pkg ? [{ name: LabelName.PACKAGE, value: pkg }] : [],
{ name: LabelName.LANGUAGE, value: "javascript" },
{ name: LabelName.FRAMEWORK, value: "wdio" },
{ name: LabelName.THREAD, value: cid },
...getEnvironmentLabels()
];
this._pushRuntimeMessage({ type: "metadata", data: { labels } });
}
onTestPass(test) {
if (this._inCucumberStepMode(test)) {
this._handleCucumberStepEnd(test, AllureStatusEnum.PASSED);
return;
}
this._attachLogs();
const end = _AllureReporter.getTimeOrNow(test.end);
this._endTest({
status: AllureStatusEnum.PASSED,
stage: AllureStage2.FINISHED,
stop: end,
duration: test.duration
});
}
onTestRetry(test) {
if (this._inCucumberStepMode(test)) {
this._handleCucumberStepEnd(test, getTestStatus(test, this._config), getStatusDetailsFromFailedTest(test));
return;
}
this._attachLogs();
const status = getTestStatus(test, this._config);
this._endTest({
status,
statusDetails: getStatusDetailsFromFailedTest(test),
stop: _AllureReporter.getTimeOrNow(test.end),
duration: test.duration
});
}
onTestFail(test) {
if (this._inCucumberStepMode(test)) {
const st = getTestStatus(test, this._config);
this._handleCucumberStepEnd(test, st, getStatusDetailsFromFailedTest(test));
return;
}
if (!this._hasPendingTest