@@ -7,20 +7,32 @@ import type { CucumberRunnerConfig } from './types';
7
7
import * as utils from './utils' ;
8
8
import { NodeContext } from 'sauce-testrunner-utils/lib/types' ;
9
9
10
- function buildArgs ( runCfg : CucumberRunnerConfig , cucumberBin : string ) {
10
+ export function buildArgs ( runCfg : CucumberRunnerConfig , cucumberBin : string ) {
11
11
const paths : string [ ] = [ ] ;
12
12
runCfg . suite . options . paths . forEach ( ( p ) => {
13
13
paths . push ( path . join ( runCfg . projectPath , p ) ) ;
14
14
} ) ;
15
15
const procArgs = [
16
16
cucumberBin ,
17
17
...paths ,
18
- '--publish-quiet' , // Deprecated in 9.4.0. Will be removed in 11.0.0 or later.
19
18
'--force-exit' ,
20
19
'--require-module' ,
21
20
'ts-node/register' ,
21
+ // NOTE: The Cucumber formatter (--format) option uses the "type":"path" format.
22
+ // If the "path" is not specified, the output defaults to stdout.
23
+ // Cucumber supports only one stdout formatter; if multiple are specified,
24
+ // the last one listed takes precedence.
25
+ //
26
+ // To ensure the Sauce test report file is reliably generated and not overridden
27
+ // by a user-specified stdout formatter, configure the following:
28
+ // 1. In the --format option, set the output to a file (e.g., cucumber.log) to
29
+ // avoid writing to stdout.
30
+ // 2. Use the --format-options flag to explicitly specify the outputFile
31
+ // (e.g., sauce-test-report.json).
32
+ //
33
+ // Both settings must be configured correctly to ensure the Sauce test report file is generated.
22
34
'--format' ,
23
- '@saucelabs/cucumber-reporter' ,
35
+ '" @saucelabs/cucumber-reporter":"cucumber.log" ' ,
24
36
'--format-options' ,
25
37
JSON . stringify ( buildFormatOption ( runCfg ) ) ,
26
38
] ;
@@ -50,15 +62,12 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) {
50
62
procArgs . push ( '-t' ) ;
51
63
procArgs . push ( tag ) ;
52
64
} ) ;
65
+
53
66
runCfg . suite . options . format ?. forEach ( ( format ) => {
54
67
procArgs . push ( '--format' ) ;
55
- const opts = format . split ( ':' ) ;
56
- if ( opts . length === 2 ) {
57
- procArgs . push ( `${ opts [ 0 ] } :${ path . join ( runCfg . assetsDir , opts [ 1 ] ) } ` ) ;
58
- } else {
59
- procArgs . push ( format ) ;
60
- }
68
+ procArgs . push ( normalizeFormat ( format , runCfg . assetsDir ) ) ;
61
69
} ) ;
70
+
62
71
if ( runCfg . suite . options . parallel ) {
63
72
procArgs . push ( '--parallel' ) ;
64
73
procArgs . push ( runCfg . suite . options . parallel . toString ( 10 ) ) ;
@@ -67,6 +76,64 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) {
67
76
return procArgs ;
68
77
}
69
78
79
+ /**
80
+ * Normalizes a Cucumber-js format string.
81
+ *
82
+ * This function handles structured inputs in the format `key:value`, `"key:value"`,
83
+ * or `"key":"value"` and returns a normalized string in the form `"key":"value"`.
84
+ * For simple inputs (e.g., `usage`) or unstructured formats, the function returns the
85
+ * input unchanged.
86
+ *
87
+ * If the input starts with `file://`, an error is thrown to indicate an invalid format.
88
+ *
89
+ * @param {string } format - The input format string. Examples include:
90
+ * - `"key:value"`
91
+ * - `"key":"value"`
92
+ * - `key:value`
93
+ * - `usage`
94
+ * - `"file://implementation":"output_file"`
95
+ * @param {string } assetDir - The directory to prepend to the value for relative paths.
96
+ * @returns {string } The normalized format string.
97
+ *
98
+ * Examples:
99
+ * - Input: `"html:formatter/report.html"`, `"/project/assets"`
100
+ * Output: `"html":"/project/assets/formatter/report.html"`
101
+ * - Input: `"usage"`, `"/project/assets"`
102
+ * Output: `"usage"`
103
+ * - Input: `"file://implementation":"output_file"`, `"/project/assets"`
104
+ * Output: `"file://implementation":"output_file"` (unchanged)
105
+ */
106
+ export function normalizeFormat ( format : string , assetDir : string ) : string {
107
+ // Formats starting with file:// are not supported by the current implementation.
108
+ // Restrict users from using this format.
109
+ if ( format . startsWith ( 'file://' ) ) {
110
+ throw new Error (
111
+ `Ambiguous colon usage detected. The provided format "${ format } " is not allowed.` ,
112
+ ) ;
113
+ }
114
+ // Try to match structured inputs in the format key:value, "key:value", or "key":"value".
115
+ let match = format . match ( / ^ " ? ( [ ^ : ] + ) : " ? ( [ ^ " ] + ) " ? $ / ) ;
116
+
117
+ if ( ! match ) {
118
+ if ( ! format . startsWith ( '"file://' ) ) {
119
+ return format ;
120
+ }
121
+
122
+ // Match file-based structured inputs like "file://implementation":"output_file".
123
+ match = format . match ( / ^ " ( [ ^ " ] + ) " : " ( [ ^ " ] + ) " $ / ) ;
124
+ }
125
+
126
+ if ( ! match ) {
127
+ return format ;
128
+ }
129
+
130
+ let [ , key , value ] = match ;
131
+ key = key . replaceAll ( '"' , '' ) ;
132
+ value = value . replaceAll ( '"' , '' ) ;
133
+
134
+ return `"${ key } ":"${ path . join ( assetDir , value ) } "` ;
135
+ }
136
+
70
137
export async function runCucumber (
71
138
nodeBin : string ,
72
139
runCfg : CucumberRunnerConfig ,
@@ -142,9 +209,7 @@ export async function runCucumber(
142
209
function buildFormatOption ( cfg : CucumberRunnerConfig ) {
143
210
return {
144
211
upload : false ,
145
- suiteName : cfg . suite . name ,
146
- build : cfg . sauce . metadata ?. build ,
147
- tags : cfg . sauce . metadata ?. tags ,
148
212
outputFile : path . join ( cfg . assetsDir , 'sauce-test-report.json' ) ,
213
+ ...cfg . suite . options . formatOptions ,
149
214
} ;
150
215
}
0 commit comments