UNPKG

@wdio/allure-reporter

Version:
1,535 lines (1,524 loc) 66.2 kB
// 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