diff --git a/README.md b/README.md index f41f96f..a223cc6 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ The `junit-upload` and `playwright-json-upload` commands upload test results fro - `--attachments` - Try to detect and upload any attachments with the test result - `--force` - Ignore API request errors, invalid test cases, or attachments - `--ignore-unmatched` - Suppress individual unmatched test messages, show summary only +- `--skip-report-stdout` - Control when to skip stdout blocks from test report (choices: `on-success`, `never`; default: `never`) +- `--skip-report-stderr` - Control when to skip stderr blocks from test report (choices: `on-success`, `never`; default: `never`) - `-h, --help` - Show command help ### Run Name Template Placeholders @@ -137,6 +139,18 @@ Ensure the required environment variables are defined before running these comma ``` This will show only a summary like "Skipped 5 unmatched tests" instead of individual error messages for each unmatched test. +9. Skip stdout/stderr for passed tests to reduce result payload size: + ```bash + qasphere junit-upload --skip-report-stdout on-success ./test-results.xml + ``` + This will exclude stdout from passed tests while still including it for failed, blocked, or skipped tests. + + Skip both stdout and stderr for passed tests: + ```bash + qasphere junit-upload --skip-report-stdout on-success --skip-report-stderr on-success ./test-results.xml + ``` + This is useful when you have verbose logging in tests but only want to see output for failures. + ## Test Report Requirements The QAS CLI requires test cases in your reports (JUnit XML or Playwright JSON) to reference corresponding test cases in QA Sphere. These references are used to map test results from your automation to the appropriate test cases in QA Sphere. If a report lacks these references or the referenced test case doesn't exist in QA Sphere, the tool will display an error message. diff --git a/package.json b/package.json index fe8eed8..5f5ea4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qas-cli", - "version": "0.4.1", + "version": "0.4.2", "description": "QAS CLI is a command line tool for submitting your automation test results to QA Sphere at https://qasphere.com/", "type": "module", "main": "./build/bin/qasphere.js", diff --git a/src/commands/resultUpload.ts b/src/commands/resultUpload.ts index b24acc1..df5cac0 100644 --- a/src/commands/resultUpload.ts +++ b/src/commands/resultUpload.ts @@ -53,6 +53,18 @@ export class ResultUploadCommandModule implements CommandModule + + + + + + + + + + (/Users/a/Developer/Hypersequent/bistro-e2e-webdriver/test/specs/contents.e2e.ts:12:21)]]> + + (/Users/a/Developer/Hypersequent/bistro-e2e-webdriver/test/specs/contents.e2e.ts:12:21) +]]> + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tests/junit-xml-parsing.spec.ts b/src/tests/junit-xml-parsing.spec.ts index 2b1b934..32c0636 100644 --- a/src/tests/junit-xml-parsing.spec.ts +++ b/src/tests/junit-xml-parsing.spec.ts @@ -10,7 +10,10 @@ describe('Junit XML parsing', () => { const xmlContent = await readFile(xmlPath, 'utf8') // This should not throw any exceptions - const testcases = await parseJUnitXml(xmlContent, xmlBasePath) + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'never', + }) // Verify that we got the expected number of test cases expect(testcases).toHaveLength(12) @@ -48,7 +51,10 @@ describe('Junit XML parsing', () => { const xmlPath = `${xmlBasePath}/comprehensive-test.xml` const xmlContent = await readFile(xmlPath, 'utf8') - const testcases = await parseJUnitXml(xmlContent, xmlBasePath) + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'never', + }) // Test specific scenarios from our comprehensive test const failureTests = testcases.filter((tc) => tc.status === 'failed') @@ -78,7 +84,10 @@ describe('Junit XML parsing', () => { const xmlPath = `${xmlBasePath}/empty-system-err.xml` const xmlContent = await readFile(xmlPath, 'utf8') - const testcases = await parseJUnitXml(xmlContent, xmlBasePath) + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(1) // Should parse as success (no failure/error/skipped present) @@ -92,7 +101,10 @@ describe('Junit XML parsing', () => { const xmlPath = `${xmlBasePath}/jest-failure-type-missing.xml` const xmlContent = await readFile(xmlPath, 'utf8') - const testcases = await parseJUnitXml(xmlContent, xmlBasePath) + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(3) // Verify test result types @@ -110,4 +122,127 @@ describe('Junit XML parsing', () => { expect(failedTest?.name).toContain('subtracts two numbers correctly') expect(failedTest?.message).toContain('expect(received).toBe(expected)') }) + + test('Should extract attachments from failure/error message attributes (WebDriverIO style)', async () => { + const xmlPath = `${xmlBasePath}/webdriverio-real.xml` + const xmlContent = await readFile(xmlPath, 'utf8') + + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'never', + }) + expect(testcases).toHaveLength(4) + + // Find the test case BD-055 which has a failure element with attachment in the message attribute + const bd055 = testcases.find((tc) => tc.name.includes('BD-055')) + expect(bd055).toBeDefined() + expect(bd055?.status).toBe('failed') + + // This test should have an attachment extracted from the failure message attribute + // This is the WebDriverIO style where attachments are embedded in the failure message + expect(bd055?.attachments.length).toBeGreaterThan(0) + expect(bd055?.attachments[0].filename).toContain('BD_055') + expect(bd055?.attachments[0].filename).toContain('.png') + }) + + test('Should include stdout/stderr when skipStdout and skipStderr are set to "never"', async () => { + const xmlPath = `${xmlBasePath}/empty-system-err.xml` + const xmlContent = await readFile(xmlPath, 'utf8') + + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'never', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + // Should include stdout content + expect(testcases[0].message).toContain('ViewManager initialized') + }) + + test('Should skip stdout for passed tests when skipStdout is set to "on-success"', async () => { + const xmlPath = `${xmlBasePath}/empty-system-err.xml` + const xmlContent = await readFile(xmlPath, 'utf8') + + const testcases = await parseJUnitXml(xmlContent, xmlBasePath, { + skipStdout: 'on-success', + skipStderr: 'never', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + // Should NOT include stdout content for passed tests + expect(testcases[0].message).not.toContain('ViewManager initialized') + expect(testcases[0].message).toBe('') + }) + + test('Should skip stderr for passed tests when skipStderr is set to "on-success"', async () => { + const xml = ` + + + + stdout content + stderr content + + +` + + const testcases = await parseJUnitXml(xml, xmlBasePath, { + skipStdout: 'never', + skipStderr: 'on-success', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + // Should include stdout but not stderr for passed tests + expect(testcases[0].message).toContain('stdout content') + expect(testcases[0].message).not.toContain('stderr content') + }) + + test('Should include stdout/stderr for failed tests even when skip options are set to "on-success"', async () => { + const xml = ` + + + + Failure details + stdout from failed test + stderr from failed test + + +` + + const testcases = await parseJUnitXml(xml, xmlBasePath, { + skipStdout: 'on-success', + skipStderr: 'on-success', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('failed') + // Should include both stdout and stderr for failed tests + expect(testcases[0].message).toContain('Failure details') + expect(testcases[0].message).toContain('stdout from failed test') + expect(testcases[0].message).toContain('stderr from failed test') + }) + + test('Should skip both stdout and stderr for passed tests when both skip options are set to "on-success"', async () => { + const xml = ` + + + + stdout content + stderr content + + +` + + const testcases = await parseJUnitXml(xml, xmlBasePath, { + skipStdout: 'on-success', + skipStderr: 'on-success', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + // Should not include stdout or stderr for passed tests + expect(testcases[0].message).toBe('') + }) }) diff --git a/src/tests/playwright-json-parsing.spec.ts b/src/tests/playwright-json-parsing.spec.ts index a42911c..a88850a 100644 --- a/src/tests/playwright-json-parsing.spec.ts +++ b/src/tests/playwright-json-parsing.spec.ts @@ -10,7 +10,10 @@ describe('Playwright JSON parsing', () => { const jsonContent = await readFile(jsonPath, 'utf8') // This should not throw any exceptions - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) // Verify that we got the expected number of test cases expect(testcases).toHaveLength(12) @@ -46,7 +49,10 @@ describe('Playwright JSON parsing', () => { const jsonPath = `${playwrightJsonBasePath}/empty-tsuite.json` const jsonContent = await readFile(jsonPath, 'utf8') - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) // Should only have the one test from ui.cart.spec.ts, not the empty ui.contents.spec.ts expect(testcases).toHaveLength(1) @@ -95,7 +101,10 @@ describe('Playwright JSON parsing', () => { ], }) - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(1) // Should use the last result (passed on retry) @@ -166,7 +175,10 @@ describe('Playwright JSON parsing', () => { ], }) - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(2) // Verify folder is set to top-level suite title @@ -225,7 +237,10 @@ describe('Playwright JSON parsing', () => { ], }) - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(1) // Verify ANSI codes are stripped from message @@ -330,7 +345,10 @@ describe('Playwright JSON parsing', () => { ], }) - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(3) // Test with annotation should have marker prefixed @@ -451,7 +469,10 @@ describe('Playwright JSON parsing', () => { ], }) - const testcases = await parsePlaywrightJson(jsonContent, '') + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) expect(testcases).toHaveLength(4) expect(testcases[0].status).toBe('passed') // expected @@ -459,4 +480,231 @@ describe('Playwright JSON parsing', () => { expect(testcases[2].status).toBe('passed') // flaky (passed on retry) expect(testcases[3].status).toBe('skipped') // skipped }) + + test('Should include stdout/stderr when skipStdout and skipStderr are set to "never"', async () => { + const jsonContent = JSON.stringify({ + suites: [ + { + title: 'test.spec.ts', + specs: [ + { + title: 'Passed test with output', + tags: [], + tests: [ + { + annotations: [], + expectedStatus: 'passed', + projectName: 'chromium', + results: [ + { + status: 'passed', + errors: [], + stdout: [{ text: 'stdout content' }], + stderr: [{ text: 'stderr content' }], + retry: 0, + attachments: [], + }, + ], + status: 'expected', + }, + ], + }, + ], + suites: [], + }, + ], + }) + + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'never', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + expect(testcases[0].message).toContain('stdout content') + expect(testcases[0].message).toContain('stderr content') + }) + + test('Should skip stdout for passed tests when skipStdout is set to "on-success"', async () => { + const jsonContent = JSON.stringify({ + suites: [ + { + title: 'test.spec.ts', + specs: [ + { + title: 'Passed test with output', + tags: [], + tests: [ + { + annotations: [], + expectedStatus: 'passed', + projectName: 'chromium', + results: [ + { + status: 'passed', + errors: [], + stdout: [{ text: 'stdout content' }], + stderr: [{ text: 'stderr content' }], + retry: 0, + attachments: [], + }, + ], + status: 'expected', + }, + ], + }, + ], + suites: [], + }, + ], + }) + + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'on-success', + skipStderr: 'never', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + expect(testcases[0].message).not.toContain('stdout content') + expect(testcases[0].message).toContain('stderr content') + }) + + test('Should skip stderr for passed tests when skipStderr is set to "on-success"', async () => { + const jsonContent = JSON.stringify({ + suites: [ + { + title: 'test.spec.ts', + specs: [ + { + title: 'Passed test with output', + tags: [], + tests: [ + { + annotations: [], + expectedStatus: 'passed', + projectName: 'chromium', + results: [ + { + status: 'passed', + errors: [], + stdout: [{ text: 'stdout content' }], + stderr: [{ text: 'stderr content' }], + retry: 0, + attachments: [], + }, + ], + status: 'expected', + }, + ], + }, + ], + suites: [], + }, + ], + }) + + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'never', + skipStderr: 'on-success', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + expect(testcases[0].message).toContain('stdout content') + expect(testcases[0].message).not.toContain('stderr content') + }) + + test('Should include stdout/stderr for failed tests even when skip options are set to "on-success"', async () => { + const jsonContent = JSON.stringify({ + suites: [ + { + title: 'test.spec.ts', + specs: [ + { + title: 'Failed test with output', + tags: [], + tests: [ + { + annotations: [], + expectedStatus: 'passed', + projectName: 'chromium', + results: [ + { + status: 'failed', + errors: [{ message: 'Test failed' }], + stdout: [{ text: 'stdout from failed test' }], + stderr: [{ text: 'stderr from failed test' }], + retry: 0, + attachments: [], + }, + ], + status: 'unexpected', + }, + ], + }, + ], + suites: [], + }, + ], + }) + + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'on-success', + skipStderr: 'on-success', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('failed') + expect(testcases[0].message).toContain('Test failed') + expect(testcases[0].message).toContain('stdout from failed test') + expect(testcases[0].message).toContain('stderr from failed test') + }) + + test('Should skip both stdout and stderr for passed tests when both skip options are set to "on-success"', async () => { + const jsonContent = JSON.stringify({ + suites: [ + { + title: 'test.spec.ts', + specs: [ + { + title: 'Passed test with output', + tags: [], + tests: [ + { + annotations: [], + expectedStatus: 'passed', + projectName: 'chromium', + results: [ + { + status: 'passed', + errors: [], + stdout: [{ text: 'stdout content' }], + stderr: [{ text: 'stderr content' }], + retry: 0, + attachments: [], + }, + ], + status: 'expected', + }, + ], + }, + ], + suites: [], + }, + ], + }) + + const testcases = await parsePlaywrightJson(jsonContent, '', { + skipStdout: 'on-success', + skipStderr: 'on-success', + }) + + expect(testcases).toHaveLength(1) + expect(testcases[0].status).toBe('passed') + expect(testcases[0].message).not.toContain('stdout content') + expect(testcases[0].message).not.toContain('stderr content') + expect(testcases[0].message).toBe('') + }) }) diff --git a/src/utils/result-upload/ResultUploadCommandHandler.ts b/src/utils/result-upload/ResultUploadCommandHandler.ts index d05db1d..43aa3ef 100644 --- a/src/utils/result-upload/ResultUploadCommandHandler.ts +++ b/src/utils/result-upload/ResultUploadCommandHandler.ts @@ -12,7 +12,18 @@ import { parsePlaywrightJson } from './playwrightJsonParser' export type UploadCommandType = 'junit-upload' | 'playwright-json-upload' -export type Parser = (data: string, attachmentBaseDirectory: string) => Promise +export type SkipOutputOption = 'on-success' | 'never' + +export interface ParserOptions { + skipStdout: SkipOutputOption + skipStderr: SkipOutputOption +} + +export type Parser = ( + data: string, + attachmentBaseDirectory: string, + options: ParserOptions +) => Promise export interface ResultUploadCommandArgs { type: UploadCommandType @@ -22,6 +33,8 @@ export interface ResultUploadCommandArgs { force: boolean attachments: boolean ignoreUnmatched: boolean + skipReportStdout: SkipOutputOption + skipReportStderr: SkipOutputOption } interface FileResults { @@ -91,9 +104,14 @@ export class ResultUploadCommandHandler { protected async parseFiles(): Promise { const results: FileResults[] = [] + const parserOptions: ParserOptions = { + skipStdout: this.args.skipReportStdout, + skipStderr: this.args.skipReportStderr, + } + for (const file of this.args.files) { const fileData = readFileSync(file).toString() - const fileResults = await commandTypeParsers[this.type](fileData, dirname(file)) + const fileResults = await commandTypeParsers[this.type](fileData, dirname(file), parserOptions) results.push({ file, results: fileResults }) } diff --git a/src/utils/result-upload/junitXmlParser.ts b/src/utils/result-upload/junitXmlParser.ts index a2d75d9..45d1bc4 100644 --- a/src/utils/result-upload/junitXmlParser.ts +++ b/src/utils/result-upload/junitXmlParser.ts @@ -2,7 +2,7 @@ import escapeHtml from 'escape-html' import xml from 'xml2js' import z from 'zod' import { Attachment, TestCaseResult } from './types' -import { Parser } from './ResultUploadCommandHandler' +import { Parser, ParserOptions } from './ResultUploadCommandHandler' import { ResultStatus } from '../../api/schemas' import { getAttachments } from './utils' @@ -75,7 +75,8 @@ const junitXmlSchema = z.object({ export const parseJUnitXml: Parser = async ( xmlString: string, - attachmentBaseDirectory: string + attachmentBaseDirectory: string, + options: ParserOptions ): Promise => { const xmlData = await xml.parseStringPromise(xmlString, { explicitCharkey: true, @@ -90,7 +91,7 @@ export const parseJUnitXml: Parser = async ( for (const suite of validated.testsuites.testsuite) { for (const tcase of suite.testcase ?? []) { - const result = getResult(tcase) + const result = getResult(tcase, options) const index = testcases.push({ folder: suite.$.name ?? '', @@ -99,17 +100,42 @@ export const parseJUnitXml: Parser = async ( attachments: [], }) - 1 - const attachmentPaths = [] + const attachmentPaths = new Set() + + // Extract from system-out for (const out of tcase['system-out'] || []) { const text = typeof out === 'string' ? out : out._ ?? '' if (text) { - attachmentPaths.push(...extractAttachmentPaths(text)) + extractAttachmentPaths(text).forEach((path) => attachmentPaths.add(path)) + } + } + + // Helper function to extract attachments from failure/error/skipped elements + const extractAttachmentsFromElements = ( + elements: (string | { _?: string; $?: { message?: string } })[] | undefined + ) => { + for (const element of elements || []) { + if (typeof element === 'string') { + extractAttachmentPaths(element).forEach((path) => attachmentPaths.add(path)) + } else if (typeof element === 'object') { + if (element.$?.message) { + extractAttachmentPaths(element.$.message).forEach((path) => attachmentPaths.add(path)) + } + if (element._) { + extractAttachmentPaths(element._).forEach((path) => attachmentPaths.add(path)) + } + } } } + // Extract attachments from failure, error, and skipped elements + extractAttachmentsFromElements(tcase.failure) + extractAttachmentsFromElements(tcase.error) + extractAttachmentsFromElements(tcase.skipped) + attachmentsPromises.push({ index, - promise: getAttachments(attachmentPaths, attachmentBaseDirectory), + promise: getAttachments(Array.from(attachmentPaths), attachmentBaseDirectory), }) } } @@ -124,40 +150,47 @@ export const parseJUnitXml: Parser = async ( } const getResult = ( - tcase: z.infer + tcase: z.infer, + options: ParserOptions ): { status: ResultStatus; message: string } => { const err = tcase['system-err'] || [] const out = tcase['system-out'] || [] - if (tcase.error) - return { - status: 'blocked', - message: getResultMessage( - { result: tcase.error, type: 'code' }, - { result: out, type: 'code' }, - { result: err, type: 'code' } - ), - } - if (tcase.failure) - return { - status: 'failed', - message: getResultMessage( - { result: tcase.failure, type: 'code' }, - { result: out, type: 'code' }, - { result: err, type: 'code' } - ), - } - if (tcase.skipped) - return { - status: 'skipped', - message: getResultMessage( - { result: tcase.skipped, type: 'code' }, - { result: out, type: 'code' }, - { result: err, type: 'code' } - ), - } + + // Determine test status first + let status: ResultStatus + let mainResult: GetResultMessageOption | undefined + + if (tcase.error) { + status = 'blocked' + mainResult = { result: tcase.error, type: 'code' } + } else if (tcase.failure) { + status = 'failed' + mainResult = { result: tcase.failure, type: 'code' } + } else if (tcase.skipped) { + status = 'skipped' + mainResult = { result: tcase.skipped, type: 'code' } + } else { + status = 'passed' + } + + // Conditionally include stdout/stderr based on status and options + const includeStdout = !(status === 'passed' && options.skipStdout === 'on-success') + const includeStderr = !(status === 'passed' && options.skipStderr === 'on-success') + + const messageOptions: GetResultMessageOption[] = [] + if (mainResult) { + messageOptions.push(mainResult) + } + if (includeStdout) { + messageOptions.push({ result: out, type: 'code' }) + } + if (includeStderr) { + messageOptions.push({ result: err, type: 'code' }) + } + return { - status: 'passed', - message: getResultMessage({ result: out, type: 'code' }, { result: err, type: 'code' }), + status, + message: getResultMessage(...messageOptions), } } diff --git a/src/utils/result-upload/playwrightJsonParser.ts b/src/utils/result-upload/playwrightJsonParser.ts index 44c6351..c322dfc 100644 --- a/src/utils/result-upload/playwrightJsonParser.ts +++ b/src/utils/result-upload/playwrightJsonParser.ts @@ -2,7 +2,7 @@ import z from 'zod' import escapeHtml from 'escape-html' import stripAnsi from 'strip-ansi' import { Attachment, TestCaseResult } from './types' -import { Parser } from './ResultUploadCommandHandler' +import { Parser, ParserOptions } from './ResultUploadCommandHandler' import { ResultStatus } from '../../api/schemas' import { parseTCaseUrl } from '../misc' import { getAttachments } from './utils' @@ -80,7 +80,8 @@ const playwrightJsonSchema = z.object({ export const parsePlaywrightJson: Parser = async ( jsonString: string, - attachmentBaseDirectory: string + attachmentBaseDirectory: string, + options: ParserOptions ): Promise => { const jsonData = JSON.parse(jsonString) const validated = playwrightJsonSchema.parse(jsonData) @@ -101,6 +102,7 @@ export const parsePlaywrightJson: Parser = async ( } const markerFromAnnotations = getTCaseMarkerFromAnnotations(test.annotations) // What about result.annotations? + const status = mapPlaywrightStatus(test.status) const numTestcases = testcases.push({ // Use markerFromAnnotations as name prefix, so that it takes precedence over any // other marker present. Prefixing it to name also helps in detectProjectCode @@ -108,8 +110,8 @@ export const parsePlaywrightJson: Parser = async ( ? `${markerFromAnnotations}: ${titlePrefix}${spec.title}` : `${titlePrefix}${spec.title}`, folder: topLevelSuite, - status: mapPlaywrightStatus(test.status), - message: buildMessage(result), + status, + message: buildMessage(result, status, options), attachments: [], }) @@ -177,7 +179,7 @@ const mapPlaywrightStatus = (status: Status): ResultStatus => { } } -const buildMessage = (result: Result) => { +const buildMessage = (result: Result, status: ResultStatus, options: ParserOptions) => { let message = '' if (result.retry) { @@ -194,7 +196,9 @@ const buildMessage = (result: Result) => { }) } - if (result.stdout.length > 0) { + // Conditionally include stdout based on status and options + const includeStdout = !(status === 'passed' && options.skipStdout === 'on-success') + if (includeStdout && result.stdout.length > 0) { message += '

Output:

' result.stdout.forEach((out) => { const content = 'text' in out ? out.text : out.buffer @@ -205,7 +209,9 @@ const buildMessage = (result: Result) => { }) } - if (result.stderr.length > 0) { + // Conditionally include stderr based on status and options + const includeStderr = !(status === 'passed' && options.skipStderr === 'on-success') + if (includeStderr && result.stderr.length > 0) { message += '

Errors (stderr):

' result.stderr.forEach((err) => { const content = 'text' in err ? err.text : err.buffer