From 195c5c6e99dda1043115c92b57edc5bc8094a240 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Wed, 20 Nov 2024 15:20:41 -0800 Subject: [PATCH 01/24] feat: update cucumber format parsing --- src/cucumber-runner.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index f356fd0b..3a75bf36 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -15,7 +15,6 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { const procArgs = [ cucumberBin, ...paths, - '--publish-quiet', // Deprecated in 9.4.0. Will be removed in 11.0.0 or later. '--force-exit', '--require-module', 'ts-node/register', @@ -50,20 +49,41 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { procArgs.push('-t'); procArgs.push(tag); }); + + function parseNewFormat(format: string, assetsDir: string) { + // Regex to validate and extract key and value from the new format. + // Example: "html":"file://hostname/formatter/report.html" + const match = format.match(/^"([^"]+)":"(.+)"$/); + if (!match) { + return null; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, key, value] = match; + return `"${key}":"${path.join(assetsDir, value)}"`; + } + runCfg.suite.options.format?.forEach((format) => { procArgs.push('--format'); + + const newFormat = parseNewFormat(format, runCfg.assetsDir); + if (newFormat) { + return newFormat; + } + const opts = format.split(':'); if (opts.length === 2) { - procArgs.push(`${opts[0]}:${path.join(runCfg.assetsDir, opts[1])}`); - } else { - procArgs.push(format); + return `"${opts[0]}":"${path.join(runCfg.assetsDir, opts[1])}"`; } + + return `"${format}"`; }); + if (runCfg.suite.options.parallel) { procArgs.push('--parallel'); procArgs.push(runCfg.suite.options.parallel.toString(10)); } + console.log('procArgs: ', procArgs); return procArgs; } From aa780f4eb34fbfc0a358d1af446ec0887fe5335c Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Wed, 20 Nov 2024 19:08:17 -0800 Subject: [PATCH 02/24] revise implementation and add unit test --- src/cucumber-runner.ts | 57 ++++++++++++---------- tests/unit/src/cucumber-runner.spec.js | 65 ++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 tests/unit/src/cucumber-runner.spec.js diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 3a75bf36..8647bb8a 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -7,7 +7,7 @@ import type { CucumberRunnerConfig } from './types'; import * as utils from './utils'; import { NodeContext } from 'sauce-testrunner-utils/lib/types'; -function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { +export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { const paths: string[] = []; runCfg.suite.options.paths.forEach((p) => { paths.push(path.join(runCfg.projectPath, p)); @@ -50,32 +50,10 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { procArgs.push(tag); }); - function parseNewFormat(format: string, assetsDir: string) { - // Regex to validate and extract key and value from the new format. - // Example: "html":"file://hostname/formatter/report.html" - const match = format.match(/^"([^"]+)":"(.+)"$/); - if (!match) { - return null; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [_, key, value] = match; - return `"${key}":"${path.join(assetsDir, value)}"`; - } - runCfg.suite.options.format?.forEach((format) => { + const updatedFormat = normalizeFormat(format, runCfg.assetsDir); procArgs.push('--format'); - - const newFormat = parseNewFormat(format, runCfg.assetsDir); - if (newFormat) { - return newFormat; - } - - const opts = format.split(':'); - if (opts.length === 2) { - return `"${opts[0]}":"${path.join(runCfg.assetsDir, opts[1])}"`; - } - - return `"${format}"`; + procArgs.push(updatedFormat); }); if (runCfg.suite.options.parallel) { @@ -87,6 +65,35 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { return procArgs; } +/** + * Normalizes a Cucumber format string by ensuring it is in the form of `"key":"value"`. + * + * @param {string} format - The input format string, which can be in various forms: + * - `"key:value"` + * - `"key":"value"` + * - `key:value` + * @param {string} assetDir - The asset directory. + * @throws {Error} If the input format is invalid (e.g., missing a colon separator). + * @returns {string} The normalized format string in the form of `"key":"value"`, + * with the asset directory prepended to relative paths. + * + * Example: + * Input: `"html:formatter/report.html"`, `"/project/assets"` + * Output: `"html":"/project/assets/formatter/report.html"` + */ +export function normalizeFormat(format: string, assetDir: string): string { + const match = format.match(/^"?([^:]+):"?([^"]+)"?$/); + if (!match) { + throw new Error(`Invalid format: ${format}`); + } + + let [, key, value] = match; + key = key.replace(/^"|"$/g, ''); + value = value.replace(/^"|"$/g, ''); + const updatedPath = path.join(assetDir, value); + return `"${key}":"${updatedPath}"`; +} + export async function runCucumber( nodeBin: string, runCfg: CucumberRunnerConfig, diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js new file mode 100644 index 00000000..f51539d1 --- /dev/null +++ b/tests/unit/src/cucumber-runner.spec.js @@ -0,0 +1,65 @@ +const { buildArgs, normalizeFormat } = require('../../../src/cucumber-runner'); + +describe('buildArgs', () => { + const cucumberBin = '/usr/local/bin/cucumber'; + + it('should build correct arguments with basic configuration', () => { + const runCfg = { + sauce: { + metadata: {}, + }, + projectPath: '/project', + assetsDir: '/project/assets', + suite: { + options: { + paths: ['features/test.feature'], + }, + }, + }; + + const result = buildArgs(runCfg, cucumberBin); + + expect(result).toEqual([ + cucumberBin, + '/project/features/test.feature', + '--force-exit', + '--require-module', + 'ts-node/register', + '--format', + '@saucelabs/cucumber-reporter', + '--format-options', + '{"upload":false,"outputFile":"/project/assets/sauce-test-report.json"}', + ]); + }); +}); + +describe('normalizeFormat', () => { + const assetDir = '/project/assets'; + + it('should normalize formats with both quoted format type and path', () => { + expect(normalizeFormat(`"html":"formatter/report.html"`, assetDir)).toBe( + `"html":"/project/assets/formatter/report.html"`, + ); + }); + + it('should normalize formats with only one pair of quote', () => { + expect(normalizeFormat(`"html:formatter/report.html"`, assetDir)).toBe( + `"html":"/project/assets/formatter/report.html"`, + ); + }); + + it('should normalize formats with no quotes', () => { + expect(normalizeFormat(`html:formatter/report.html`, assetDir)).toBe( + `"html":"/project/assets/formatter/report.html"`, + ); + }); + + it('should throw an error for invalid formats', () => { + expect(() => + normalizeFormat(`html-formatter/report.html`, assetDir), + ).toThrow('Invalid format: html-formatter/report.html'); + expect(() => + normalizeFormat(`"htmlformatter/report.html"`, assetDir), + ).toThrow('Invalid format: "htmlformatter/report.html"'); + }); +}); From 3cd800e3f00cf7c49f29a4136261459b3aa13892 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Wed, 20 Nov 2024 19:34:28 -0800 Subject: [PATCH 03/24] support simple format strings --- src/cucumber-runner.ts | 25 ++++++++++++++++--------- tests/unit/src/cucumber-runner.spec.js | 10 +++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 8647bb8a..c7c54508 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -66,25 +66,32 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { } /** - * Normalizes a Cucumber format string by ensuring it is in the form of `"key":"value"`. + * Normalizes a Cucumber-js format string. * - * @param {string} format - The input format string, which can be in various forms: + * For structured inputs (`key:value` or `"key:value"`), returns a string in the + * form `"key":"value"`, with the asset directory prepended to relative paths. + * For simple inputs (e.g., `progress-bar`), returns the input as-is. + * + * @param {string} format - The input format string. Examples include: * - `"key:value"` * - `"key":"value"` * - `key:value` - * @param {string} assetDir - The asset directory. - * @throws {Error} If the input format is invalid (e.g., missing a colon separator). - * @returns {string} The normalized format string in the form of `"key":"value"`, - * with the asset directory prepended to relative paths. + * - `progress-bar` + * @param {string} assetDir - The directory to prepend to the value for relative paths. + * @returns {string} The normalized format string. For structured inputs, it returns + * a string in the form `"key":"value"`. For simple inputs, it + * returns the input unchanged. * * Example: - * Input: `"html:formatter/report.html"`, `"/project/assets"` - * Output: `"html":"/project/assets/formatter/report.html"` + * - Input: `"html":"formatter/report.html"`, `"/project/assets"` + * Output: `"html":"/project/assets/formatter/report.html"` + * - Input: `"progress-bar"`, `"/project/assets"` + * Output: `"progress-bar"` */ export function normalizeFormat(format: string, assetDir: string): string { const match = format.match(/^"?([^:]+):"?([^"]+)"?$/); if (!match) { - throw new Error(`Invalid format: ${format}`); + return format; } let [, key, value] = match; diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index f51539d1..ce530f4f 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -54,12 +54,8 @@ describe('normalizeFormat', () => { ); }); - it('should throw an error for invalid formats', () => { - expect(() => - normalizeFormat(`html-formatter/report.html`, assetDir), - ).toThrow('Invalid format: html-formatter/report.html'); - expect(() => - normalizeFormat(`"htmlformatter/report.html"`, assetDir), - ).toThrow('Invalid format: "htmlformatter/report.html"'); + it('should return simple strings as-is', () => { + expect(normalizeFormat(`"progress-bar"`, assetDir)).toBe('"progress-bar"'); + expect(normalizeFormat(`progress-bar`, assetDir)).toBe('progress-bar'); }); }); From 1185713129ae0ee7fa4cd7fb90004d679efa7af1 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Wed, 20 Nov 2024 21:48:40 -0800 Subject: [PATCH 04/24] cleanup --- src/cucumber-runner.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index c7c54508..bbf4171f 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -51,9 +51,8 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { }); runCfg.suite.options.format?.forEach((format) => { - const updatedFormat = normalizeFormat(format, runCfg.assetsDir); procArgs.push('--format'); - procArgs.push(updatedFormat); + procArgs.push(normalizeFormat(format, runCfg.assetsDir)); }); if (runCfg.suite.options.parallel) { @@ -61,7 +60,6 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { procArgs.push(runCfg.suite.options.parallel.toString(10)); } - console.log('procArgs: ', procArgs); return procArgs; } @@ -89,6 +87,7 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * Output: `"progress-bar"` */ export function normalizeFormat(format: string, assetDir: string): string { + // Checks if the format is structured; if not, returns it unchanged. const match = format.match(/^"?([^:]+):"?([^"]+)"?$/); if (!match) { return format; From cea1e222103d732785177603957e5d3e930a9c12 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Thu, 21 Nov 2024 10:41:20 -0800 Subject: [PATCH 05/24] rename example formatter in comments --- src/cucumber-runner.ts | 12 ++++++------ tests/unit/src/cucumber-runner.spec.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index bbf4171f..fb1e7cdb 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -68,13 +68,13 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * * For structured inputs (`key:value` or `"key:value"`), returns a string in the * form `"key":"value"`, with the asset directory prepended to relative paths. - * For simple inputs (e.g., `progress-bar`), returns the input as-is. + * For simple inputs (e.g., `usage`), returns the input as-is. * * @param {string} format - The input format string. Examples include: * - `"key:value"` * - `"key":"value"` * - `key:value` - * - `progress-bar` + * - `usage` * @param {string} assetDir - The directory to prepend to the value for relative paths. * @returns {string} The normalized format string. For structured inputs, it returns * a string in the form `"key":"value"`. For simple inputs, it @@ -83,8 +83,8 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * Example: * - Input: `"html":"formatter/report.html"`, `"/project/assets"` * Output: `"html":"/project/assets/formatter/report.html"` - * - Input: `"progress-bar"`, `"/project/assets"` - * Output: `"progress-bar"` + * - Input: `"usage"`, `"/project/assets"` + * Output: `"usage"` */ export function normalizeFormat(format: string, assetDir: string): string { // Checks if the format is structured; if not, returns it unchanged. @@ -94,8 +94,8 @@ export function normalizeFormat(format: string, assetDir: string): string { } let [, key, value] = match; - key = key.replace(/^"|"$/g, ''); - value = value.replace(/^"|"$/g, ''); + key = key.replaceAll('"', ''); + value = value.replaceAll('"', ''); const updatedPath = path.join(assetDir, value); return `"${key}":"${updatedPath}"`; } diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index ce530f4f..10a04ee7 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -55,7 +55,7 @@ describe('normalizeFormat', () => { }); it('should return simple strings as-is', () => { - expect(normalizeFormat(`"progress-bar"`, assetDir)).toBe('"progress-bar"'); - expect(normalizeFormat(`progress-bar`, assetDir)).toBe('progress-bar'); + expect(normalizeFormat(`"usage"`, assetDir)).toBe('"usage"'); + expect(normalizeFormat(`usage`, assetDir)).toBe('usage'); }); }); From 7a5c744268ef046f7ecaf9be8272c8450ddece6d Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Thu, 21 Nov 2024 22:01:17 -0800 Subject: [PATCH 06/24] redirect the output to sauce-test-report --- src/cucumber-runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index fb1e7cdb..5b3d7d70 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -19,7 +19,7 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { '--require-module', 'ts-node/register', '--format', - '@saucelabs/cucumber-reporter', + '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', '--format-options', JSON.stringify(buildFormatOption(runCfg)), ]; From d4e719d65aa202e647d5869d6127b33219b300ad Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Thu, 21 Nov 2024 22:06:32 -0800 Subject: [PATCH 07/24] fix test --- tests/unit/src/cucumber-runner.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index 10a04ee7..906d1311 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -26,7 +26,7 @@ describe('buildArgs', () => { '--require-module', 'ts-node/register', '--format', - '@saucelabs/cucumber-reporter', + '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', '--format-options', '{"upload":false,"outputFile":"/project/assets/sauce-test-report.json"}', ]); From e8bdb375b678b6988d67214003edd20b6a2f08b6 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Thu, 21 Nov 2024 22:48:56 -0800 Subject: [PATCH 08/24] add notes --- src/cucumber-runner.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 5b3d7d70..40ce6635 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -18,6 +18,11 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { '--force-exit', '--require-module', 'ts-node/register', + // NOTE: Cucumber only supports a single stdout formatter. If multiple stdout + // formatters are specified, Cucumber will use the last one provided. + // To ensure the sauce test report file is always generated, redirect the output to + // sauce-test-report.json using the --format argument and specify the outputFile + // in --format-options simultaneously. '--format', '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', '--format-options', From 5bafda0cc81ffc9cb40eb8c4f40243b81fd5232f Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 08:31:31 -0800 Subject: [PATCH 09/24] revise notes --- src/cucumber-runner.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 40ce6635..a1ed9523 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -18,11 +18,14 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { '--force-exit', '--require-module', 'ts-node/register', - // NOTE: Cucumber only supports a single stdout formatter. If multiple stdout - // formatters are specified, Cucumber will use the last one provided. - // To ensure the sauce test report file is always generated, redirect the output to - // sauce-test-report.json using the --format argument and specify the outputFile - // in --format-options simultaneously. + // NOTE: The Cucumber formatter (--format) setting follows the "type":"path" format. + // If the "path" is missing, the output defaults to stdout. + // Cucumber supports only one stdout formatter, and if multiple are specified, + // it will use the last one provided. + // To ensure the Sauce test report file is always generated, + // set the output to a file using the --format option, + // and use the --format-options flag to specify the outputFile. + // Both fields must be configured for the file to be created reliably. '--format', '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', '--format-options', From cb686cdef04b613df51c0e3f16a66ed6f1e5de29 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 08:51:40 -0800 Subject: [PATCH 10/24] revise notes --- src/cucumber-runner.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index a1ed9523..2be45228 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -18,14 +18,14 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { '--force-exit', '--require-module', 'ts-node/register', - // NOTE: The Cucumber formatter (--format) setting follows the "type":"path" format. - // If the "path" is missing, the output defaults to stdout. - // Cucumber supports only one stdout formatter, and if multiple are specified, - // it will use the last one provided. - // To ensure the Sauce test report file is always generated, - // set the output to a file using the --format option, - // and use the --format-options flag to specify the outputFile. - // Both fields must be configured for the file to be created reliably. + // NOTE: The Cucumber formatter (--format) setting uses the "type:path" format. + // If the "path" is not provided, the output defaults to stdout. + // Cucumber supports only one stdout formatter; if multiple are specified, + // it will prioritize the last one listed. + // To ensure the Sauce test report file is always generated and not overridden + // by a user-specified stdout formatter, set the output to a file using the --format option + // and configure the --format-options flag to specify the outputFile. + // Both settings must be properly configured to reliably generate the file. '--format', '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', '--format-options', From d0df43e39b4bfba2214da8b52682c809369abd77 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 08:52:33 -0800 Subject: [PATCH 11/24] oops --- src/cucumber-runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 2be45228..b3a46851 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -18,7 +18,7 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { '--force-exit', '--require-module', 'ts-node/register', - // NOTE: The Cucumber formatter (--format) setting uses the "type:path" format. + // NOTE: The Cucumber formatter (--format) setting uses the "type":"path" format. // If the "path" is not provided, the output defaults to stdout. // Cucumber supports only one stdout formatter; if multiple are specified, // it will prioritize the last one listed. From 0efeb6d1110743e80eac055752c60ddc4909adae Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 09:48:39 -0800 Subject: [PATCH 12/24] support absolute path --- src/cucumber-runner.ts | 6 ++++-- tests/unit/src/cucumber-runner.spec.js | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index b3a46851..9a01a738 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -104,8 +104,10 @@ export function normalizeFormat(format: string, assetDir: string): string { let [, key, value] = match; key = key.replaceAll('"', ''); value = value.replaceAll('"', ''); - const updatedPath = path.join(assetDir, value); - return `"${key}":"${updatedPath}"`; + if (value.startsWith('file://')) { + return `"${key}":"${value}"`; + } + return `"${key}":"${path.join(assetDir, value)}"`; } export async function runCucumber( diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index 906d1311..e56734e7 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -36,24 +36,30 @@ describe('buildArgs', () => { describe('normalizeFormat', () => { const assetDir = '/project/assets'; - it('should normalize formats with both quoted format type and path', () => { + it('should normalize format with both quoted format type and path', () => { expect(normalizeFormat(`"html":"formatter/report.html"`, assetDir)).toBe( `"html":"/project/assets/formatter/report.html"`, ); }); - it('should normalize formats with only one pair of quote', () => { + it('should normalize format with only one pair of quote', () => { expect(normalizeFormat(`"html:formatter/report.html"`, assetDir)).toBe( `"html":"/project/assets/formatter/report.html"`, ); }); - it('should normalize formats with no quotes', () => { + it('should normalize format with no quotes', () => { expect(normalizeFormat(`html:formatter/report.html`, assetDir)).toBe( `"html":"/project/assets/formatter/report.html"`, ); }); + it('should normalize format with absolute path', () => { + expect( + normalizeFormat(`"html":"file:///tmp/formatter/report.html"`, assetDir), + ).toBe(`"html":"file:///tmp/formatter/report.html"`); + }); + it('should return simple strings as-is', () => { expect(normalizeFormat(`"usage"`, assetDir)).toBe('"usage"'); expect(normalizeFormat(`usage`, assetDir)).toBe('usage'); From ced147dfeae294bafa24a62d23fbbbf214db019d Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 10:02:01 -0800 Subject: [PATCH 13/24] add more test cases --- tests/unit/src/cucumber-runner.spec.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index e56734e7..1ae1103d 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -54,10 +54,16 @@ describe('normalizeFormat', () => { ); }); - it('should normalize format with absolute path', () => { + it('should normalize formats with absolute path', () => { expect( normalizeFormat(`"html":"file:///tmp/formatter/report.html"`, assetDir), ).toBe(`"html":"file:///tmp/formatter/report.html"`); + expect( + normalizeFormat(`"html:file:///tmp/formatter/report.html"`, assetDir), + ).toBe(`"html":"file:///tmp/formatter/report.html"`); + expect( + normalizeFormat(`html:file:///tmp/formatter/report.html`, assetDir), + ).toBe(`"html":"file:///tmp/formatter/report.html"`); }); it('should return simple strings as-is', () => { From 850315da96a70ca770587a3ab83e4e8f5a1ea3d6 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 10:06:34 -0800 Subject: [PATCH 14/24] update comments --- src/cucumber-runner.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 9a01a738..1921aa9d 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -75,8 +75,11 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * Normalizes a Cucumber-js format string. * * For structured inputs (`key:value` or `"key:value"`), returns a string in the - * form `"key":"value"`, with the asset directory prepended to relative paths. - * For simple inputs (e.g., `usage`), returns the input as-is. + * form `"key":"value"`. If the value starts with `file://`, it is treated as an + * absolute path and no asset directory is prepended. Otherwise, the asset + * directory is prepended to relative paths. + * + * For simple inputs (e.g., `usage`), the input is returned unchanged. * * @param {string} format - The input format string. Examples include: * - `"key:value"` @@ -84,13 +87,13 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * - `key:value` * - `usage` * @param {string} assetDir - The directory to prepend to the value for relative paths. - * @returns {string} The normalized format string. For structured inputs, it returns - * a string in the form `"key":"value"`. For simple inputs, it - * returns the input unchanged. + * @returns {string} The normalized format string. * * Example: * - Input: `"html":"formatter/report.html"`, `"/project/assets"` * Output: `"html":"/project/assets/formatter/report.html"` + * - Input: `"html":"file://formatter/report.html"`, `"/project/assets"` + * Output: `"html":"file://formatter/report.html"` * - Input: `"usage"`, `"/project/assets"` * Output: `"usage"` */ From 783703ddbab6ead876e5459edf9fb2e711808ec3 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 12:27:32 -0800 Subject: [PATCH 15/24] handle file based formatter implemenation --- src/cucumber-runner.ts | 34 +++++++++++++++++++------- tests/unit/src/cucumber-runner.spec.js | 9 +++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 1921aa9d..0e8bc0bd 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -74,32 +74,46 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { /** * Normalizes a Cucumber-js format string. * - * For structured inputs (`key:value` or `"key:value"`), returns a string in the - * form `"key":"value"`. If the value starts with `file://`, it is treated as an - * absolute path and no asset directory is prepended. Otherwise, the asset - * directory is prepended to relative paths. + * For structured inputs (`key:value`, `"key:value"`, or `"key":"value"`), returns a string + * in the form `"key":"value"`. If the value starts with `file://`, it is treated as an + * absolute path, and no asset directory is prepended. Otherwise, the asset directory + * is prepended to relative paths. * - * For simple inputs (e.g., `usage`), the input is returned unchanged. + * For simple inputs (e.g., `usage`) or other unstructured formats, the input is returned unchanged. * * @param {string} format - The input format string. Examples include: * - `"key:value"` * - `"key":"value"` * - `key:value` * - `usage` + * - `"file://implementation":"output_file"` * @param {string} assetDir - The directory to prepend to the value for relative paths. * @returns {string} The normalized format string. * - * Example: - * - Input: `"html":"formatter/report.html"`, `"/project/assets"` + * Examples: + * - Input: `"html:formatter/report.html"`, `"/project/assets"` * Output: `"html":"/project/assets/formatter/report.html"` * - Input: `"html":"file://formatter/report.html"`, `"/project/assets"` * Output: `"html":"file://formatter/report.html"` * - Input: `"usage"`, `"/project/assets"` * Output: `"usage"` + * - Input: `"file://implementation":"output_file"`, `"/project/assets"` + * Output: `"file://implementation":"/project/assets/output_file"` */ export function normalizeFormat(format: string, assetDir: string): string { - // Checks if the format is structured; if not, returns it unchanged. - const match = format.match(/^"?([^:]+):"?([^"]+)"?$/); + // Try to match structured inputs in the format key:value, "key:value", or "key":"value". + let match = format.match(/^"?([^:]+):"?([^"]+)"?$/); + + if (!match) { + // Check if the format uses a file path starting with "file://". + if (!format.startsWith('"file://')) { + return format; + } + + // Match file-based structured inputs like "file://implementation":"output_file". + match = format.match(/^"([^"]+)":"([^"]+)"$/); + } + if (!match) { return format; } @@ -107,9 +121,11 @@ export function normalizeFormat(format: string, assetDir: string): string { let [, key, value] = match; key = key.replaceAll('"', ''); value = value.replaceAll('"', ''); + if (value.startsWith('file://')) { return `"${key}":"${value}"`; } + return `"${key}":"${path.join(assetDir, value)}"`; } diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index 1ae1103d..2c0e95b5 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -66,6 +66,15 @@ describe('normalizeFormat', () => { ).toBe(`"html":"file:///tmp/formatter/report.html"`); }); + it('should normalize format with file path type', () => { + expect( + normalizeFormat( + `"file://formatter/implementation":"report.json"`, + assetDir, + ), + ).toBe(`"file://formatter/implementation":"/project/assets/report.json"`); + }); + it('should return simple strings as-is', () => { expect(normalizeFormat(`"usage"`, assetDir)).toBe('"usage"'); expect(normalizeFormat(`usage`, assetDir)).toBe('usage'); From ffb8742ffb2c86df8532b44d35a8290ef742ce7b Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 15:28:18 -0800 Subject: [PATCH 16/24] no file uri support in path --- src/cucumber-runner.ts | 4 ---- tests/unit/src/cucumber-runner.spec.js | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 0e8bc0bd..e7ca1350 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -122,10 +122,6 @@ export function normalizeFormat(format: string, assetDir: string): string { key = key.replaceAll('"', ''); value = value.replaceAll('"', ''); - if (value.startsWith('file://')) { - return `"${key}":"${value}"`; - } - return `"${key}":"${path.join(assetDir, value)}"`; } diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index 2c0e95b5..6004a0f0 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -54,18 +54,6 @@ describe('normalizeFormat', () => { ); }); - it('should normalize formats with absolute path', () => { - expect( - normalizeFormat(`"html":"file:///tmp/formatter/report.html"`, assetDir), - ).toBe(`"html":"file:///tmp/formatter/report.html"`); - expect( - normalizeFormat(`"html:file:///tmp/formatter/report.html"`, assetDir), - ).toBe(`"html":"file:///tmp/formatter/report.html"`); - expect( - normalizeFormat(`html:file:///tmp/formatter/report.html`, assetDir), - ).toBe(`"html":"file:///tmp/formatter/report.html"`); - }); - it('should normalize format with file path type', () => { expect( normalizeFormat( From 9485fd8b0dc562185bf661da94a955c8cbd7d362 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 16:03:05 -0800 Subject: [PATCH 17/24] support formatOptions --- src/cucumber-runner.ts | 1 + src/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index e7ca1350..73011e3b 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -204,5 +204,6 @@ function buildFormatOption(cfg: CucumberRunnerConfig) { build: cfg.sauce.metadata?.build, tags: cfg.sauce.metadata?.tags, outputFile: path.join(cfg.assetsDir, 'sauce-test-report.json'), + ...cfg.suite.options.formatOptions, }; } diff --git a/src/types.ts b/src/types.ts index 239ff61a..182338f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -101,6 +101,7 @@ export interface CucumberSuite { import?: string[]; tags?: string[]; format?: string[]; + formatOptions?: object; parallel?: number; paths: string[]; }; From 0bfbc32dfc3f03d9254b71eb447af3c7b285a8b7 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 20:47:18 -0800 Subject: [PATCH 18/24] update comment --- src/cucumber-runner.ts | 15 +++++---------- tests/unit/src/cucumber-runner.spec.js | 5 ++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 73011e3b..448f67e3 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -74,12 +74,10 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { /** * Normalizes a Cucumber-js format string. * - * For structured inputs (`key:value`, `"key:value"`, or `"key":"value"`), returns a string - * in the form `"key":"value"`. If the value starts with `file://`, it is treated as an - * absolute path, and no asset directory is prepended. Otherwise, the asset directory - * is prepended to relative paths. - * - * For simple inputs (e.g., `usage`) or other unstructured formats, the input is returned unchanged. + * This function handles structured inputs in the format `key:value`, `"key:value"`, + * or `"key":"value"` and returns a normalized string in the form `"key":"value"`. + * For simple inputs (e.g., `usage`) or unstructured formats, the function returns the + * input unchanged. * * @param {string} format - The input format string. Examples include: * - `"key:value"` @@ -93,19 +91,16 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * Examples: * - Input: `"html:formatter/report.html"`, `"/project/assets"` * Output: `"html":"/project/assets/formatter/report.html"` - * - Input: `"html":"file://formatter/report.html"`, `"/project/assets"` - * Output: `"html":"file://formatter/report.html"` * - Input: `"usage"`, `"/project/assets"` * Output: `"usage"` * - Input: `"file://implementation":"output_file"`, `"/project/assets"` - * Output: `"file://implementation":"/project/assets/output_file"` + * Output: `"file://implementation":"output_file"` (unchanged) */ export function normalizeFormat(format: string, assetDir: string): string { // Try to match structured inputs in the format key:value, "key:value", or "key":"value". let match = format.match(/^"?([^:]+):"?([^"]+)"?$/); if (!match) { - // Check if the format uses a file path starting with "file://". if (!format.startsWith('"file://')) { return format; } diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index 6004a0f0..46d9d2b4 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -13,6 +13,9 @@ describe('buildArgs', () => { suite: { options: { paths: ['features/test.feature'], + formatOptions: { + build: 'mybuild', + }, }, }, }; @@ -28,7 +31,7 @@ describe('buildArgs', () => { '--format', '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', '--format-options', - '{"upload":false,"outputFile":"/project/assets/sauce-test-report.json"}', + '{"upload":false,"build":"mybuild","outputFile":"/project/assets/sauce-test-report.json"}', ]); }); }); From 0d27a4fad15bc49e44070dd54b3fcd259e486c47 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 20:53:44 -0800 Subject: [PATCH 19/24] update comments for sauce cucumber report setting --- src/cucumber-runner.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 448f67e3..75328ec7 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -18,16 +18,21 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { '--force-exit', '--require-module', 'ts-node/register', - // NOTE: The Cucumber formatter (--format) setting uses the "type":"path" format. - // If the "path" is not provided, the output defaults to stdout. + // NOTE: The Cucumber formatter (--format) option uses the "type":"path" format. + // If the "path" is not specified, the output defaults to stdout. // Cucumber supports only one stdout formatter; if multiple are specified, - // it will prioritize the last one listed. - // To ensure the Sauce test report file is always generated and not overridden - // by a user-specified stdout formatter, set the output to a file using the --format option - // and configure the --format-options flag to specify the outputFile. - // Both settings must be properly configured to reliably generate the file. + // the last one listed takes precedence. + // + // To ensure the Sauce test report file is reliably generated and not overridden + // by a user-specified stdout formatter, configure the following: + // 1. In the --format option, set the output to a file (e.g., cucumber.log) to + // avoid writing to stdout. + // 2. Use the --format-options flag to explicitly specify the outputFile + // (e.g., sauce-test-report.json). + // + // Both settings must be configured correctly to ensure the Sauce test report file is generated. '--format', - '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', + '"@saucelabs/cucumber-reporter":"cucumber.log"', '--format-options', JSON.stringify(buildFormatOption(runCfg)), ]; From e1bcfb15240068e1420a9b0be47c2a548b24abfd Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Fri, 22 Nov 2024 20:59:32 -0800 Subject: [PATCH 20/24] update test --- tests/unit/src/cucumber-runner.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index 46d9d2b4..bb7abf19 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -29,7 +29,7 @@ describe('buildArgs', () => { '--require-module', 'ts-node/register', '--format', - '"@saucelabs/cucumber-reporter":"sauce-test-report.json"', + '"@saucelabs/cucumber-reporter":"cucumber.log"', '--format-options', '{"upload":false,"build":"mybuild","outputFile":"/project/assets/sauce-test-report.json"}', ]); From a2248aba48ac5be5ee525913effc20573724fa04 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Mon, 25 Nov 2024 09:01:31 -0800 Subject: [PATCH 21/24] add an invalid case --- src/cucumber-runner.ts | 9 +++++++++ tests/unit/src/cucumber-runner.spec.js | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 75328ec7..3e9abc33 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -84,6 +84,8 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * For simple inputs (e.g., `usage`) or unstructured formats, the function returns the * input unchanged. * + * If the input starts with `file://`, an error is thrown to indicate an invalid format. + * * @param {string} format - The input format string. Examples include: * - `"key:value"` * - `"key":"value"` @@ -102,6 +104,13 @@ export function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) { * Output: `"file://implementation":"output_file"` (unchanged) */ export function normalizeFormat(format: string, assetDir: string): string { + // Formats starting with file:// are not supported by the current implementation. + // Restrict users from using this format. + if (format.startsWith('file://')) { + throw new Error( + `Invalid format setting detected. The provided format "${format}" is not allowed.`, + ); + } // Try to match structured inputs in the format key:value, "key:value", or "key":"value". let match = format.match(/^"?([^:]+):"?([^"]+)"?$/); diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index bb7abf19..bbd17ea5 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -66,6 +66,12 @@ describe('normalizeFormat', () => { ).toBe(`"file://formatter/implementation":"/project/assets/report.json"`); }); + it('should throw an error for an invalid file path type', () => { + expect(() => { + normalizeFormat(`file://formatter/implementation:report.json`, assetDir); + }).toThrow('Invalid format setting detected'); + }); + it('should return simple strings as-is', () => { expect(normalizeFormat(`"usage"`, assetDir)).toBe('"usage"'); expect(normalizeFormat(`usage`, assetDir)).toBe('usage'); From e5f0f0dbc31235db193cad9a6866547c2bb602ba Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Mon, 25 Nov 2024 09:26:48 -0800 Subject: [PATCH 22/24] kill useless format options --- src/cucumber-runner.ts | 3 --- tests/unit/src/cucumber-runner.spec.js | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 3e9abc33..2d3f0966 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -209,9 +209,6 @@ export async function runCucumber( function buildFormatOption(cfg: CucumberRunnerConfig) { return { upload: false, - suiteName: cfg.suite.name, - build: cfg.sauce.metadata?.build, - tags: cfg.sauce.metadata?.tags, outputFile: path.join(cfg.assetsDir, 'sauce-test-report.json'), ...cfg.suite.options.formatOptions, }; diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index bbd17ea5..e7b5f932 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -14,7 +14,7 @@ describe('buildArgs', () => { options: { paths: ['features/test.feature'], formatOptions: { - build: 'mybuild', + myOption: 'test', }, }, }, @@ -31,7 +31,7 @@ describe('buildArgs', () => { '--format', '"@saucelabs/cucumber-reporter":"cucumber.log"', '--format-options', - '{"upload":false,"build":"mybuild","outputFile":"/project/assets/sauce-test-report.json"}', + '{"upload":false,"outputFile":"/project/assets/sauce-test-report.json","myOption":"test"}', ]); }); }); From d06c5141f17ff6288fec8212773ada5c1245d343 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Mon, 25 Nov 2024 10:38:46 -0800 Subject: [PATCH 23/24] give formatOptions more precise type --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 182338f0..cce6df35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -101,7 +101,7 @@ export interface CucumberSuite { import?: string[]; tags?: string[]; format?: string[]; - formatOptions?: object; + formatOptions?: { [key: string]: string }; parallel?: number; paths: string[]; }; From 78865d528e2f8f108a5bdd22cc16eb2080883bf4 Mon Sep 17 00:00:00 2001 From: Tian Feng Date: Mon, 25 Nov 2024 10:53:37 -0800 Subject: [PATCH 24/24] revise error msg --- src/cucumber-runner.ts | 2 +- tests/unit/src/cucumber-runner.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cucumber-runner.ts b/src/cucumber-runner.ts index 2d3f0966..d41d2ff3 100644 --- a/src/cucumber-runner.ts +++ b/src/cucumber-runner.ts @@ -108,7 +108,7 @@ export function normalizeFormat(format: string, assetDir: string): string { // Restrict users from using this format. if (format.startsWith('file://')) { throw new Error( - `Invalid format setting detected. The provided format "${format}" is not allowed.`, + `Ambiguous colon usage detected. The provided format "${format}" is not allowed.`, ); } // Try to match structured inputs in the format key:value, "key:value", or "key":"value". diff --git a/tests/unit/src/cucumber-runner.spec.js b/tests/unit/src/cucumber-runner.spec.js index e7b5f932..d744b9c3 100644 --- a/tests/unit/src/cucumber-runner.spec.js +++ b/tests/unit/src/cucumber-runner.spec.js @@ -69,7 +69,7 @@ describe('normalizeFormat', () => { it('should throw an error for an invalid file path type', () => { expect(() => { normalizeFormat(`file://formatter/implementation:report.json`, assetDir); - }).toThrow('Invalid format setting detected'); + }).toThrow('Ambiguous colon usage detected'); }); it('should return simple strings as-is', () => {