From ab758f193b5d49daf2ce1a7aef1f2a681b6d1290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Grabovsk=C3=BD?= Date: Mon, 16 Jan 2023 13:57:37 +0100 Subject: [PATCH] WIP: Migrate to fast-xml-parser The `xml2js-parser` package hasn't been updated in 6 years, so it's time to move to a more up-to-date alternative. `fast-xml-parser` looks fairly decent, has TypeScript declarations built-in and will allow us to debug and iterate faster on changes in the future. Closes #10 --- package-lock.json | 58 +++++++----- package.json | 4 +- src/components/TestSuites.tsx | 45 +++++---- src/testsuite.test.ts | 125 +++++++++++-------------- src/testsuite.ts | 44 ++++----- src/utils/xunitParser.ts | 167 ++++++++++++++++++++++++---------- 6 files changed, 254 insertions(+), 189 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b7a635d..c222e271 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "axios": "^1.3.4", "buffer": "^6.0.3", "classnames": "^2.3.1", + "fast-xml-parser": "^4.0.13", "graphql": "^16.6.0", "immutability-helper": "^3.1.1", "linkify-react": "^4.1.0", @@ -43,8 +44,7 @@ "typescript": "^4.9.5", "uuid": "^9.0.0", "validator": "^13.9.0", - "web-vitals": "^3.1.1", - "xml2js-parser": "^1.1.1" + "web-vitals": "^3.1.1" }, "devDependencies": { "@types/linkifyjs": "^2.1.4", @@ -8184,6 +8184,22 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-xml-parser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.3.tgz", + "integrity": "sha512-LsNDahCiCcJPe8NO7HijcnukHB24tKbfDDA5IILx9dmW3Frb52lhbeX6MPNUSvyGNfav2VTYpJ/OqkRoVLrh2Q==", + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -15830,6 +15846,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, "node_modules/style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -17851,17 +17873,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, - "node_modules/xml2js-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/xml2js-parser/-/xml2js-parser-1.1.1.tgz", - "integrity": "sha512-HHrJvJt8HxMFj6abJqOqaLqByxwoF42B/tZB5D97sUmgWTg+4ZjcUzdvrd2zr4Nwhevnvegz5k8Leom9CG3yAg==", - "dependencies": { - "sax": "^1.2.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -23856,6 +23867,14 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-xml-parser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.3.tgz", + "integrity": "sha512-LsNDahCiCcJPe8NO7HijcnukHB24tKbfDDA5IILx9dmW3Frb52lhbeX6MPNUSvyGNfav2VTYpJ/OqkRoVLrh2Q==", + "requires": { + "strnum": "^1.0.5" + } + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -29356,6 +29375,11 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -30904,14 +30928,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, - "xml2js-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/xml2js-parser/-/xml2js-parser-1.1.1.tgz", - "integrity": "sha512-HHrJvJt8HxMFj6abJqOqaLqByxwoF42B/tZB5D97sUmgWTg+4ZjcUzdvrd2zr4Nwhevnvegz5k8Leom9CG3yAg==", - "requires": { - "sax": "^1.2.1" - } - }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", diff --git a/package.json b/package.json index 6b127f76..a30c82fc 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "axios": "^1.3.4", "buffer": "^6.0.3", "classnames": "^2.3.1", + "fast-xml-parser": "^4.0.13", "graphql": "^16.6.0", "immutability-helper": "^3.1.1", "linkify-react": "^4.1.0", @@ -42,8 +43,7 @@ "typescript": "^4.9.5", "uuid": "^9.0.0", "validator": "^13.9.0", - "web-vitals": "^3.1.1", - "xml2js-parser": "^1.1.1" + "web-vitals": "^3.1.1" }, "scripts": { "start": "react-scripts start", diff --git a/src/components/TestSuites.tsx b/src/components/TestSuites.tsx index 142cd098..9e38be30 100644 --- a/src/components/TestSuites.tsx +++ b/src/components/TestSuites.tsx @@ -104,45 +104,50 @@ const TestSuitesInternal: React.FC = (props) => { }; const mkLogLink = (log: TestCaseLogsEntry): JSX.Element => ( - - {log.$.name} + + {log.$name} ); interface LogsLinksProps { - logs: TestCaseLogs[]; + logs?: TestCaseLogsEntry | TestCaseLogsEntry[]; } const LogsLinks: React.FC = ({ logs }) => { - if (!logs || _.isEmpty(logs[0].log)) return null; + if (!logs || _.isEmpty(logs)) return null; + if (!_.isArray(logs)) logs = [logs]; const logsLinks = mkSeparatedList(mkLogsLinks(logs)); return

