@wdio/junit-reporter 9.32.0-alpha.6 → 10.0.0-alpha.148

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/AGENTS.md ADDED
@@ -0,0 +1,20 @@
1
+ # @wdio/junit-reporter
2
+
3
+ JUnit XML reporter. It extends `@wdio/reporter`. User docs are this package's `README.md`.
4
+
5
+ ## Commands
6
+
7
+ ```sh
8
+ pnpm run test:package wdio-junit-reporter
9
+ ```
10
+
11
+ CJS `addProperty` load check: `pnpm --filter @wdio/test-interop-cjs run test:junit-reporter`.
12
+
13
+ ## Test ownership
14
+
15
+ - JUnit XML shape (suites, failures, skips, Cucumber scenarios, BrowserStack package names, and file association) is owned by `tests/reporter.test.ts` through `_buildJunitXml`. Keep those snapshots. Do not stub the suite builders and assert the stub.
16
+ - `onRunnerEnd` must write the built report. Assert that document. A mocked `undefined` snapshot does not.
17
+ - Whitespace-stripped snapshots do not lock command-log spacing. Exact `COMMAND:` / `RESULT:` text stays on the stdout test.
18
+ - `addProperty` is the public worker API (process event `junit:addProperty`). Call `addProperty`, not the private listener.
19
+ - `addWorkerLogs` is owned by writing to `stdout` while a test is current, then restoring `stdout.write`.
20
+ - The CJS interop check uses `assert.equal(typeof fn, 'function')`. `assert(typeof fn, 'function')` cannot fail.
package/README.md CHANGED
@@ -149,18 +149,23 @@ export const config = {
149
149
  The following options are supported:
150
150
 
151
151
  ### outputDir
152
+
153
+ <Option type="String">
154
+
152
155
  Define a directory where your XML files should get stored. Ignored if either `logFile` or `setLogFile` are defined.
153
156
 
154
- Type: `String`
157
+ </Option>
155
158
 
156
159
  ### outputFileFormat
160
+
161
+ <Option type="Object" default={`(opts) => \`wdio-\${opts.cid}-junit-reporter.log\``}>
162
+
157
163
  Function for defining the filename format for the reporter log files. Ignored if either `logFile` or `setLogFile`
158
164
  are defined.
159
165
 
160
166
  The function accepts an object input parameter with the `cid` and `capabilities` keys.
161
167
 
162
- Type: `Object`<br />
163
- Default: ``(opts) => `wdio-${opts.cid}-junit-reporter.log` ``<br />
168
+ </Option>
164
169
  Example:
165
170
  ```js
166
171
  // wdio.conf.js
@@ -175,10 +180,13 @@ export const config = {
175
180
  ```
176
181
 
177
182
  ### logFile
183
+
184
+ <Option type="String">
185
+
178
186
  Path to the reporter log file relative to the current directory. Overrides `outputDir` and `outputFileFormat`.
179
187
  Ignored if `setLogFile` is defined.
180
188
 
181
- Type: `String`<br />
189
+ </Option>
182
190
  Example:
183
191
  ```js
184
192
  // wdio.conf.js
@@ -192,11 +200,14 @@ export const config = {
192
200
  ```
193
201
 
194
202
  ### setLogFile
203
+
204
+ <Option type="Object">
205
+
195
206
  Function for defining the path for the reporter log files. Overrides `outputDir`, `outputFileFormat`, and `logFile`.
196
207
 
197
208
  The function accepts two input parameters: `cid` and `name` (the reporter name, set to `junit`).
198
209
 
199
- Type: `Object`<br />
210
+ </Option>
200
211
  Example:
201
212
  ```js
202
213
  // wdio.conf.js
@@ -210,25 +221,32 @@ export const config = {
210
221
  ```
211
222
 
212
223
  ### stdout
224
+
225
+ <Option type="boolean" default="false">
226
+
213
227
  Output the generated XML to the console instead of creating a log file.
214
228
 
215
- Type: `boolean`<br />
216
- Default: `false`
229
+ </Option>
217
230
 
218
231
  ### writeStream
232
+
233
+ <Option type="WriteStream">
234
+
219
235
  Set a stream to which the generated XML should be output, instead of creating a log file.
220
236
 
221
237
  Note: `logFile` must not be set, unless `stdout` is set to `true`.
222
238
 
223
- Type: `WriteStream`
239
+ </Option>
224
240
 
225
241
  ### suiteNameFormat
242
+
243
+ <Option type="Regex | Object" default="/[^a-zA-Z0-9@]+/">
244
+
226
245
  Format the generated name of a test suite, using custom regex or a function.
227
246
 
228
247
  The function accepts an object input parameter with the `name` and `suite` keys.
229
248
 
230
- Type: `Regex | Object`,<br />
231
- Default: `/[^a-zA-Z0-9@]+/`<br />
249
+ </Option>
232
250
  Example with Regex:
233
251
  ```js
234
252
  // wdio.conf.js
@@ -255,12 +273,15 @@ export const config = {
255
273
  ```
256
274
 
257
275
  ### classNameFormat
276
+
277
+ <Option type="Object">
278
+
258
279
  Format the generated classname of a test case.
259
280
 
260
281
  The function accepts an object input parameter with the [`packageName`](#packagename), `activeFeatureName`
261
282
  (Cucumber only), and `suite` (non-Cucumber only) keys.
262
283
 
263
- Type: `Object`<br />
284
+ </Option>
264
285
  Default (Cucumber): ``(opts) => `${opts.packageName}${opts.activeFeatureName}` ``<br />
265
286
  Default (others): ``(opts) => `${opts.packageName}.${(opts.suite.fullTitle || opts.suite.title).replace(/\s/g, '_')}` ``<br />
266
287
  Example (Cucumber):
@@ -288,14 +309,14 @@ export const config = {
288
309
  };
289
310
  ```
290
311
 
291
-
292
312
  ### addFileAttribute
293
313
 
314
+ <Option type="Boolean" default="false">
315
+
294
316
  Adds a `file` attribute to each testcase. The value of this attribute matches the filepath property of the parent test
295
317
  suite. This config is primarily for CircleCI, but may break on other CI platforms.
296
318
 
297
- Type: `Boolean`<br />
298
- Default: `false`<br />
319
+ </Option>
299
320
  Example simplified XML:
300
321
  ```xml
301
322
  <testsuite>
@@ -310,9 +331,11 @@ Example simplified XML:
310
331
 
311
332
  ### packageName
312
333
 
334
+ <Option type="String">
335
+
313
336
  You can break out packages by an additional level by setting `'packageName'`. For example, if you wanted to iterate over a test suite with a different environment variable set:
314
337
 
315
- Type: `String`<br />
338
+ </Option>
316
339
  Default (Cucumber): `CucumberJUnitReport-${sanitizedCapabilities}`<br />
317
340
  Default (others): `${sanitizedCapabilities}`<br />
318
341
  Example:
@@ -331,6 +354,8 @@ export const config = {
331
354
 
332
355
  ### errorOptions
333
356
 
357
+ <Option type="Object" default={`errorOptions: { error: "message" }`}>
358
+
334
359
  Allows to set various combinations of error notifications inside xml.<br />
335
360
  Given a Jasmine test like `expect(true).toBe(false, 'my custom message')` you will get this test error:
336
361
 
@@ -347,8 +372,7 @@ Given a Jasmine test like `expect(true).toBe(false, 'my custom message')` you wi
347
372
 
348
373
  Therefore you can choose *which* key will be used *where*, see the example below.
349
374
 
350
- Type: `Object`,<br />
351
- Default: `errorOptions: { error: "message" }`<br />
375
+ </Option>
352
376
  Example:
353
377
 
354
378
  ```js
@@ -369,10 +393,11 @@ export const config = {
369
393
 
370
394
  ### addWorkerLogs
371
395
 
396
+ <Option type="Boolean" default="false">
397
+
372
398
  Attach console logs from the test in the reporter.
373
399
 
374
- Type: `Boolean`<br />
375
- Default: `false`<br />
400
+ </Option>
376
401
  Example:
377
402
 
378
403
  ```js
@@ -426,3 +451,5 @@ If there is no such post-build step in your CI system, there is probably a plugi
426
451
  ----
427
452
 
428
453
  For more information on WebdriverIO see the [homepage](https://webdriver.io).
454
+
455
+
package/build/index.d.ts CHANGED
@@ -38,9 +38,27 @@ declare class JunitReporter extends WDIOReporter {
38
38
  private _addSuiteToBuilder;
39
39
  private _buildJunitXml;
40
40
  private _buildOrderedReport;
41
+ /**
42
+ * Emit filename-only suites that matched more than one grouped spec. They
43
+ * still belong in the report (issue #13052) but must not inherit the first
44
+ * spec's full path.
45
+ */
46
+ private _addAmbiguousBasenameSuites;
41
47
  private _getStandardOutput;
42
48
  private _getCommandStandardOutput;
43
49
  private _format;
50
+ private _toFilePath;
51
+ private _isBasenameOnly;
52
+ private _fileNamesEqual;
53
+ private _matchingSpecCount;
54
+ /**
55
+ * Decide whether a suite can be tied to a specific runner spec.
56
+ * - unique: full path match, or a filename-only Jasmine suite whose
57
+ * basename appears once in runner.specs
58
+ * - ambiguous: filename-only suite that matches multiple grouped specs
59
+ * - none: not the same file
60
+ */
61
+ private _suiteFileAssociation;
44
62
  private _sameFileName;
45
63
  }
46
64
  export * from './common/api.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,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;AAmBtD;;;;;GAKG;AACH,cAAM,aAAc,SAAQ,YAAY;IAajB,OAAO,EAAE,oBAAoB;IAZhD,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;IAChC,OAAO,CAAC,4BAA4B,CAAwD;IAC5F,OAAO,CAAC,YAAY,CAAC,CAAW;IAChC,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAS;gBAER,OAAO,EAAE,oBAAoB;IAiBhD,WAAW,CAAC,SAAS,EAAE,SAAS;IAIhC,WAAW,CAAC,IAAI,EAAE,SAAS;IAU3B,WAAW,CAAC,MAAM,EAAE,WAAW;IAK/B,OAAO,CAAC,yBAAyB;IAMjC,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;IAkBvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,4BAA4B;IA6GpC,OAAO,CAAC,kBAAkB;IAqC1B,OAAO,CAAC,cAAc;IA2CtB,OAAO,CAAC,mBAAmB;IAwC3B,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,aAAa;CAgBxB;AAED,cAAc,iBAAiB,CAAA;AAC/B,eAAe,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,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;AAmBtD;;;;;GAKG;AACH,cAAM,aAAc,SAAQ,YAAY;IAajB,OAAO,EAAE,oBAAoB;IAZhD,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;IAChC,OAAO,CAAC,4BAA4B,CAAwD;IAC5F,OAAO,CAAC,YAAY,CAAC,CAAW;IAChC,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAS;gBAER,OAAO,EAAE,oBAAoB;IAiBhD,WAAW,CAAC,SAAS,EAAE,SAAS;IAIhC,WAAW,CAAC,IAAI,EAAE,SAAS;IAU3B,WAAW,CAAC,MAAM,EAAE,WAAW;IAK/B,OAAO,CAAC,yBAAyB;IAMjC,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;IAkBvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,4BAA4B;IA6GpC,OAAO,CAAC,kBAAkB;IAqC1B,OAAO,CAAC,cAAc;IAuDtB,OAAO,CAAC,mBAAmB;IA6D3B;;;;OAIG;IACH,OAAO,CAAC,2BAA2B;IAkCnC,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,kBAAkB;IAO1B;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAoB7B,OAAO,CAAC,aAAa;CAuBxB;AAED,cAAc,iBAAiB,CAAA;AAC/B,eAAe,aAAa,CAAA"}
package/build/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/index.ts
2
+ import path from "node:path";
2
3
  import url from "node:url";
3
4
  import os from "node:os";
4
5
  import WDIOReporter from "@wdio/reporter";
@@ -318,17 +319,19 @@ ${stepsOutput}
318
319
  this._suiteTitleLabel = "suiteName";
319
320
  this._fileNameLabel = "file";
320
321
  }
322
+ const assignedSuiteKeys = /* @__PURE__ */ new Set();
321
323
  runner.specs.forEach((specFileName) => {
322
324
  if (isCucumberFrameworkRunner) {
323
- this._buildOrderedReport(builder, runner, specFileName, "feature", isCucumberFrameworkRunner);
324
- this._buildOrderedReport(builder, runner, specFileName, "scenario", isCucumberFrameworkRunner);
325
+ this._buildOrderedReport(builder, runner, specFileName, "feature", isCucumberFrameworkRunner, assignedSuiteKeys);
326
+ this._buildOrderedReport(builder, runner, specFileName, "scenario", isCucumberFrameworkRunner, assignedSuiteKeys);
325
327
  } else {
326
- this._buildOrderedReport(builder, runner, specFileName, "", isCucumberFrameworkRunner);
328
+ this._buildOrderedReport(builder, runner, specFileName, "", isCucumberFrameworkRunner, assignedSuiteKeys);
327
329
  }
328
330
  });
331
+ this._addAmbiguousBasenameSuites(builder, runner, isCucumberFrameworkRunner, assignedSuiteKeys);
329
332
  return builder.build();
330
333
  }
331
- _buildOrderedReport(builder, runner, specFileName, type, isCucumberFrameworkRunner) {
334
+ _buildOrderedReport(builder, runner, specFileName, type, isCucumberFrameworkRunner, assignedSuiteKeys) {
332
335
  const suiteKeys = Object.keys(this.suites);
333
336
  if (suiteKeys.length === 0) {
334
337
  if (!isCucumberFrameworkRunner || isCucumberFrameworkRunner && type === "feature") {
@@ -343,16 +346,48 @@ ${stepsOutput}
343
346
  if (suiteKey.match(/^"before all"/)) {
344
347
  continue;
345
348
  }
349
+ if (assignedSuiteKeys.has(suiteKey)) {
350
+ continue;
351
+ }
346
352
  const suite = this.suites[suiteKey];
347
- const sameSpecFileName = this._sameFileName(specFileName, suite.file);
348
- if (isCucumberFrameworkRunner && suite.type === type && sameSpecFileName) {
353
+ if (this._suiteFileAssociation(specFileName, suite.file, runner?.specs) !== "unique") {
354
+ continue;
355
+ }
356
+ if (isCucumberFrameworkRunner && suite.type === type) {
357
+ assignedSuiteKeys.add(suiteKey);
349
358
  builder = this._addCucumberFeatureToBuilder(builder, runner, specFileName, suite);
350
- } else if (!isCucumberFrameworkRunner && sameSpecFileName) {
359
+ } else if (!isCucumberFrameworkRunner) {
360
+ assignedSuiteKeys.add(suiteKey);
351
361
  builder = this._addSuiteToBuilder(builder, runner, specFileName, suite);
352
362
  }
353
363
  }
354
364
  return builder;
355
365
  }
366
+ /**
367
+ * Emit filename-only suites that matched more than one grouped spec. They
368
+ * still belong in the report (issue #13052) but must not inherit the first
369
+ * spec's full path.
370
+ */
371
+ _addAmbiguousBasenameSuites(builder, runner, isCucumberFrameworkRunner, assignedSuiteKeys) {
372
+ const types = isCucumberFrameworkRunner ? ["feature", "scenario"] : [""];
373
+ for (const type of types) {
374
+ for (const suiteKey of Object.keys(this.suites)) {
375
+ if (suiteKey.match(/^"before all"/) || assignedSuiteKeys.has(suiteKey)) {
376
+ continue;
377
+ }
378
+ const suite = this.suites[suiteKey];
379
+ if (isCucumberFrameworkRunner && suite.type !== type) {
380
+ continue;
381
+ }
382
+ if (!suite.file || !this._isBasenameOnly(this._toFilePath(suite.file)) || this._matchingSpecCount(suite.file, runner.specs) <= 1) {
383
+ continue;
384
+ }
385
+ assignedSuiteKeys.add(suiteKey);
386
+ builder = isCucumberFrameworkRunner ? this._addCucumberFeatureToBuilder(builder, runner, suite.file, suite) : this._addSuiteToBuilder(builder, runner, suite.file, suite);
387
+ }
388
+ }
389
+ return builder;
390
+ }
356
391
  _getStandardOutput(test) {
357
392
  let consoleOutput = "";
358
393
  if (this._addWorkerLogs) {
@@ -388,6 +423,39 @@ ${stepsOutput}
388
423
  _format(val) {
389
424
  return JSON.stringify(limit(val));
390
425
  }
426
+ _toFilePath(file) {
427
+ return file.startsWith("file://") ? url.fileURLToPath(file) : file;
428
+ }
429
+ _isBasenameOnly(file) {
430
+ return !file.includes("/") && !file.includes("\\");
431
+ }
432
+ _fileNamesEqual(file1, file2) {
433
+ return file1.localeCompare(file2, void 0, { sensitivity: this._isWindows ? "accent" : "variant" }) === 0;
434
+ }
435
+ _matchingSpecCount(suiteFile, runnerSpecs) {
436
+ const suiteBase = path.basename(this._toFilePath(suiteFile));
437
+ return runnerSpecs.filter((spec) => this._fileNamesEqual(path.basename(this._toFilePath(spec)), suiteBase)).length;
438
+ }
439
+ /**
440
+ * Decide whether a suite can be tied to a specific runner spec.
441
+ * - unique: full path match, or a filename-only Jasmine suite whose
442
+ * basename appears once in runner.specs
443
+ * - ambiguous: filename-only suite that matches multiple grouped specs
444
+ * - none: not the same file
445
+ */
446
+ _suiteFileAssociation(specFileName, suiteFile, runnerSpecs = []) {
447
+ if (!this._sameFileName(specFileName, suiteFile)) {
448
+ return "none";
449
+ }
450
+ if (!suiteFile) {
451
+ return "unique";
452
+ }
453
+ const suitePath = this._toFilePath(suiteFile);
454
+ if (!this._isBasenameOnly(suitePath)) {
455
+ return "unique";
456
+ }
457
+ return this._matchingSpecCount(suiteFile, runnerSpecs) > 1 ? "ambiguous" : "unique";
458
+ }
391
459
  _sameFileName(file1, file2) {
392
460
  if (!file1 && !file2) {
393
461
  return true;
@@ -395,9 +463,13 @@ ${stepsOutput}
395
463
  if (!file1 || !file2) {
396
464
  return false;
397
465
  }
398
- file1 = file1.startsWith("file://") ? url.fileURLToPath(file1) : file1;
399
- file2 = file2.startsWith("file://") ? url.fileURLToPath(file2) : file2;
400
- return file1.localeCompare(file2, void 0, { sensitivity: this._isWindows ? "accent" : "variant" }) === 0;
466
+ file1 = this._toFilePath(file1);
467
+ file2 = this._toFilePath(file2);
468
+ if (this._isBasenameOnly(file1) || this._isBasenameOnly(file2)) {
469
+ file1 = path.basename(file1);
470
+ file2 = path.basename(file2);
471
+ }
472
+ return this._fileNamesEqual(file1, file2);
401
473
  }
402
474
  };
403
475
  var index_default = JunitReporter;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@wdio/junit-reporter",
3
- "version": "9.32.0-alpha.6+3d8818a67",
3
+ "version": "10.0.0-alpha.148+3a926c3d0",
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",
7
7
  "license": "MIT",
8
8
  "engines": {
9
- "node": ">=18.20.0"
9
+ "node": ">=22.19.0"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -33,12 +33,12 @@
33
33
  "require": "./build/index.cjs"
34
34
  }
35
35
  },
36
- "typeScriptVersion": "3.8.3",
36
+ "typeScriptVersion": "6.0.3",
37
37
  "dependencies": {
38
- "@wdio/reporter": "9.32.0-alpha.6+3d8818a67",
39
- "@wdio/types": "9.32.0-alpha.6+3d8818a67",
38
+ "@wdio/reporter": "10.0.0-alpha.148+3a926c3d0",
39
+ "@wdio/types": "10.0.0-alpha.148+3a926c3d0",
40
40
  "json-stringify-safe": "^5.0.1",
41
- "junit-report-builder": "^5.1.1"
41
+ "junit-report-builder": "^5.1.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/json-stringify-safe": "^5.0.0"
@@ -46,5 +46,5 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
- "gitHead": "3d8818a67404f0b5d4b81eda877ffad1f4d317c0"
49
+ "gitHead": "3a926c3d08dafa93c92021b2892341709d74968a"
50
50
  }