@wdio/junit-reporter 9.0.0-alpha.78 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -120,7 +120,7 @@ All test case failures are mapped as JUnit test case errors. A failed test case
120
120
 
121
121
  ```xml
122
122
  <testcase classname="chrome.a_test_case" name="a_test_suite_a_test_case" time="0.372">
123
- <error message="Error: some error"/>
123
+ <failure message="Error: some error"/>
124
124
  <system-err>
125
125
  <![CDATA[
126
126
  Error: some assertion failure
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxE,OAAO,YAAY,MAAM,gBAAgB,CAAA;AAIzC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAOtD;;;;;GAKG;AACH,cAAM,aAAc,SAAQ,YAAY;IAQhB,OAAO,EAAE,oBAAoB;IAPjD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAQ;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAQ;IACjC,OAAO,CAAC,cAAc,CAAC,CAAQ;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAK;IAC5B,OAAO,CAAC,kBAAkB,CAAC,CAAK;gBAEZ,OAAO,EAAE,oBAAoB;IAOjD,WAAW,CAAE,SAAS,EAAE,SAAS;IAIjC,WAAW,CAAE,MAAM,EAAE,WAAW;IAKhC,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,4BAA4B;IA6EpC,OAAO,CAAC,kBAAkB;IA6E1B,OAAO,CAAC,cAAc;IAuCtB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,aAAa;CAMxB;AAED,eAAe,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAc,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxE,OAAO,YAAY,MAAM,gBAAgB,CAAA;AAGzC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAQtD;;;;;GAKG;AACH,cAAM,aAAc,SAAQ,YAAY;IAQhB,OAAO,EAAE,oBAAoB;IAPjD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAQ;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAQ;IACjC,OAAO,CAAC,cAAc,CAAC,CAAQ;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAK;IAC5B,OAAO,CAAC,kBAAkB,CAAC,CAAK;gBAEZ,OAAO,EAAE,oBAAoB;IAOjD,WAAW,CAAE,SAAS,EAAE,SAAS;IAIjC,WAAW,CAAE,MAAM,EAAE,WAAW;IAKhC,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,4BAA4B;IA4EpC,OAAO,CAAC,kBAAkB;IA4E1B,OAAO,CAAC,cAAc;IA0CtB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,aAAa;CAMxB;AAED,eAAe,aAAa,CAAA"}
package/build/index.js CHANGED
@@ -1,282 +1,295 @@
1
- import url from 'node:url';
2
- import junit from 'junit-report-builder';
3
- import WDIOReporter from '@wdio/reporter';
4
- import { limit } from './utils.js';
5
- const ansiRegex = new RegExp([
6
- '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
7
- '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
8
- ].join('|'), 'g');
9
- /**
10
- * Reporter that converts test results from a single instance/runner into an XML JUnit report. This class
11
- * uses junit-report-builder (https://github.com/davidparsson/junit-report-builder) to build report.The report
12
- * generated from this reporter should conform to the standard JUnit report schema
13
- * (https://github.com/junit-team/junit5/blob/master/platform-tests/src/test/resources/jenkins-junit.xsd).
14
- */
15
- class JunitReporter extends WDIOReporter {
16
- options;
17
- _suiteNameRegEx;
18
- _packageName;
19
- _suiteTitleLabel;
20
- _fileNameLabel;
21
- _activeFeature;
22
- _activeFeatureName;
23
- constructor(options) {
24
- super(options);
25
- this.options = options;
26
- this._suiteNameRegEx = this.options.suiteNameFormat instanceof RegExp
27
- ? this.options.suiteNameFormat
28
- : /[^a-zA-Z0-9@]+/; // Reason for ignoring @ is; reporters like wdio-report-portal will fetch the tags from testcase name given as @foo @bar
1
+ // src/index.ts
2
+ import url from "node:url";
3
+ import WDIOReporter from "@wdio/reporter";
4
+ import junit from "junit-report-builder";
5
+
6
+ // src/utils.ts
7
+ import stringify from "json-stringify-safe";
8
+ var OBJLENGTH = 10;
9
+ var ARRLENGTH = 10;
10
+ var STRINGLIMIT = 1e3;
11
+ var STRINGTRUNCATE = 200;
12
+ var limit = function(rawVal) {
13
+ if (!rawVal) {
14
+ return rawVal;
15
+ }
16
+ let val = JSON.parse(stringify(rawVal));
17
+ const type = Object.prototype.toString.call(val);
18
+ if (type === "[object String]") {
19
+ if (val.length > 100 && isBase64(val)) {
20
+ return `[base64] ${val.length} bytes`;
29
21
  }
30
- onTestRetry(testStats) {
31
- testStats.skip('Retry');
22
+ if (val.length > STRINGLIMIT) {
23
+ return val.substr(0, STRINGTRUNCATE) + ` ... (${val.length - STRINGTRUNCATE} more bytes)`;
32
24
  }
33
- onRunnerEnd(runner) {
34
- const xml = this._buildJunitXml(runner);
35
- this.write(xml);
25
+ return val;
26
+ } else if (type === "[object Array]") {
27
+ const length = val.length;
28
+ if (length > ARRLENGTH) {
29
+ val = val.slice(0, ARRLENGTH);
30
+ val.push(`(${length - ARRLENGTH} more items)`);
36
31
  }
37
- _prepareName(name = 'Skipped test') {
38
- return name.split(this._suiteNameRegEx).filter((item) => item && item.length).join(' ');
32
+ return val.map(limit);
33
+ } else if (type === "[object Object]") {
34
+ const keys = Object.keys(val);
35
+ const removed = [];
36
+ for (let i = 0, l = keys.length; i < l; i++) {
37
+ if (i < OBJLENGTH) {
38
+ val[keys[i]] = limit(val[keys[i]]);
39
+ } else {
40
+ delete val[keys[i]];
41
+ removed.push(keys[i]);
42
+ }
39
43
  }
40
- _addFailedHooks(suite) {
41
- /**
42
- * Add failed hooks to suite as tests.
43
- */
44
- const failedHooks = suite.hooks.filter(hook => hook.error && hook.title.match(/^"(before|after)( all| each)?" hook/));
45
- failedHooks.forEach(hook => {
46
- const { title, _duration, error, state } = hook;
47
- suite.tests.push({
48
- _duration,
49
- title,
50
- error,
51
- state: state,
52
- output: []
53
- });
54
- });
55
- return suite;
44
+ if (removed.length) {
45
+ val._ = keys.length - OBJLENGTH + " more keys: " + JSON.stringify(removed);
56
46
  }
57
- _addCucumberFeatureToBuilder(builder, runner, specFileName, suite) {
58
- const featureName = !this.options.suiteNameFormat || this.options.suiteNameFormat instanceof RegExp
59
- ? this._prepareName(suite.title)
60
- : this.options.suiteNameFormat({ name: this.options.suiteNameFormat.name, suite });
61
- const filePath = specFileName.replace(process.cwd(), '.');
62
- if (suite.type === 'feature') {
63
- const feature = builder.testSuite()
64
- .name(featureName)
65
- .timestamp(suite.start)
66
- .time(suite._duration / 1000)
67
- .property('specId', 0)
68
- .property(this._suiteTitleLabel, suite.title)
69
- .property('capabilities', runner.sanitizedCapabilities)
70
- .property(this._fileNameLabel, filePath);
71
- this._activeFeature = feature;
72
- this._activeFeatureName = featureName;
73
- }
74
- else if (this._activeFeature) {
75
- let scenario = suite;
76
- const testName = this._prepareName(suite.title);
77
- const classNameFormat = this.options.classNameFormat ? this.options.classNameFormat({ packageName: this._packageName, activeFeatureName: this._activeFeatureName }) : `${this._packageName}.${this._activeFeatureName}`;
78
- const testCase = this._activeFeature.testCase()
79
- .className(classNameFormat)
80
- .name(`${testName}`)
81
- .time(scenario._duration / 1000);
82
- if (this.options.addFileAttribute) {
83
- testCase.file(filePath);
84
- }
85
- scenario = this._addFailedHooks(scenario);
86
- let stepsOutput = '';
87
- let isFailing = false;
88
- for (const stepKey of Object.keys(scenario.tests)) { // tests are trested as steps in Cucumber
89
- if (stepKey === 'undefined') { // fix cucumber hooks crashing reporter
90
- continue;
91
- }
92
- let stepEmoji = '✅';
93
- const step = scenario.tests[stepKey];
94
- if (step.state === 'pending' || step.state === 'skipped') {
95
- if (!isFailing) {
96
- testCase.skipped();
97
- }
98
- stepEmoji = '⚠️';
99
- }
100
- else if (step.state === 'failed') {
101
- if (step.error) {
102
- if (this.options.errorOptions) {
103
- const errorOptions = this.options.errorOptions;
104
- for (const key of Object.keys(errorOptions)) {
105
- testCase[key](step.error
106
- ? step.error[errorOptions[key]]
107
- : null);
108
- }
109
- }
110
- else {
111
- // default
112
- testCase.error(step.error.message);
113
- }
114
- testCase.standardError(`\n${step.error.stack}\n`);
115
- }
116
- else {
117
- testCase.error();
118
- }
119
- testCase.failure();
120
- isFailing = true;
121
- stepEmoji = '❗';
122
- }
123
- const output = this._getStandardOutput(step);
124
- stepsOutput += output ? stepEmoji + ' ' + step.title : stepEmoji + ' ' + step.title + '\n' + output;
125
- }
126
- testCase.standardOutput(`\n${stepsOutput}\n`);
47
+ return val;
48
+ }
49
+ return val;
50
+ };
51
+ function isBase64(str) {
52
+ if (typeof str !== "string") {
53
+ throw new Error("Expected string but received invalid type.");
54
+ }
55
+ const len = str.length;
56
+ const notBase64 = /[^A-Z0-9+/=]/i;
57
+ if (!len || len % 4 !== 0 || notBase64.test(str)) {
58
+ return false;
59
+ }
60
+ const firstPaddingChar = str.indexOf("=");
61
+ return firstPaddingChar === -1 || firstPaddingChar === len - 1 || firstPaddingChar === len - 2 && str[len - 1] === "=";
62
+ }
63
+
64
+ // src/index.ts
65
+ var ansiRegex = new RegExp([
66
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
67
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
68
+ ].join("|"), "g");
69
+ var JunitReporter = class extends WDIOReporter {
70
+ constructor(options) {
71
+ super(options);
72
+ this.options = options;
73
+ this._suiteNameRegEx = this.options.suiteNameFormat instanceof RegExp ? this.options.suiteNameFormat : /[^a-zA-Z0-9@]+/;
74
+ }
75
+ _suiteNameRegEx;
76
+ _packageName;
77
+ _suiteTitleLabel;
78
+ _fileNameLabel;
79
+ _activeFeature;
80
+ _activeFeatureName;
81
+ onTestRetry(testStats) {
82
+ testStats.skip("Retry");
83
+ }
84
+ onRunnerEnd(runner) {
85
+ const xml = this._buildJunitXml(runner);
86
+ this.write(xml);
87
+ }
88
+ _prepareName(name = "Skipped test") {
89
+ return name.split(this._suiteNameRegEx).filter(
90
+ (item) => item && item.length
91
+ ).join(" ");
92
+ }
93
+ _addFailedHooks(suite) {
94
+ const failedHooks = suite.hooks.filter((hook) => hook.error && hook.title.match(/^"(before|after)( all| each)?" hook/));
95
+ failedHooks.forEach((hook) => {
96
+ const { title, _duration, error, state } = hook;
97
+ suite.tests.push({
98
+ _duration,
99
+ title,
100
+ error,
101
+ state,
102
+ output: []
103
+ });
104
+ });
105
+ return suite;
106
+ }
107
+ _addCucumberFeatureToBuilder(builder, runner, specFileName, suite) {
108
+ const featureName = !this.options.suiteNameFormat || this.options.suiteNameFormat instanceof RegExp ? this._prepareName(suite.title) : this.options.suiteNameFormat({ name: this.options.suiteNameFormat.name, suite });
109
+ const filePath = specFileName.replace(process.cwd(), ".");
110
+ if (suite.type === "feature") {
111
+ const feature = builder.testSuite().name(featureName).timestamp(suite.start).time(suite._duration / 1e3).property("specId", 0).property(this._suiteTitleLabel, suite.title).property("capabilities", runner.sanitizedCapabilities).property(this._fileNameLabel, filePath);
112
+ this._activeFeature = feature;
113
+ this._activeFeatureName = featureName;
114
+ } else if (this._activeFeature) {
115
+ let scenario = suite;
116
+ const testName = this._prepareName(suite.title);
117
+ const classNameFormat = this.options.classNameFormat ? this.options.classNameFormat({ packageName: this._packageName, activeFeatureName: this._activeFeatureName }) : `${this._packageName}.${this._activeFeatureName}`;
118
+ const testCase = this._activeFeature.testCase().className(classNameFormat).name(`${testName}`).time(scenario._duration / 1e3);
119
+ if (this.options.addFileAttribute) {
120
+ testCase.file(filePath);
121
+ }
122
+ scenario = this._addFailedHooks(scenario);
123
+ let stepsOutput = "";
124
+ let isFailing = false;
125
+ for (const stepKey of Object.keys(scenario.tests)) {
126
+ if (stepKey === "undefined") {
127
+ continue;
127
128
  }
128
- return builder;
129
- }
130
- _addSuiteToBuilder(builder, runner, specFileName, suite) {
131
- const filePath = specFileName.replace(process.cwd(), '.');
132
- const suiteName = !this.options.suiteNameFormat || this.options.suiteNameFormat instanceof RegExp
133
- ? this._prepareName(suite.title)
134
- : this.options.suiteNameFormat({ name: this.options.suiteNameFormat.name, suite });
135
- const testSuite = builder.testSuite()
136
- .name(suiteName)
137
- .timestamp(suite.start)
138
- .time(suite._duration / 1000)
139
- .property('specId', 0)
140
- .property(this._suiteTitleLabel, suite.title)
141
- .property('capabilities', runner.sanitizedCapabilities)
142
- .property(this._fileNameLabel, filePath);
143
- suite = this._addFailedHooks(suite);
144
- for (const testKey of Object.keys(suite.tests)) {
145
- if (testKey === 'undefined') { // fix cucumber hooks crashing reporter (INFO: we may not need this anymore)
146
- continue;
147
- }
148
- const test = suite.tests[testKey];
149
- const testName = this._prepareName(test.title);
150
- const classNameFormat = this.options.classNameFormat
151
- ? this.options.classNameFormat({ packageName: this._packageName, suite })
152
- : `${this._packageName}.${(suite.fullTitle || suite.title).replace(/\s/g, '_')}`;
153
- const testCase = testSuite.testCase()
154
- .className(classNameFormat)
155
- .name(testName)
156
- .time(test._duration / 1000);
157
- if (this.options.addFileAttribute) {
158
- testCase.file(filePath);
159
- }
160
- if (test.state === 'pending' || test.state === 'skipped') {
161
- testCase.skipped();
162
- if (test.error) {
163
- testCase.standardError(`\n${test.error.stack?.replace(ansiRegex, '')}\n`);
164
- }
165
- }
166
- else if (test.state === 'failed') {
167
- if (test.error) {
168
- if (test.error.message) {
169
- test.error.message = test.error.message.replace(ansiRegex, '');
170
- }
171
- if (this.options.errorOptions) {
172
- const errorOptions = this.options.errorOptions;
173
- for (const key of Object.keys(errorOptions)) {
174
- testCase[key](test.error[errorOptions[key]]);
175
- }
176
- }
177
- else {
178
- // default
179
- testCase.error(test.error.message);
180
- }
181
- testCase.standardError(`\n${test.error.stack?.replace(ansiRegex, '')}\n`);
182
- }
183
- else {
184
- testCase.error();
185
- }
186
- testCase.failure();
187
- }
188
- const output = this._getStandardOutput(test);
189
- if (output) {
190
- testCase.standardOutput(`\n${output}\n`);
129
+ let stepEmoji = "\u2705";
130
+ const step = scenario.tests[stepKey];
131
+ if (step.state === "pending" || step.state === "skipped") {
132
+ if (!isFailing) {
133
+ testCase.skipped();
134
+ }
135
+ stepEmoji = "\u26A0\uFE0F";
136
+ } else if (step.state === "failed") {
137
+ if (step.error) {
138
+ if (this.options.errorOptions) {
139
+ const errorOptions = this.options.errorOptions;
140
+ for (const key of Object.keys(errorOptions)) {
141
+ testCase[key](
142
+ step.error ? step.error[errorOptions[key]] : null
143
+ );
144
+ }
145
+ } else {
146
+ testCase.failure(step.error.message);
191
147
  }
148
+ testCase.standardError(`
149
+ ${step.error.stack}
150
+ `);
151
+ } else {
152
+ testCase.failure();
153
+ }
154
+ isFailing = true;
155
+ stepEmoji = "\u2757";
192
156
  }
193
- return builder;
157
+ const output = this._getStandardOutput(step);
158
+ stepsOutput += output ? stepEmoji + " " + step.title : stepEmoji + " " + step.title + "\n" + output;
159
+ }
160
+ testCase.standardOutput(`
161
+ ${stepsOutput}
162
+ `);
194
163
  }
195
- _buildJunitXml(runner) {
196
- const builder = junit.newBuilder();
197
- if (runner.config.hostname !== undefined && runner.config.hostname.indexOf('browserstack') > -1) {
198
- // NOTE: deviceUUID is used to build sanitizedCapabilities resulting in a ever-changing package name in runner.sanitizedCapabilities when running Android tests under Browserstack. (i.e. ht79v1a03938.android.9)
199
- // NOTE: platformVersion is used to build sanitizedCapabilities which can be incorrect and includes a minor version for iOS which is not guaranteed to be the same under Browserstack.
200
- const browserstackSanitizedCapabilities = [
201
- runner.capabilities.device,
202
- runner.capabilities.os,
203
- (runner.capabilities.os_version || '').replace(/\./g, '_'),
204
- ]
205
- .filter(Boolean)
206
- .map((capability) => capability.toLowerCase())
207
- .join('.')
208
- .replace(/ /g, '') || runner.sanitizedCapabilities;
209
- this._packageName = this.options.packageName || browserstackSanitizedCapabilities;
210
- }
211
- else {
212
- this._packageName = this.options.packageName || runner.sanitizedCapabilities;
164
+ return builder;
165
+ }
166
+ _addSuiteToBuilder(builder, runner, specFileName, suite) {
167
+ const filePath = specFileName.replace(process.cwd(), ".");
168
+ const suiteName = !this.options.suiteNameFormat || this.options.suiteNameFormat instanceof RegExp ? this._prepareName(suite.title) : this.options.suiteNameFormat({ name: this.options.suiteNameFormat.name, suite });
169
+ const testSuite = builder.testSuite().name(suiteName).timestamp(suite.start).time(suite._duration / 1e3).property("specId", 0).property(this._suiteTitleLabel, suite.title).property("capabilities", runner.sanitizedCapabilities).property(this._fileNameLabel, filePath);
170
+ suite = this._addFailedHooks(suite);
171
+ for (const testKey of Object.keys(suite.tests)) {
172
+ if (testKey === "undefined") {
173
+ continue;
174
+ }
175
+ const test = suite.tests[testKey];
176
+ const testName = this._prepareName(test.title);
177
+ const classNameFormat = this.options.classNameFormat ? this.options.classNameFormat({ packageName: this._packageName, suite }) : `${this._packageName}.${(suite.fullTitle || suite.title).replace(/\s/g, "_")}`;
178
+ const testCase = testSuite.testCase().className(classNameFormat).name(testName).time(test._duration / 1e3);
179
+ if (this.options.addFileAttribute) {
180
+ testCase.file(filePath);
181
+ }
182
+ if (test.state === "pending" || test.state === "skipped") {
183
+ testCase.skipped();
184
+ if (test.error) {
185
+ testCase.standardError(`
186
+ ${test.error.stack?.replace(ansiRegex, "")}
187
+ `);
213
188
  }
214
- const isCucumberFrameworkRunner = runner.config.framework === 'cucumber';
215
- if (isCucumberFrameworkRunner) {
216
- this._packageName = `CucumberJUnitReport-${this._packageName}`;
217
- this._suiteTitleLabel = 'featureName';
218
- this._fileNameLabel = 'featureFile';
219
- }
220
- else {
221
- this._suiteTitleLabel = 'suiteName';
222
- this._fileNameLabel = 'file';
223
- }
224
- runner.specs.forEach((specFileName) => {
225
- if (isCucumberFrameworkRunner) {
226
- this._buildOrderedReport(builder, runner, specFileName, 'feature', isCucumberFrameworkRunner);
227
- this._buildOrderedReport(builder, runner, specFileName, 'scenario', isCucumberFrameworkRunner);
228
- }
229
- else {
230
- this._buildOrderedReport(builder, runner, specFileName, '', isCucumberFrameworkRunner);
231
- }
232
- });
233
- return builder.build();
234
- }
235
- _buildOrderedReport(builder, runner, specFileName, type, isCucumberFrameworkRunner) {
236
- for (const suiteKey of Object.keys(this.suites)) {
237
- /**
238
- * ignore root before all
239
- */
240
- /* istanbul ignore if */
241
- if (suiteKey.match(/^"before all"/)) {
242
- continue;
243
- }
244
- const suite = this.suites[suiteKey];
245
- const sameSpecFileName = this._sameFileName(specFileName, suite.file);
246
- if (isCucumberFrameworkRunner && suite.type === type && sameSpecFileName) {
247
- builder = this._addCucumberFeatureToBuilder(builder, runner, specFileName, suite);
248
- }
249
- else if (!isCucumberFrameworkRunner && sameSpecFileName) {
250
- builder = this._addSuiteToBuilder(builder, runner, specFileName, suite);
189
+ } else if (test.state === "failed") {
190
+ if (test.error) {
191
+ if (test.error.message) {
192
+ test.error.message = test.error.message.replace(ansiRegex, "");
193
+ }
194
+ if (this.options.errorOptions) {
195
+ const errorOptions = this.options.errorOptions;
196
+ for (const key of Object.keys(errorOptions)) {
197
+ testCase[key](test.error[errorOptions[key]]);
251
198
  }
199
+ } else {
200
+ testCase.failure(test.error.message);
201
+ }
202
+ testCase.standardError(`
203
+ ${test.error.stack?.replace(ansiRegex, "")}
204
+ `);
205
+ } else {
206
+ testCase.failure();
252
207
  }
253
- return builder;
208
+ }
209
+ const output = this._getStandardOutput(test);
210
+ if (output) {
211
+ testCase.standardOutput(`
212
+ ${output}
213
+ `);
214
+ }
254
215
  }
255
- _getStandardOutput(test) {
256
- const standardOutput = [];
257
- test.output.forEach((data) => {
258
- switch (data.type) {
259
- case 'command':
260
- standardOutput.push(data.method
261
- ? `COMMAND: ${data.method.toUpperCase()} ` +
262
- `${data.endpoint.replace(':sessionId', data.sessionId)} - ${this._format(data.body)}`
263
- : `COMMAND: ${data.command} - ${this._format(data.params)}`);
264
- break;
265
- case 'result':
266
- standardOutput.push(`RESULT: ${this._format(data.body)}`);
267
- break;
268
- }
269
- });
270
- return standardOutput.length ? standardOutput.join('\n') : '';
216
+ return builder;
217
+ }
218
+ _buildJunitXml(runner) {
219
+ const builder = junit.newBuilder();
220
+ if (runner.config.hostname !== void 0 && runner.config.hostname.indexOf("browserstack") > -1) {
221
+ const browserstackSanitizedCapabilities = [
222
+ // @ts-expect-error capability only exists when running on BrowserStack
223
+ runner.capabilities.device,
224
+ // @ts-expect-error capability only exists when running on BrowserStack
225
+ runner.capabilities.os,
226
+ // @ts-expect-error capability only exists when running on BrowserStack
227
+ (runner.capabilities.os_version || "").replace(/\./g, "_")
228
+ ].filter(Boolean).map((capability) => capability.toLowerCase()).join(".").replace(/ /g, "") || runner.sanitizedCapabilities;
229
+ this._packageName = this.options.packageName || browserstackSanitizedCapabilities;
230
+ } else {
231
+ this._packageName = this.options.packageName || runner.sanitizedCapabilities;
271
232
  }
272
- _format(val) {
273
- return JSON.stringify(limit(val));
233
+ const isCucumberFrameworkRunner = runner.config.framework === "cucumber";
234
+ if (isCucumberFrameworkRunner) {
235
+ this._packageName = `CucumberJUnitReport-${this._packageName}`;
236
+ this._suiteTitleLabel = "featureName";
237
+ this._fileNameLabel = "featureFile";
238
+ } else {
239
+ this._suiteTitleLabel = "suiteName";
240
+ this._fileNameLabel = "file";
274
241
  }
275
- _sameFileName(file1, file2) {
276
- // ensure both files are not a file URL
277
- file1 = file1?.startsWith('file://') ? url.fileURLToPath(file1) : file1;
278
- file2 = file2?.startsWith('file://') ? url.fileURLToPath(file2) : file2;
279
- return file1 === file2;
242
+ runner.specs.forEach((specFileName) => {
243
+ if (isCucumberFrameworkRunner) {
244
+ this._buildOrderedReport(builder, runner, specFileName, "feature", isCucumberFrameworkRunner);
245
+ this._buildOrderedReport(builder, runner, specFileName, "scenario", isCucumberFrameworkRunner);
246
+ } else {
247
+ this._buildOrderedReport(builder, runner, specFileName, "", isCucumberFrameworkRunner);
248
+ }
249
+ });
250
+ return builder.build();
251
+ }
252
+ _buildOrderedReport(builder, runner, specFileName, type, isCucumberFrameworkRunner) {
253
+ for (const suiteKey of Object.keys(this.suites)) {
254
+ if (suiteKey.match(/^"before all"/)) {
255
+ continue;
256
+ }
257
+ const suite = this.suites[suiteKey];
258
+ const sameSpecFileName = this._sameFileName(specFileName, suite.file);
259
+ if (isCucumberFrameworkRunner && suite.type === type && sameSpecFileName) {
260
+ builder = this._addCucumberFeatureToBuilder(builder, runner, specFileName, suite);
261
+ } else if (!isCucumberFrameworkRunner && sameSpecFileName) {
262
+ builder = this._addSuiteToBuilder(builder, runner, specFileName, suite);
263
+ }
280
264
  }
281
- }
282
- export default JunitReporter;
265
+ return builder;
266
+ }
267
+ _getStandardOutput(test) {
268
+ const standardOutput = [];
269
+ test.output.forEach((data) => {
270
+ switch (data.type) {
271
+ case "command":
272
+ standardOutput.push(
273
+ data.method ? `COMMAND: ${data.method.toUpperCase()} ${data.endpoint.replace(":sessionId", data.sessionId)} - ${this._format(data.body)}` : `COMMAND: ${data.command} - ${this._format(data.params)}`
274
+ );
275
+ break;
276
+ case "result":
277
+ standardOutput.push(`RESULT: ${this._format(data.body)}`);
278
+ break;
279
+ }
280
+ });
281
+ return standardOutput.length ? standardOutput.join("\n") : "";
282
+ }
283
+ _format(val) {
284
+ return JSON.stringify(limit(val));
285
+ }
286
+ _sameFileName(file1, file2) {
287
+ file1 = file1?.startsWith("file://") ? url.fileURLToPath(file1) : file1;
288
+ file2 = file2?.startsWith("file://") ? url.fileURLToPath(file2) : file2;
289
+ return file1 === file2;
290
+ }
291
+ };
292
+ var src_default = JunitReporter;
293
+ export {
294
+ src_default as default
295
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/junit-reporter",
3
- "version": "9.0.0-alpha.78+fee2f8a88",
3
+ "version": "9.0.0",
4
4
  "description": "A WebdriverIO reporter that creates Jenkins compatible XML based JUnit reports",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-junit-reporter",
@@ -26,13 +26,15 @@
26
26
  "type": "module",
27
27
  "types": "./build/index.d.ts",
28
28
  "exports": {
29
- ".": "./build/index.js",
30
- "./package.json": "./package.json"
29
+ ".": {
30
+ "import": "./build/index.js",
31
+ "types": "./build/index.d.ts"
32
+ }
31
33
  },
32
34
  "typeScriptVersion": "3.8.3",
33
35
  "dependencies": {
34
- "@wdio/reporter": "9.0.0-alpha.78+fee2f8a88",
35
- "@wdio/types": "9.0.0-alpha.78+fee2f8a88",
36
+ "@wdio/reporter": "9.0.0",
37
+ "@wdio/types": "9.0.0",
36
38
  "json-stringify-safe": "^5.0.1",
37
39
  "junit-report-builder": "^3.0.0"
38
40
  },
@@ -42,5 +44,5 @@
42
44
  "publishConfig": {
43
45
  "access": "public"
44
46
  },
45
- "gitHead": "fee2f8a88d132537795eaf144abf1a7e242f99ff"
47
+ "gitHead": "957693463371a4cb329395dcdbce8fb0c930ab93"
46
48
  }
package/build/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
package/build/utils.js DELETED
@@ -1,67 +0,0 @@
1
- import stringify from 'json-stringify-safe';
2
- const OBJLENGTH = 10;
3
- const ARRLENGTH = 10;
4
- const STRINGLIMIT = 1000;
5
- const STRINGTRUNCATE = 200;
6
- export const limit = function (rawVal) {
7
- if (!rawVal) {
8
- return rawVal;
9
- }
10
- // Ensure we're working with a copy
11
- let val = JSON.parse(stringify(rawVal));
12
- const type = Object.prototype.toString.call(val);
13
- if (type === '[object String]') {
14
- if (val.length > 100 && isBase64(val)) {
15
- return `[base64] ${val.length} bytes`;
16
- }
17
- if (val.length > STRINGLIMIT) {
18
- return val.substr(0, STRINGTRUNCATE) + ` ... (${val.length - STRINGTRUNCATE} more bytes)`;
19
- }
20
- return val;
21
- }
22
- else if (type === '[object Array]') {
23
- const length = val.length;
24
- if (length > ARRLENGTH) {
25
- val = val.slice(0, ARRLENGTH);
26
- val.push(`(${length - ARRLENGTH} more items)`);
27
- }
28
- return val.map(limit);
29
- }
30
- else if (type === '[object Object]') {
31
- const keys = Object.keys(val);
32
- const removed = [];
33
- for (let i = 0, l = keys.length; i < l; i++) {
34
- if (i < OBJLENGTH) {
35
- val[keys[i]] = limit(val[keys[i]]);
36
- }
37
- else {
38
- delete val[keys[i]];
39
- removed.push(keys[i]);
40
- }
41
- }
42
- if (removed.length) {
43
- val._ = (keys.length - OBJLENGTH) + ' more keys: ' + JSON.stringify(removed);
44
- }
45
- return val;
46
- }
47
- return val;
48
- };
49
- /**
50
- * checks if provided string is Base64
51
- * @param {string} str string to check
52
- * @return {boolean} `true` if the provided string is Base64
53
- */
54
- export function isBase64(str) {
55
- if (typeof str !== 'string') {
56
- throw new Error('Expected string but received invalid type.');
57
- }
58
- const len = str.length;
59
- const notBase64 = /[^A-Z0-9+/=]/i;
60
- if (!len || len % 4 !== 0 || notBase64.test(str)) {
61
- return false;
62
- }
63
- const firstPaddingChar = str.indexOf('=');
64
- return (firstPaddingChar === -1 ||
65
- firstPaddingChar === len - 1 ||
66
- (firstPaddingChar === len - 2 && str[len - 1] === '='));
67
- }
File without changes