Log files: {logsLinks}

; }; -const mkLogsLinks = (logs: TestCaseLogs[]): JSX.Element[] => { - /** logs[0].log - [log1, log2, log3] */ - if (!logs[0] || _.isEmpty(logs[0].log)) return []; - return _.map(logs[0].log, (log) => mkLogLink(log)); +const mkLogsLinks = (logs: TestCaseLogsEntry[] | undefined): JSX.Element[] => { + if (!logs || _.isEmpty(logs)) return []; + return _.map(logs, (log) => mkLogLink(log)); }; const mkPhase = (phase: TestCasePhasesEntry): IRow => { + const logs = _.isArray(phase.logs?.log) + ? phase.logs?.log + : phase.logs?.log + ? [phase.logs?.log] + : []; return { cells: [ - , - phase.$.name, - mkSeparatedList(mkLogsLinks(phase.logs)), + , + phase.$name, + mkSeparatedList(mkLogsLinks(logs)), ], - key: phase.$.name, + key: phase.$name, }; }; interface PhasesProps { - phases?: TestCasePhases[]; + phases?: TestCasePhasesEntry[]; } const Phases: React.FC = ({ phases }) => { - if (!phases || _.isEmpty(phases[0]?.phase)) return null; - const rows: IRow[] = _.map(phases[0].phase, (phase) => mkPhase(phase)); + if (_.isEmpty(phases)) return null; + const rows: IRow[] = _.map(phases, (phase) => mkPhase(phase)); return ( = ({ phases }) => { const mkTestOutput = (output: TestCaseTestOutputsEntry): IRow => ({ cells: [ - <>{output.$.result}, - <>{output.$.message}, - <>{output.$.remedy}, + <>{output.$result}, + <>{output.$message}, + <>{output.$remedy}, ], }); @@ -208,7 +213,7 @@ const TestCaseContent: React.FC = (props) => { return ( <> - + ); }; @@ -285,7 +290,7 @@ const TestCaseItem: React.FC = (props) => { )} - + ); diff --git a/src/testsuite.test.ts b/src/testsuite.test.ts index d0554830..085c8c67 100644 --- a/src/testsuite.test.ts +++ b/src/testsuite.test.ts @@ -26,9 +26,9 @@ describe('getProperty function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [], + phases: undefined, message: '', // properties: undefined, // Not present in the input. 'test-outputs': [], @@ -41,11 +41,11 @@ describe('getProperty function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [], + phases: undefined, message: '', - properties: [], + properties: undefined, 'test-outputs': [], }; expect(getProperty(testCase, 'ci.arch')).toBeUndefined(); @@ -56,18 +56,16 @@ describe('getProperty function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [], + phases: undefined, message: '', - properties: [ - { - property: [ - { $: { name: 'ci.name', value: 'Fedora CI' } }, - { $: { name: 'ci.epoch', value: '2022' } }, - ], - }, - ], + properties: { + property: [ + { $name: 'ci.name', $value: 'Fedora CI' }, + { $name: 'ci.epoch', $value: '2022' }, + ], + }, 'test-outputs': [], }; expect(getProperty(testCase, 'ci.arch')).toBeUndefined(); @@ -78,19 +76,17 @@ describe('getProperty function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [], + phases: undefined, message: '', - properties: [ - { - property: [ - { $: { name: 'ci.name', value: 'Fedora CI' } }, - { $: { name: 'ci.epoch', value: '2022' } }, - { $: { name: 'ci.arch', value: 'aarch64' } }, - ], - }, - ], + properties: { + property: [ + { $name: 'ci.name', $value: 'Fedora CI' }, + { $name: 'ci.epoch', $value: '2022' }, + { $name: 'ci.arch', $value: 'aarch64' }, + ], + }, 'test-outputs': [], }; expect(getProperty(testCase, 'ci.arch')).toStrictEqual('aarch64'); @@ -101,25 +97,19 @@ describe('getProperty function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [], + phases: undefined, message: '', - properties: [ - { - property: [ - { $: { name: 'ci.name', value: 'Fedora CI' } }, - { $: { name: 'ci.epoch', value: '2022' } }, - { $: { name: 'ci.arch', value: 'aarch64' } }, - ], - }, - { - property: [ - { $: { name: 'ci.name', value: 'Second CI' } }, - { $: { name: 'ci.arch', value: 's390x' } }, - ], - }, - ], + properties: { + property: [ + { $name: 'ci.name', $value: 'Fedora CI' }, + { $name: 'ci.epoch', $value: '2022' }, + { $name: 'ci.arch', $value: 'aarch64' }, + { $name: 'ci.name', $value: 'Second CI' }, + { $name: 'ci.arch', $value: 's390x' }, + ], + }, 'test-outputs': [], }; expect(getProperty(testCase, 'ci.arch')).toStrictEqual('aarch64'); @@ -132,7 +122,7 @@ describe('hasTestCaseContet function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', message: '', }; @@ -144,9 +134,9 @@ describe('hasTestCaseContet function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [], + phases: undefined, message: '', 'test-outputs': [], }; @@ -158,15 +148,12 @@ describe('hasTestCaseContet function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [ - { - phase: [ - { $: { name: 'prepare', result: 'pass' }, logs: [] }, - ], - }, - ], + phases: { + phase: [{ $name: 'prepare', $result: 'pass', logs: undefined }], + }, + message: '', 'test-outputs': [], }; @@ -178,18 +165,16 @@ describe('hasTestCaseContet function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', message: '', 'test-outputs': [ { 'test-output': [ { - $: { - message: 'Test case OK', - remedy: '', - result: 'pass', - }, + $message: 'Test case OK', + $remedy: '', + $result: 'pass', }, ], }, @@ -203,25 +188,19 @@ describe('hasTestCaseContet function', () => { _uuid: 'd50ce531-01f0-4e8f-b398-ee1bdb4be223', name: '/test/basic', time: '1658312226', - logs: [], + logs: undefined, status: 'pass', - phases: [ - { - phase: [ - { $: { name: 'prepare', result: 'pass' }, logs: [] }, - ], - }, - ], + phases: { + phase: [{ $name: 'prepare', $result: 'pass', logs: undefined }], + }, message: '', 'test-outputs': [ { 'test-output': [ { - $: { - message: 'Test case OK', - remedy: '', - result: 'pass', - }, + $message: 'Test case OK', + $remedy: '', + $result: 'pass', }, ], }, diff --git a/src/testsuite.ts b/src/testsuite.ts index 92636230..e5d45ec0 100644 --- a/src/testsuite.ts +++ b/src/testsuite.ts @@ -23,16 +23,18 @@ import _ from 'lodash'; export type TestCaseStatus = 'error' | 'fail' | 'pass' | 'skip'; export interface TestCaseLogsEntry { - $: { name: string; href: string }; + $name: string; + $href: string; } export interface TestCaseLogs { - log: TestCaseLogsEntry[]; + log: TestCaseLogsEntry | TestCaseLogsEntry[]; } export interface TestCasePhasesEntry { - $: { name: string; result: string }; - logs: TestCaseLogs[]; + $name: string; + $result: string; + logs?: TestCaseLogs; } export interface TestCasePhases { @@ -40,7 +42,8 @@ export interface TestCasePhases { } export interface TestCasePropertiesEntry { - $: { name: string; value: string }; + $name: string; + $value: string; } export interface TestCaseProperties { @@ -48,7 +51,9 @@ export interface TestCaseProperties { } export interface TestCaseTestOutputsEntry { - $: { message: string; remedy: string; result: string }; + $message: string; + $remedy: string; + $result: string; } export interface TestCaseTestOutputs { @@ -64,18 +69,19 @@ export interface TestCase { _uuid: string; name: string; time: string; - logs: TestCaseLogs[]; + logs?: TestCaseLogs; status: TestCaseStatus; - phases?: TestCasePhases[]; + phases?: TestCasePhases; message: string; - properties?: TestCaseProperties[]; + properties?: Partial>; 'test-outputs'?: TestCaseTestOutputs[]; } export type TestSuiteStatus = 'error' | 'fail' | 'pass' | 'skip' | 'tests'; export interface TestSuitePropertiesEntry { - $: { name: string; value: string }; + $name: string; + $value: string; } export interface TestSuiteProperties { @@ -91,24 +97,14 @@ export interface TestSuite { time?: string; tests: TestCase[]; status: string; - properties: TestSuiteProperties[]; + properties: TestSuiteProperties; /** Number of test cases with each of the possible statuses. */ count: Record; } -export const getProperty = (testCase: TestCase, propertyName: string) => { - const matchedProperty = testCase.properties - ?.flatMap((property) => property.property) - /* - * Make sure the properties are in the expected format. - * TODO: This is already assumed in the type. It shoud be ensured by - * validation earlier in the data flow. - */ - .filter((property) => _.has(property, '$.name')) - .find((property) => property.$.name === propertyName); - return matchedProperty?.$.value; -}; +export const getProperty = (testCase: TestCase, propertyName: string) => + _.get(testCase.properties, propertyName); export const hasTestCaseContent = (testCase: TestCase) => - !_.isEmpty(testCase.phases?.[0]?.phase) || + !_.isEmpty(testCase.phases?.phase?.[0]) || !_.isEmpty(testCase['test-outputs']); diff --git a/src/utils/xunitParser.ts b/src/utils/xunitParser.ts index 9422117a..285c9fb4 100644 --- a/src/utils/xunitParser.ts +++ b/src/utils/xunitParser.ts @@ -19,10 +19,57 @@ */ import * as uuid from 'uuid'; -const parseString = require('xml2js-parser').parseStringSync; +import { X2jOptions, XMLParser } from 'fast-xml-parser'; +import _ from 'lodash'; -const xml2js = (xml: string) => { - const data = parseString(xml); +// IDEAL TYPES +type DetailedTestResults = TestSuiteResult[]; + +interface TestSuiteResult { + cases: TestCaseResult[]; + /* + * Are there logs at the testsuite level? Yes, but only as an attribute of + * . + */ + log?: TestResultLog; + message?: string; + properties: TestProperties; +} + +interface TestCaseResult { + logs?: TestResultLog[]; + name: string; + phases: TestCasePhase[]; + result?: TestCaseOutcome; + time?: number; +} + +interface TestCasePhase { + logs: TestResultLog[]; // > logs > log + name: string; + result?: TestPhaseOutcome; + time?: number; +} + +interface TestResultLog { + label: string; // $name + link: string; // $href +} + +type TestCaseOutcome = 'error' | 'fail' | 'pass' | 'skip'; +type TestPhaseOutcome = Exclude; +type TestProperties = Partial>; +// END IDEAL TYPES + +const XML_PARSER_OPTIONS: Partial = { + alwaysCreateTextNode: true, + attributeNamePrefix: '$', + ignoreAttributes: false, +}; + +const parseSuitesXml = (xml: string) => { + const parser = new XMLParser(XML_PARSER_OPTIONS); + const data = parser.parse(xml); let suites = []; if (data.testsuites && data.testsuites.testsuite) { @@ -64,62 +111,82 @@ const expandMeta = (thing: any) => { }; const buildProperties = (suite: any) => { - const properties: any = {}; - if (suite.properties) { - suite.properties - .filter((property: any) => typeof property !== 'string') - .forEach((property: any) => { - property.property.forEach((prop: any) => { - const meta = prop.$; - properties[meta.name] = meta.value; - }); + const properties = suite?.properties?.property; + const propsMap: any = {}; + + if (properties) { + if (_.isArray(properties)) { + properties.forEach((prop: any) => { + /* + * NOTE: This overwrites previous values with subsequent ones. + * For example, given the xunit + * ... + * + * + * ... + * the `osci.result` property will have the value 'passed'. + */ + propsMap[prop.$name] = prop.$value; }); + } else if ( + _.isObject(properties) && + '$name' in properties && + '$value' in properties + ) { + const prop = properties; + propsMap[prop.$name as string] = prop.$value; + } else { + console.warn('Invalid in xunit'); + } } - properties._uuid = uuid.v4(); - return properties; + + propsMap._uuid = uuid.v4(); + return propsMap; }; -const extactMessage = (thing: any) => { - if (typeof thing === 'string') return; +const extractMessage = (thing: any) => { + if (!thing || _.isString(thing)) return; thing.message = ''; - if (thing._) { - thing.message = thing._; - delete thing._; + if (thing['#text']) { + thing.message = thing['#text']; + delete thing['#text']; } }; -const extractTestCore = (test: any, type: any, status: any) => { - if (test[type]) { - test.status = status; - - const core = test[type][0]; - extactMessage(core); - - if (test.message === '') { - if (core.message) { - test.message = core.message; - } else if (core.$) { - test.message = ''; - if (core.$.message) test.message += core.$.message; - if (core.$.type) test.message += core.$.type; - } else if (typeof core === 'string') { - test.message = core; - } - } +const extractTestCore = (test: any, type: string, status: string) => { + if (!test[type]) return; + + test.status = status; - if (test.message) test.message = escape(test.message); + const core = test[type]; + extractMessage(core); - delete test[type]; + if (test.message === '') { + if (_.isString(core)) { + test.message = core; + } else if (core.message) { + test.message = core.message; + } else if (core.$message || core.$type) { + test.message = ''; + if (core.$message) test.message += core.$message; + if (core.$type) test.message += core.$type; + } } + + if (test.message) test.message = escape(test.message); + + delete test[type]; }; const buildTest = (test: any) => { - test.status = 'pass'; - test.name = 'no name'; + test.status = test.$status || 'pass'; + test.name = test.$name || 'no name'; + + // expandMeta(test); - expandMeta(test); + extractMessage(test); - extactMessage(test); + test.properties = buildProperties(test); extractTestCore(test, 'passed', 'pass'); extractTestCore(test, 'passing', 'pass'); @@ -149,13 +216,15 @@ const buildTest = (test: any) => { }; const buildTests = (suite: any) => { - suite.tests = suite.testcase + let testcases = suite.testcase; + if (!_.isArray(testcases)) testcases = [testcases]; + suite.tests = testcases .filter((test: any) => { - if (typeof test === 'string') return test.trim() !== ''; + if (_.isString(test)) return test.trim() !== ''; return true; }) .map((test: any) => { - if (typeof test === 'string') return buildTest({ _: test }); + if (_.isString(test)) return buildTest({ '#text': test }); return buildTest(test); }); delete suite.testcase; @@ -164,11 +233,11 @@ const buildTests = (suite: any) => { const buildSuites = (suites: any[]) => suites .filter((suite: any) => { - if (typeof suite === 'string') return suite.trim() !== ''; + if (_.isString(suite)) return suite.trim() !== ''; return true; }) .map((suite: any) => { - expandMeta(suite); + // expandMeta(suite); suite.properties = buildProperties(suite); delete suite.tests; @@ -236,6 +305,6 @@ const buildSuites = (suites: any[]) => }); export const xunitParser = (xml: string) => { - const suites = xml2js(xml); + const suites = parseSuitesXml(xml); return buildSuites(suites); };