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
JavaScript
;
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