UNPKG

protractor-errors

Version:

A protractor runner that allows you to run errors from previous runs.

231 lines (190 loc) 7.69 kB
const xml2js = require('xml2js'); const fs = require('fs'); const path = require('path'); const m = require('./memory'); const xmlArray = it => (Array.isArray(it) ? it : [it]).filter(Boolean); /** * * The amend function merges the original run data into the new (errors) run, so that * any tests that were not run in the errors run will not be dropped from future runs. * @example Report 1: 350 tests / 50 errors Report 2: 48 tests / 10 errors Report 3: 9 tests / 1 error Normally this would be a case of losing information about 3 tests having been left out of their respective incremental errors runs. With "amend", the entire body of the `origin` run is carried over to your current `errors` run, amended only by the actual tests that ran. It would potentially look like this: Report 1: 350 tests / 50 errors (350 run) Report 2: 350 tests / 12 errors (48 run) Report 3: 350 tests / 4 errors (9 run) * * The 3 errors that were previously being dropped out are preserved so you * can continue running those errors. * */ module.exports = function () { if (!browser || !browser.params || !jasmine || !browser.params.currentTime) { console.error('Missing browser.params required for protractor-errors amend.'); return; } const directoryName = browser.params.errorsTag ? browser.params.currentTime + '_' + browser.params.errorsTag : browser.params.currentTime; if (String(browser.params.errorsRun).toLowerCase() !== 'true') { return; } try { return amend(browser.params.errorsPath, directoryName, browser.params.errorsTag); } catch (e) { console.log(e); } }; async function amend(reportPathName, ignoreFile, tag) { const reportsFilePath = path.join(process.cwd(), reportPathName); // STEP 1 - READ THROUGH THE DIRECTORIES AND DETERMINE THE MOST RECENT 2 DIRECTORIES const directories = fs.readdirSync(reportsFilePath).sort((a, b) => { const [left, right] = [a, b].map(f => { const fullFilePath = path.join(reportsFilePath, f), fileStats = fs.statSync(fullFilePath); if (fileStats.isFile()) { return 0; } return fileStats.birthtime.getTime(); }); return left - right; }); const [mostRecentDirectory, secondMostRecentDirectory] = [ path.join(reportsFilePath, directories.pop()), m.errorsRunOriginDirectory ]; if (!mostRecentDirectory || !secondMostRecentDirectory || (mostRecentDirectory === secondMostRecentDirectory)) { const message = 'At least two protractor runs are needed.'; console.log(message); throw new Error(message); } // STEP 2 - Combine run 1 and run 2 information to rewrite run 2's report. try { const run1 = readXmlFiles(fs.readdirSync(secondMostRecentDirectory).map(file => { return path.join(secondMostRecentDirectory, file); }), secondMostRecentDirectory); const run2 = readXmlFiles(fs.readdirSync(mostRecentDirectory).map(file => { return path.join(mostRecentDirectory, file); }), mostRecentDirectory); await amendWrite(run1, run2, secondMostRecentDirectory, mostRecentDirectory); } catch (e) { console.error(e); } } function readXmlFiles(fullFilePaths, directoryName) { const output = {}; fullFilePaths.forEach(function (fullFilePath) { const relativeFilePath = fullFilePath.slice(fullFilePath.indexOf(directoryName) + directoryName.length + 1); output[relativeFilePath] = { fullFilePath, contents: readXmlFile(fullFilePath) } }); return output; } /** * @returns {Promise} */ function readXmlFile(fullFilePath) { const fileContents = fs.readFileSync(fullFilePath); return parseXmlFileContents(fileContents); } /** * @returns {Promise} */ function parseXmlFileContents(fileContents) { return new Promise((resolve, reject) => { xml2js.parseString(fileContents, function (err, result) { if (err) { console.log("Failed to parse xml file contents"); return reject(err); } return resolve(result); }); }) } /** * @param run1 - key is suite filename, common between runs, value is an object with contents field (parsed xml object). * @param run2 - same structure as run1 but different absolute path. * @param previousRunDirectory * @param latestRunDirectory * * Run 2 is the one that just finished. */ async function amendWrite(run1, run2, previousRunDirectory, latestRunDirectory) { for (const key in run1) { const suiteA = run1[key]; const suiteB = run2[key]; if (!suiteB || !suiteB.contents) { // nothing to amend continue; } const testSuiteArrayA = xmlArray((await suiteA.contents).testsuites.testsuite); const testSuiteArrayB = xmlArray((await suiteB.contents).testsuites.testsuite); const getSuite = testcase => testcase.$.classname.trim().toLowerCase().replace(' .',' '); const getSpec = testcase => testcase.$.name.trim().toLowerCase(); const findCase = (suite, spec, inGroup = testSuiteArrayB) => { var matchedSuite; for (const haystackSuite of xmlArray(inGroup)) { for (const testcase of xmlArray(haystackSuite.testcase)) { const _suite = getSuite(testcase); const _spec = getSpec(testcase); if (suite === _suite) { matchedSuite = suite; if (spec === _spec) { return [testcase, haystackSuite] } } } } return [undefined, matchedSuite]; }; // merge A into B, preferring values from B (newer). // in memory only. The write operation will be to B. for (const suiteObject of testSuiteArrayA || []) { const it = suiteObject.testcase || []; for (var i = 0; i < it.length; ++i) { const testcase = it[i]; const suite = getSuite(testcase); const spec = getSpec(testcase); const [counterpart] = findCase(suite, spec); if (!testcase.failure) { // Only amending existing failures. continue; } if (!counterpart) { // nothing to amend. continue; } if (counterpart.failure) { // failed again, overwrite this case's failure. testcase.failure = counterpart.failure; continue; } // the test passed in the errors run if (counterpart.skipped) { if (!testcase.skipped) { suiteObject.$.skipped += 1; } continue; } it[i] = counterpart; suiteObject.$.failures -= 1; } } } for (const xmlSuiteFileName in run1) { if (!run1.hasOwnProperty(xmlSuiteFileName)) { continue; } const fileObject = run1[xmlSuiteFileName]; if (!fileObject) { return; } const builder = new xml2js.Builder(); const xml = builder.buildObject(await fileObject.contents); const targetFile = fileObject.fullFilePath.replace(previousRunDirectory, latestRunDirectory); fs.writeFileSync(targetFile, xml); } return 0; }