SuitestAutomation/suitest-js-api

View on GitHub
lib/utils/socketErrorMessages.js

Summary

Maintainability
A
45 mins
Test Coverage
A
91%
/* eslint-disable max-len */
const {EOL} = require('os');
const {path, has} = require('ramda');

const t = require('../texts');
const chainUtils = require('./chainUtils');
const {stripAnsiChars} = require('./stringUtils');
const {getFirstStackLine} = require('./stackTraceParser');

/** Description of the function
 * @name ToStringFunc
 * @function
 * @param {Object} jsonMessage can be generated socket message or raw lined definition json
 * @param {boolean=} [nameOnly]
 * @returns {string}
 */

/** Error map handler
 * @name ErrorMapHandler
 * @function
 * @param {Object} data
 * @param {ToStringFunc} data.toString - chain toString function which accept socketMessage or json lined definition and return human readable representation
 * @param {Object} data.response - websocket message
 * @param {Object} data.jsonMessage - socket message or raw test command json definition
 * @returns {string}
 */

const responseMessageCode = response => path(['message', 'code'], response);
const responseMessageInfo = response => path(['message', 'info'], response);
const jsExpressionErrorType = 'jsExpressionError';
const testSnippetError = 'testSnippetError';

/**
 * Get snippet lines output recursively
 * @param {Object} data
 * @param {string} data.testId - id of root test
 * @param {Object} data.definitions - test definitions by id
 * @param {Array} data.results - results for current runSnippet line
 * @param {number} data.level - level for indentation, initially 1
 * @param {string} data.verbosity - string verbosity level
 * @param {function} translate - added as function argument for testing
 */
const getSnippetLogs = ({testId, definitions, results, level, verbosity}, translate = chainUtils.translateLineResult) => {
    const indent = ' '.repeat(level);
    // Indent all text by indent constant
    const applyIndent = (str) => str.split('\n').map(l => indent + l).join('\n');
    // array of definition without comments to get right def based on lineId in results
    const resultLines = definitions[testId].filter(l => l.type !== 'comment');
    // Array of lines results with number of line in def without comments
    const linesNumbersWithResults = results.map(res => ({lineNumber: res.lineId.split('-').map(Number)[level] - 1, result: res}));
    // array of lines that have result
    const linesWithResults = linesNumbersWithResults.map(({lineNumber, result}) => ({def: resultLines[lineNumber], result}));
    // creating a list of lines and their results (if it hase it)
    const definitionsWithResults = definitions[testId].map(def => {
        if (def.type === 'openApp') {
            return;
        }
        // looking for lines in lines with results array
        const resultLine = linesWithResults.find(l => l.def === def);

        if (def.type === 'comment') {
            return def.val ? {def} : undefined;
        } else if (resultLine) {
            return resultLine;
        }

        // lines that does not have result
        return {def};
    }).filter(Boolean);

    return definitionsWithResults.reduce((logs, {def, result}) => {
        logs.push(applyIndent(translate(def, verbosity, result)));

        if (result && (result.results || result.loopResults)) {
            const getLoopLogs = loopRes => getSnippetLogs({
                testId: def.val,
                definitions,
                results: loopRes,
                level: level + 1,
                verbosity,
            }, translate);

            if (result.loopResults) {
                result.loopResults.forEach((loop, idx) => {
                    logs.push(indent + `- loop count: ${idx + 1}`);
                    logs.push(getLoopLogs(loop.results));
                });
            } else {
                logs.push(getLoopLogs(result.results));
            }
        }

        return logs;
    }, []).filter(Boolean).join('\n');
};

const commandErrors = {
    'notSupportedPlatform': () => t['commandError.notSupportedPlatform'](),
    'timeout': () => t['commandError.timeout'](),
    'generalError': () => t['commandError.generalError'](),
    'notSupportedDriver': () => t['commandError.notSupportedDriver'](),
    'screenshotFailed': () => t['commandError.screenshotFailed'](),
    'notSupportedConfiguration': () => t['commandError.notSupportedConfiguration'](),
};

/**
 * @description check that response error is related to "command" (not "line") execution.
 * For now it is related only for taking screenshots
 * @param response
 */
function isCommandError(response) {
    return response.result === 'error' && has(response.errorType, commandErrors);
}

/**
 * @description Create human readable error message from suitest error response
 * @param verbosity
 * @param {*} response websocket message
 * @param {Object} jsonMessage
 * @param {Array} snippets
 * @returns {string}
 */
function getErrorMessage({response, jsonMessage, verbosity, snippets}) {
    if (response.results && snippets) {
        return getSnippetLogs(
            {verbosity, definitions: snippets, results: response.results, testId: jsonMessage.request.val, level: 1},
        );
    }

    if (isCommandError(response)) {
        return commandErrors[response.errorType]();
    }

    return chainUtils.translateLineResult(jsonMessage, verbosity, response);
}

/**
 * @description Normalize errorType
 * @param {*} response webscoket message
 * @returns {string}
 */
function normalizeErrorType(response) {
    if (response.executeThrowException || response.matchJSThrowException) {
        return jsExpressionErrorType;
    }
    if (response.results) {
        return testSnippetError;
    }

    return response.errorType || response.executionError;
}

module.exports = {
    getErrorMessage,
    /**
     * @description Create human readable error message for real time info logs
     * @param {string} message
     * @param {string} prefix
     * @param {Object} res
     * @param {string} [stack]
     * @returns {string}
     */
    getInfoErrorMessage: (message, prefix = '', res, stack) => {
        const msg = prefix + stripAnsiChars(message);
        const firstStackLine = stack && getFirstStackLine(stack);
        const nl = firstStackLine && msg.endsWith(EOL) ? '' : EOL;

        return msg + nl + firstStackLine;
    },
    responseMessageCode,
    responseMessageInfo,
    normalizeErrorType,
    getSnippetLogs,
};