From 0f51ea7156967ec8e4e8c5c62b1d92ec3840daf7 Mon Sep 17 00:00:00 2001 From: David Goss Date: Mon, 15 Jan 2024 15:01:58 +0000 Subject: [PATCH] include stackTrace in result exception object (#2371) --- CHANGELOG.md | 1 + features/support/formatter_output_helpers.ts | 1 + package-lock.json | 16 +-- package.json | 4 +- src/runtime/format_error.ts | 24 ++-- src/runtime/format_error_spec.ts | 139 +++++++++++++------ src/runtime/test_case_runner_spec.ts | 2 + 7 files changed, 124 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1df00d4..49ca9ceb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber ## [Unreleased] ### Added - Allow `provided` configuration to be a string ([#2373](https://github.com/cucumber/cucumber-js/pull/2373)) +- Include `stackTrace` in result exception object ([#2371](https://github.com/cucumber/cucumber-js/pull/2371)) ## [10.2.1] - 2024-01-07 ### Fixed diff --git a/features/support/formatter_output_helpers.ts b/features/support/formatter_output_helpers.ts index 6e4f188dc..b24396190 100644 --- a/features/support/formatter_output_helpers.ts +++ b/features/support/formatter_output_helpers.ts @@ -118,4 +118,5 @@ export const ignorableKeys = [ 'seconds', // errors 'message', + 'stackTrace', ] diff --git a/package-lock.json b/package-lock.json index a267a0c26..2ea27c21a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@cucumber/html-formatter": "21.2.0", "@cucumber/message-streams": "4.0.1", "@cucumber/messages": "24.0.1", - "@cucumber/tag-expressions": "6.0.0", + "@cucumber/tag-expressions": "6.1.0", "assertion-error-formatter": "^3.0.0", "capital-case": "^1.0.4", "chalk": "^4.1.2", @@ -55,7 +55,7 @@ "cucumber-js": "bin/cucumber.js" }, "devDependencies": { - "@cucumber/compatibility-kit": "14.1.0", + "@cucumber/compatibility-kit": "15.0.0", "@cucumber/query": "12.0.1", "@microsoft/api-extractor": "7.39.0", "@sinonjs/fake-timers": "10.0.2", @@ -618,9 +618,9 @@ "integrity": "sha512-lRkiehckosIOdc7p1L44nZsttO5dVHFjpwKKWZ07x8SeoAdV/sPuGe1PISe0AmAowFGza62nMOgG4KaroGzwFQ==" }, "node_modules/@cucumber/compatibility-kit": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-14.1.0.tgz", - "integrity": "sha512-BWmS2kTNn0XHuQIhDxo7aTRR4AzzSVXB2CuEB8J+Fuqg4j9nWFvSFB0UoWtlp+PSOxRS4QfmnZmeBcsjBZ5r0A==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/compatibility-kit/-/compatibility-kit-15.0.0.tgz", + "integrity": "sha512-5pzgNY0ylsQpcU0CxTBIRlo7+2F1helz/CUMbgE8E7IBxKVQ1czA/WgWtHoeVGm0i0zK5QE7TyDOUL5Nikek0Q==", "dev": true }, "node_modules/@cucumber/cucumber-expressions": { @@ -799,9 +799,9 @@ } }, "node_modules/@cucumber/tag-expressions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-6.0.0.tgz", - "integrity": "sha512-JbNb/254Wn6b8cfrIJoqR0NekHXvoB/eMvSY4RK11H8k+YZfm7mZesu/3yVX67nkW+Y+PGjZFcgTMcfjwFRsRw==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-6.1.0.tgz", + "integrity": "sha512-+3DwRumrCJG27AtzCIL37A/X+A/gSfxOPLg8pZaruh5SLumsTmpvilwroVWBT2fPzmno/tGXypeK5a7NHU4RzA==" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", diff --git a/package.json b/package.json index bc421310a..8d81f9db4 100644 --- a/package.json +++ b/package.json @@ -216,7 +216,7 @@ "@cucumber/html-formatter": "21.2.0", "@cucumber/message-streams": "4.0.1", "@cucumber/messages": "24.0.1", - "@cucumber/tag-expressions": "6.0.0", + "@cucumber/tag-expressions": "6.1.0", "assertion-error-formatter": "^3.0.0", "capital-case": "^1.0.4", "chalk": "^4.1.2", @@ -251,7 +251,7 @@ "yup": "1.2.0" }, "devDependencies": { - "@cucumber/compatibility-kit": "14.1.0", + "@cucumber/compatibility-kit": "15.0.0", "@cucumber/query": "12.0.1", "@microsoft/api-extractor": "7.39.0", "@sinonjs/fake-timers": "10.0.2", diff --git a/src/runtime/format_error.ts b/src/runtime/format_error.ts index f65b1aae7..3082e7fed 100644 --- a/src/runtime/format_error.ts +++ b/src/runtime/format_error.ts @@ -7,20 +7,21 @@ export function formatError( error: Error, filterStackTraces: boolean ): Pick { - let filteredStack: string - if (filterStackTraces) { - try { - filteredStack = filterStackTrace(errorStackParser.parse(error)) - .map((f) => f.source) - .join('\n') - } catch { - // if we weren't able to parse and filter, we'll settle for the original - } + let processedStackTrace: string + try { + const parsedStack = errorStackParser.parse(error) + const filteredStack = filterStackTraces + ? filterStackTrace(parsedStack) + : parsedStack + processedStackTrace = filteredStack.map((f) => f.source).join('\n') + } catch { + // if we weren't able to parse and process, we'll settle for the original } const message = format(error, { colorFns: { - errorStack: (stack: string) => - filteredStack ? `\n${filteredStack}` : stack, + errorStack: (stack: string) => { + return processedStackTrace ? `\n${processedStackTrace}` : stack + }, }, }) return { @@ -28,6 +29,7 @@ export function formatError( exception: { type: error.name || 'Error', message: typeof error === 'string' ? error : error.message, + stackTrace: processedStackTrace ?? error.stack, }, } } diff --git a/src/runtime/format_error_spec.ts b/src/runtime/format_error_spec.ts index f093e19ea..b3167923d 100644 --- a/src/runtime/format_error_spec.ts +++ b/src/runtime/format_error_spec.ts @@ -3,56 +3,111 @@ import { expect } from 'chai' import { formatError } from './format_error' describe('formatError', () => { - function testFormatError(fn: () => void, filterStackTraces: boolean = false) { - try { - fn() - return undefined - } catch (error) { - return formatError(error, filterStackTraces) + describe('type and message', () => { + function testFormatError(fn: () => void) { + try { + fn() + return undefined + } catch (error) { + const { + exception: { type, message }, + } = formatError(error, false) + return { type, message } + } } - } - - it('should handle a custom error', () => { - expect( - testFormatError(() => { - assert.ok(false, 'Thing that should have been truthy was falsy!') - }).exception - ).to.eql({ - type: 'AssertionError', - message: 'Thing that should have been truthy was falsy!', + + it('should handle a custom error', () => { + expect( + testFormatError(() => { + assert.ok(false, 'Thing that should have been truthy was falsy!') + }) + ).to.eql({ + type: 'AssertionError', + message: 'Thing that should have been truthy was falsy!', + }) }) - }) - it('should handle a generic error', () => { - expect( - testFormatError(() => { - throw new Error('A generally bad thing happened!') - }).exception - ).to.eql({ - type: 'Error', - message: 'A generally bad thing happened!', + it('should handle a generic error', () => { + expect( + testFormatError(() => { + throw new Error('A generally bad thing happened!') + }) + ).to.eql({ + type: 'Error', + message: 'A generally bad thing happened!', + }) + }) + + it('should handle an omitted message', () => { + expect( + testFormatError(() => { + throw new Error() + }) + ).to.eql({ + type: 'Error', + message: '', + }) }) - }) - it('should handle an omitted message', () => { - expect( - testFormatError(() => { - throw new Error() - }).exception - ).to.eql({ - type: 'Error', - message: '', + it('should handle a thrown string', () => { + expect( + testFormatError(() => { + throw 'Yikes!' + }) + ).to.eql({ + type: 'Error', + message: 'Yikes!', + }) }) }) - it('should handle a thrown string', () => { - expect( - testFormatError(() => { - throw 'Yikes!' - }).exception - ).to.eql({ - type: 'Error', - message: 'Yikes!', + describe('stack traces', () => { + ;[false, true].forEach((filterStackTraces) => { + describe('with filterStackTraces=' + filterStackTraces, () => { + function testFormatError(fn: () => void) { + try { + fn() + return undefined + } catch (error) { + const { + exception: { stackTrace }, + } = formatError(error, false) + return stackTrace + } + } + + it('should handle a custom error', () => { + expect( + testFormatError(() => { + assert.ok(false, 'Thing that should have been truthy was falsy!') + }) + ).to.have.string(' at ') + }) + + it('should handle a generic error', () => { + expect( + testFormatError(() => { + throw new Error('A generally bad thing happened!') + }) + ).to.have.string(' at ') + }) + + it('should handle an omitted message', () => { + expect( + testFormatError(() => { + throw new Error() + }) + ).to.have.string(' at ') + }) + + it('should handle a thrown string', () => { + expect( + testFormatError(() => { + throw 'Yikes!' + }) + ).to.be.undefined + }) + }) }) }) }) diff --git a/src/runtime/test_case_runner_spec.ts b/src/runtime/test_case_runner_spec.ts index 87d1e861b..19fc5d8c8 100644 --- a/src/runtime/test_case_runner_spec.ts +++ b/src/runtime/test_case_runner_spec.ts @@ -169,6 +169,7 @@ describe('TestCaseRunner', () => { exception: { type: 'Error', message: 'fail', + stackTrace: undefined, }, } @@ -316,6 +317,7 @@ describe('TestCaseRunner', () => { exception: { type: 'Error', message: 'Oh no!', + stackTrace: undefined, }, status: messages.TestStepResultStatus.FAILED, },