UNPKG

n8n-nodes-robotframework

Version:

An n8n custom node that executes Robot Framework scripts, allowing users to automate testing and workflow tasks within n8n

231 lines 11 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RobotFramework = void 0; const n8n_workflow_1 = require("n8n-workflow"); const n8n_workflow_2 = require("n8n-workflow"); const child_process_1 = require("child_process"); const util_1 = require("util"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const execAsync = (0, util_1.promisify)(child_process_1.exec); class RobotFramework { constructor() { this.description = { displayName: 'Robot Framework', name: 'robotFramework', icon: 'file:robotframework.svg', group: ['Robot Framework'], version: 1, description: 'Executes Robot Framework test scripts, allowing automation and testing directly within n8n workflows. Configure test scripts, control output file generation, and leverage Robot Framework capabilities for robust testing automation.', defaults: { name: 'Robot Framework', }, inputs: ['main'], outputs: ['main'], properties: [ { displayName: 'Robot Framework Script', name: 'robotScript', type: 'string', default: '*** Settings ***\n\n*** Variables ***\n\n*** Tasks ***\n\n*** Keywords ***', description: 'Enter the full Robot Framework script here, including Settings, Variables, Tasks/Test Cases, and Keywords sections', }, { displayName: 'Include Output JSON', name: 'includeOutputJson', type: 'boolean', default: false, description: 'Whether to include the output.JSON file as an attachment', }, { displayName: 'Include Log HTML', name: 'includeLogHtml', type: 'boolean', default: false, description: 'Whether to include the log.html file as an attachment', }, { displayName: 'Include Report HTML', name: 'includeReportHtml', type: 'boolean', default: false, description: 'Whether to include the report.html file as an attachment', }, { displayName: 'Include Other Input Fields', name: 'includeOtherFields', type: 'boolean', default: false, description: "Whether to pass to the output all the input fields (along with variables from 'Robot Framework' node)", }, ], }; } async execute() { const stripOutputPart = (terminalOutput) => { const lastOutputIndex = terminalOutput.lastIndexOf('Output:'); if (lastOutputIndex !== -1) { return terminalOutput.substring(0, lastOutputIndex).trim(); } return terminalOutput; }; const addAttachments = (files, attachments) => { files.forEach((file) => { if (file.include && fs.existsSync(file.path)) { attachments[file.name] = { data: fs.readFileSync(file.path).toString('base64'), mimeType: 'application/octet-stream', fileName: file.name, }; } }); }; const extractVariables = (outputJson) => { var _a; const variables = {}; const tests = ((_a = outputJson === null || outputJson === void 0 ? void 0 : outputJson.suite) === null || _a === void 0 ? void 0 : _a.tests) || []; const entries = tests.flatMap((test) => test.body || []); entries.forEach((entry) => { var _a; if (entry.name === 'Log' && entry.owner === 'BuiltIn' && Array.isArray(entry.body) && Array.isArray(entry.args)) { const variableNameMatch = (_a = entry.args[0]) === null || _a === void 0 ? void 0 : _a.match(/^\$\{(.*?)\}$/); if (variableNameMatch) { const variableName = variableNameMatch[1].trim(); const messageEntry = entry.body.find((item) => item.type === 'MESSAGE' && item.level === 'INFO'); if (messageEntry && messageEntry.message) { variables[variableName] = messageEntry.message.trim(); } } } }); return variables; }; const transformVariables = (variables) => { const transformed = {}; for (const key in variables) { const cleanKey = key.replace(/^\$\{|\}$/g, ''); transformed[cleanKey] = variables[key]; } return transformed; }; const getOutputBaseDir = () => { if (process.env.ROBOT_OUTPUT_DIR) { return process.env.ROBOT_OUTPUT_DIR; } const n8nFilesDir = '/home/node/.n8n-files'; if (fs.existsSync('/home/node') || fs.existsSync(n8nFilesDir)) { return n8nFilesDir; } return os.tmpdir(); }; const prepareExecutionPaths = (itemIndex) => { const baseDir = getOutputBaseDir(); const executionId = this.getExecutionId(); const nodeName = this.getNode().name.replace(/\s+/g, '_'); const logPath = path.join(baseDir, 'n8n_robot_logs', executionId, nodeName, `run_${itemIndex + 1}`); if (!fs.existsSync(logPath)) { fs.mkdirSync(logPath, { recursive: true }); } const robotFilePath = path.join(logPath, 'script.robot'); return { logPath, robotFilePath }; }; const runRobotTests = async (logPath, robotFilePath) => { let terminalOutput = ''; let errorOccurred = false; try { const { stdout } = await execAsync(`robot -d ${logPath} --output output.json ${robotFilePath}`); terminalOutput = stdout; } catch (error) { terminalOutput = error.stdout || error.stderr || 'Execution error with no output'; errorOccurred = true; } terminalOutput = stripOutputPart(terminalOutput); return { terminalOutput, errorOccurred }; }; const extractVariablesFromOutput = (outputJsonPath) => { const outputJson = JSON.parse(fs.readFileSync(outputJsonPath, 'utf8')); return extractVariables(outputJson); }; const collectAttachments = (logPath, options) => { const outputFiles = [ { name: 'output.json', path: path.join(logPath, 'output.json'), include: options.outputJson }, { name: 'log.html', path: path.join(logPath, 'log.html'), include: options.logHtml }, { name: 'report.html', path: path.join(logPath, 'report.html'), include: options.reportHtml }, ]; const attachments = {}; addAttachments(outputFiles, attachments); return attachments; }; n8n_workflow_1.LoggerProxy.debug('Entry Point'); const items = this.getInputData(); const results = []; for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { const robotScript = `*** Settings ***\n\n${this.getNodeParameter('robotScript', itemIndex, '')}`; const includeOutputJson = this.getNodeParameter('includeOutputJson', itemIndex, false); const includeLogHtml = this.getNodeParameter('includeLogHtml', itemIndex, false); const includeReportHtml = this.getNodeParameter('includeReportHtml', itemIndex, false); const includeOtherFields = this.getNodeParameter('includeOtherFields', itemIndex, false); const { logPath, robotFilePath } = prepareExecutionPaths(itemIndex); fs.writeFileSync(robotFilePath, robotScript); const { terminalOutput, errorOccurred } = await runRobotTests(logPath, robotFilePath); const outputJsonPath = path.join(logPath, 'output.json'); if (!fs.existsSync(outputJsonPath)) { throw new n8n_workflow_2.NodeOperationError(this.getNode(), terminalOutput); } const variables = extractVariablesFromOutput(outputJsonPath); const transformedVariables = transformVariables(variables); const attachments = collectAttachments(logPath, { outputJson: includeOutputJson, logHtml: includeLogHtml, reportHtml: includeReportHtml, }); let outputItem = { json: errorOccurred ? { error: { terminal_output: terminalOutput, ...transformedVariables } } : { terminal_output: terminalOutput, ...transformedVariables }, binary: attachments, }; if (includeOtherFields) { outputItem.json = { ...items[itemIndex].json, ...outputItem.json, }; } if (errorOccurred && !this.continueOnFail()) { throw new n8n_workflow_2.NodeOperationError(this.getNode(), terminalOutput); } results.push(outputItem); } return [results]; } } exports.RobotFramework = RobotFramework; //# sourceMappingURL=RobotFramework.node.js.map