From 8e1cb3a33c0940958fcabb7ffb783af766f32887 Mon Sep 17 00:00:00 2001 From: Himanshu Rai Date: Mon, 29 Dec 2025 18:32:36 +0530 Subject: [PATCH 1/2] Add time tracking information to uploaded results --- src/api/schemas.ts | 1 + .../playwright-json/comprehensive-test.json | 12 ++++++ .../playwright-json/empty-tsuite.json | 1 + .../playwright-json/matching-tcases.json | 5 +++ .../playwright-json/missing-attachments.json | 5 +++ .../playwright-json/missing-tcases.json | 6 +++ src/tests/junit-xml-parsing.spec.ts | 12 ++++-- src/tests/playwright-json-parsing.spec.ts | 41 ++++++++++++++++++- src/utils/result-upload/ResultUploader.ts | 1 + src/utils/result-upload/junitXmlParser.ts | 4 +- .../result-upload/playwrightJsonParser.ts | 2 + src/utils/result-upload/types.ts | 1 + 12 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/api/schemas.ts b/src/api/schemas.ts index 903ff48..16e809c 100644 --- a/src/api/schemas.ts +++ b/src/api/schemas.ts @@ -23,6 +23,7 @@ export interface CreateResultsRequestItem { tcaseId: string status: ResultStatus comment?: string + timeTaken: number | null // In milliseconds } export interface CreateResultsRequest { diff --git a/src/tests/fixtures/playwright-json/comprehensive-test.json b/src/tests/fixtures/playwright-json/comprehensive-test.json index 37be6cd..ebda228 100644 --- a/src/tests/fixtures/playwright-json/comprehensive-test.json +++ b/src/tests/fixtures/playwright-json/comprehensive-test.json @@ -22,6 +22,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [] } ], @@ -48,6 +49,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 6200, "attachments": [] } ], @@ -74,6 +76,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 2000, "attachments": [] } ], @@ -106,6 +109,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1400, "attachments": [] } ], @@ -132,6 +136,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 500, "attachments": [] } ], @@ -158,6 +163,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 5050, "attachments": [] } ], @@ -186,6 +192,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 0, "attachments": [] } ], @@ -213,6 +220,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 0, "attachments": [] } ], @@ -239,6 +247,7 @@ ], "stderr": [], "retry": 0, + "duration": 0, "attachments": [] } ], @@ -270,6 +279,7 @@ ], "stderr": [], "retry": 0, + "duration": 0, "attachments": [] } ], @@ -296,6 +306,7 @@ ], "stderr": [], "retry": 0, + "duration": 10000, "attachments": [] } ], @@ -318,6 +329,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "screenshot", diff --git a/src/tests/fixtures/playwright-json/empty-tsuite.json b/src/tests/fixtures/playwright-json/empty-tsuite.json index a6ec7c7..cffc021 100644 --- a/src/tests/fixtures/playwright-json/empty-tsuite.json +++ b/src/tests/fixtures/playwright-json/empty-tsuite.json @@ -18,6 +18,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 0, "attachments": [] } ], diff --git a/src/tests/fixtures/playwright-json/matching-tcases.json b/src/tests/fixtures/playwright-json/matching-tcases.json index c1254f7..96728e2 100644 --- a/src/tests/fixtures/playwright-json/matching-tcases.json +++ b/src/tests/fixtures/playwright-json/matching-tcases.json @@ -18,6 +18,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -46,6 +47,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -80,6 +82,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -112,6 +115,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -140,6 +144,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", diff --git a/src/tests/fixtures/playwright-json/missing-attachments.json b/src/tests/fixtures/playwright-json/missing-attachments.json index 69ec500..48d85ca 100644 --- a/src/tests/fixtures/playwright-json/missing-attachments.json +++ b/src/tests/fixtures/playwright-json/missing-attachments.json @@ -18,6 +18,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -46,6 +47,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -80,6 +82,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -112,6 +115,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -140,6 +144,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", diff --git a/src/tests/fixtures/playwright-json/missing-tcases.json b/src/tests/fixtures/playwright-json/missing-tcases.json index ba5c462..8cd343b 100644 --- a/src/tests/fixtures/playwright-json/missing-tcases.json +++ b/src/tests/fixtures/playwright-json/missing-tcases.json @@ -18,6 +18,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -46,6 +47,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -80,6 +82,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -108,6 +111,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -140,6 +144,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", @@ -168,6 +173,7 @@ "stdout": [], "stderr": [], "retry": 0, + "duration": 1000, "attachments": [ { "name": "attachment", diff --git a/src/tests/junit-xml-parsing.spec.ts b/src/tests/junit-xml-parsing.spec.ts index fdf4739..0f58de9 100644 --- a/src/tests/junit-xml-parsing.spec.ts +++ b/src/tests/junit-xml-parsing.spec.ts @@ -1,6 +1,6 @@ import { expect, test, describe } from 'vitest' +import { readFile } from 'node:fs/promises' import { parseJUnitXml } from '../utils/result-upload/junitXmlParser' -import { readFile } from 'fs/promises' const xmlBasePath = './src/tests/fixtures/junit-xml' @@ -46,6 +46,7 @@ describe('Junit XML parsing', () => { expect(tc).toHaveProperty('status') expect(tc).toHaveProperty('message') expect(tc).toHaveProperty('attachments') + expect(tc).toHaveProperty('timeTaken') expect(Array.isArray(tc.attachments)).toBe(true) }) }) @@ -184,7 +185,7 @@ describe('Junit XML parsing', () => { const xml = ` - + stdout content stderr content @@ -198,6 +199,7 @@ describe('Junit XML parsing', () => { expect(testcases).toHaveLength(1) expect(testcases[0].status).toBe('passed') + expect(testcases[0].timeTaken).toBe(10500) // Should include stdout but not stderr for passed tests expect(testcases[0].message).toContain('stdout content') expect(testcases[0].message).not.toContain('stderr content') @@ -207,7 +209,7 @@ describe('Junit XML parsing', () => { const xml = ` - + Failure details stdout from failed test stderr from failed test @@ -222,6 +224,7 @@ describe('Junit XML parsing', () => { expect(testcases).toHaveLength(1) expect(testcases[0].status).toBe('failed') + expect(testcases[0].timeTaken).toBe(null) // 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') @@ -232,7 +235,7 @@ describe('Junit XML parsing', () => { const xml = ` - + stdout content stderr content @@ -246,6 +249,7 @@ describe('Junit XML parsing', () => { expect(testcases).toHaveLength(1) expect(testcases[0].status).toBe('passed') + expect(testcases[0].timeTaken).toBe(1000) // 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 c6d51a5..cc10008 100644 --- a/src/tests/playwright-json-parsing.spec.ts +++ b/src/tests/playwright-json-parsing.spec.ts @@ -44,6 +44,7 @@ describe('Playwright JSON parsing', () => { expect(tc).toHaveProperty('status') expect(tc).toHaveProperty('message') expect(tc).toHaveProperty('attachments') + expect(tc).toHaveProperty('timeTaken') expect(Array.isArray(tc.attachments)).toBe(true) }) }) @@ -60,6 +61,7 @@ describe('Playwright JSON parsing', () => { // Should only have the one test from ui.cart.spec.ts, not the empty ui.contents.spec.ts expect(testcases).toHaveLength(1) expect(testcases[0].name).toContain('Test cart TEST-002') + expect(testcases[0].timeTaken).toBe(null) }) test('Should use last result when there are retries', async () => { @@ -83,6 +85,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1300, attachments: [], }, { @@ -91,6 +94,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 1, + duration: 1200, attachments: [], }, ], @@ -111,6 +115,7 @@ describe('Playwright JSON parsing', () => { expect(testcases).toHaveLength(1) // Should use the last result (passed on retry) + expect(testcases[0].timeTaken).toBe(1200) expect(testcases[0].status).toBe('passed') expect(testcases[0].message).toContain('Test passed in 2 attempts') }) @@ -136,6 +141,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, ], @@ -163,6 +169,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 5100, attachments: [], }, ], @@ -191,6 +198,10 @@ describe('Playwright JSON parsing', () => { // Verify nested test has suite title as prefix expect(testcases[1].name).toContain('Nested Suite') expect(testcases[1].name).toContain('Nested test') + + // Verify time taken is set to duration of the test + expect(testcases[0].timeTaken).toBe(1000) + expect(testcases[1].timeTaken).toBe(5100) }) test('Should strip ANSI escape codes from errors and output', async () => { @@ -227,6 +238,7 @@ describe('Playwright JSON parsing', () => { }, ], retry: 0, + duration: 1000, attachments: [], }, ], @@ -286,6 +298,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, ], @@ -308,6 +321,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, ], @@ -335,6 +349,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, ], @@ -385,6 +400,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, ], @@ -407,7 +423,14 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, - attachments: [], + duration: 1000, + attachments: [ + { + name: 'screenshot', + contentType: 'image/png', + path: '../test-results/ui.cart-Test-cart-chromium/test-finished-1.png', + }, + ], }, ], status: 'unexpected', @@ -429,6 +452,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, { @@ -437,6 +461,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 1, + duration: 1000, attachments: [], }, ], @@ -459,6 +484,7 @@ describe('Playwright JSON parsing', () => { stdout: [], stderr: [], retry: 0, + duration: 1000, attachments: [], }, ], @@ -505,6 +531,7 @@ describe('Playwright JSON parsing', () => { stdout: [{ text: 'stdout content' }], stderr: [{ text: 'stderr content' }], retry: 0, + duration: 1000, attachments: [], }, ], @@ -550,7 +577,14 @@ describe('Playwright JSON parsing', () => { stdout: [{ text: 'stdout content' }], stderr: [{ text: 'stderr content' }], retry: 0, - attachments: [], + duration: 1000, + attachments: [ + { + name: 'screenshot', + contentType: 'image/png', + path: '../test-results/ui.cart-Test-cart-chromium/test-finished-1.png', + }, + ], }, ], status: 'expected', @@ -595,6 +629,7 @@ describe('Playwright JSON parsing', () => { stdout: [{ text: 'stdout content' }], stderr: [{ text: 'stderr content' }], retry: 0, + duration: 1000, attachments: [], }, ], @@ -640,6 +675,7 @@ describe('Playwright JSON parsing', () => { stdout: [{ text: 'stdout from failed test' }], stderr: [{ text: 'stderr from failed test' }], retry: 0, + duration: 1000, attachments: [], }, ], @@ -686,6 +722,7 @@ describe('Playwright JSON parsing', () => { stdout: [{ text: 'stdout content' }], stderr: [{ text: 'stderr content' }], retry: 0, + duration: 1000, attachments: [], }, ], diff --git a/src/utils/result-upload/ResultUploader.ts b/src/utils/result-upload/ResultUploader.ts index 55f2485..dfcb16c 100644 --- a/src/utils/result-upload/ResultUploader.ts +++ b/src/utils/result-upload/ResultUploader.ts @@ -246,6 +246,7 @@ ${chalk.yellow('To fix this issue, choose one of the following options:')} tcaseId: tcase.id, status: result.status, comment: result.message, + timeTaken: result.timeTaken, })), }) diff --git a/src/utils/result-upload/junitXmlParser.ts b/src/utils/result-upload/junitXmlParser.ts index 6bac18c..348a005 100644 --- a/src/utils/result-upload/junitXmlParser.ts +++ b/src/utils/result-upload/junitXmlParser.ts @@ -92,11 +92,13 @@ export const parseJUnitXml: Parser = async ( for (const suite of validated.testsuites.testsuite) { for (const tcase of suite.testcase ?? []) { const result = getResult(tcase, options) + const timeTakenSeconds = tcase.$.time ? Number.parseFloat(tcase.$.time) : null const index = testcases.push({ + ...result, folder: suite.$.name ?? '', name: tcase.$.name ?? '', - ...result, + timeTaken: timeTakenSeconds ? timeTakenSeconds * 1000 : null, attachments: [], }) - 1 diff --git a/src/utils/result-upload/playwrightJsonParser.ts b/src/utils/result-upload/playwrightJsonParser.ts index c322dfc..ae9444b 100644 --- a/src/utils/result-upload/playwrightJsonParser.ts +++ b/src/utils/result-upload/playwrightJsonParser.ts @@ -39,6 +39,7 @@ const resultSchema = z.object({ stdout: stdioEntrySchema.array(), stderr: stdioEntrySchema.array(), retry: z.number(), + duration: z.number(), attachments: attachmentSchema.array().optional(), annotations: annotationSchema.array().optional(), }) @@ -112,6 +113,7 @@ export const parsePlaywrightJson: Parser = async ( folder: topLevelSuite, status, message: buildMessage(result, status, options), + timeTaken: result.duration ? result.duration : null, attachments: [], }) diff --git a/src/utils/result-upload/types.ts b/src/utils/result-upload/types.ts index 0a51eb6..d46e3fc 100644 --- a/src/utils/result-upload/types.ts +++ b/src/utils/result-upload/types.ts @@ -14,5 +14,6 @@ export interface TestCaseResult { folder: string status: ResultStatus message: string + timeTaken: number | null // In milliseconds attachments: Attachment[] } From eb3e8dcc5b931547bf6145ad7a369c9d002931ba Mon Sep 17 00:00:00 2001 From: Himanshu Rai Date: Tue, 30 Dec 2025 17:58:07 +0530 Subject: [PATCH 2/2] Treat 0 time as 0 and not null --- src/tests/junit-xml-parsing.spec.ts | 6 +++--- src/tests/playwright-json-parsing.spec.ts | 2 +- src/utils/result-upload/junitXmlParser.ts | 7 +++++-- src/utils/result-upload/playwrightJsonParser.ts | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/tests/junit-xml-parsing.spec.ts b/src/tests/junit-xml-parsing.spec.ts index 0f58de9..4b16e35 100644 --- a/src/tests/junit-xml-parsing.spec.ts +++ b/src/tests/junit-xml-parsing.spec.ts @@ -224,7 +224,7 @@ describe('Junit XML parsing', () => { expect(testcases).toHaveLength(1) expect(testcases[0].status).toBe('failed') - expect(testcases[0].timeTaken).toBe(null) + expect(testcases[0].timeTaken).toBe(0) // 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') @@ -235,7 +235,7 @@ describe('Junit XML parsing', () => { const xml = ` - + stdout content stderr content @@ -249,7 +249,7 @@ describe('Junit XML parsing', () => { expect(testcases).toHaveLength(1) expect(testcases[0].status).toBe('passed') - expect(testcases[0].timeTaken).toBe(1000) + expect(testcases[0].timeTaken).toBe(null) // 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 cc10008..89f2184 100644 --- a/src/tests/playwright-json-parsing.spec.ts +++ b/src/tests/playwright-json-parsing.spec.ts @@ -61,7 +61,7 @@ describe('Playwright JSON parsing', () => { // Should only have the one test from ui.cart.spec.ts, not the empty ui.contents.spec.ts expect(testcases).toHaveLength(1) expect(testcases[0].name).toContain('Test cart TEST-002') - expect(testcases[0].timeTaken).toBe(null) + expect(testcases[0].timeTaken).toBe(0) }) test('Should use last result when there are retries', async () => { diff --git a/src/utils/result-upload/junitXmlParser.ts b/src/utils/result-upload/junitXmlParser.ts index 348a005..21d0712 100644 --- a/src/utils/result-upload/junitXmlParser.ts +++ b/src/utils/result-upload/junitXmlParser.ts @@ -92,13 +92,16 @@ export const parseJUnitXml: Parser = async ( for (const suite of validated.testsuites.testsuite) { for (const tcase of suite.testcase ?? []) { const result = getResult(tcase, options) - const timeTakenSeconds = tcase.$.time ? Number.parseFloat(tcase.$.time) : null + const timeTakenSeconds = Number.parseFloat(tcase.$.time ?? '') const index = testcases.push({ ...result, folder: suite.$.name ?? '', name: tcase.$.name ?? '', - timeTaken: timeTakenSeconds ? timeTakenSeconds * 1000 : null, + timeTaken: + Number.isFinite(timeTakenSeconds) && timeTakenSeconds >= 0 + ? timeTakenSeconds * 1000 + : null, attachments: [], }) - 1 diff --git a/src/utils/result-upload/playwrightJsonParser.ts b/src/utils/result-upload/playwrightJsonParser.ts index ae9444b..6ba15ff 100644 --- a/src/utils/result-upload/playwrightJsonParser.ts +++ b/src/utils/result-upload/playwrightJsonParser.ts @@ -113,7 +113,7 @@ export const parsePlaywrightJson: Parser = async ( folder: topLevelSuite, status, message: buildMessage(result, status, options), - timeTaken: result.duration ? result.duration : null, + timeTaken: result.duration, attachments: [], })