Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: update cucumber format parsing #284

Merged
merged 24 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 55 additions & 9 deletions src/cucumber-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@ 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));
});
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',
// 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',
'"@saucelabs/cucumber-reporter":"sauce-test-report.json"',
'--format-options',
JSON.stringify(buildFormatOption(runCfg)),
];
Expand Down Expand Up @@ -50,15 +57,12 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) {
procArgs.push('-t');
procArgs.push(tag);
});

runCfg.suite.options.format?.forEach((format) => {
procArgs.push('--format');
const opts = format.split(':');
if (opts.length === 2) {
procArgs.push(`${opts[0]}:${path.join(runCfg.assetsDir, opts[1])}`);
} else {
procArgs.push(format);
}
procArgs.push(normalizeFormat(format, runCfg.assetsDir));
});

if (runCfg.suite.options.parallel) {
procArgs.push('--parallel');
procArgs.push(runCfg.suite.options.parallel.toString(10));
Expand All @@ -67,6 +71,48 @@ function buildArgs(runCfg: CucumberRunnerConfig, cucumberBin: string) {
return procArgs;
}

/**
* 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 simple inputs (e.g., `usage`), the input is returned unchanged.
*
* @param {string} format - The input format string. Examples include:
* - `"key:value"`
* - `"key":"value"`
* - `key:value`
* - `usage`
* @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"`
* Output: `"html":"/project/assets/formatter/report.html"`
* - Input: `"html":"file://formatter/report.html"`, `"/project/assets"`
tianfeng92 marked this conversation as resolved.
Show resolved Hide resolved
* Output: `"html":"file://formatter/report.html"`
* - 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.
const match = format.match(/^"?([^:]+):"?([^"]+)"?$/);
tianfeng92 marked this conversation as resolved.
Show resolved Hide resolved
if (!match) {
return format;
}

let [, key, value] = match;
key = key.replaceAll('"', '');
value = value.replaceAll('"', '');
if (value.startsWith('file://')) {
return `"${key}":"${value}"`;
}
return `"${key}":"${path.join(assetDir, value)}"`;
}

export async function runCucumber(
nodeBin: string,
runCfg: CucumberRunnerConfig,
Expand Down
73 changes: 73 additions & 0 deletions tests/unit/src/cucumber-runner.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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":"sauce-test-report.json"',
'--format-options',
'{"upload":false,"outputFile":"/project/assets/sauce-test-report.json"}',
]);
});
});

describe('normalizeFormat', () => {
const assetDir = '/project/assets';

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 format with only one pair of quote', () => {
expect(normalizeFormat(`"html:formatter/report.html"`, assetDir)).toBe(
`"html":"/project/assets/formatter/report.html"`,
);
});

it('should normalize format with no quotes', () => {
expect(normalizeFormat(`html:formatter/report.html`, assetDir)).toBe(
`"html":"/project/assets/formatter/report.html"`,
);
});

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', () => {
expect(normalizeFormat(`"usage"`, assetDir)).toBe('"usage"');
expect(normalizeFormat(`usage`, assetDir)).toBe('usage');
});
});
Loading