Repository: onsi/ginkgo

Issue:
Convert json output into test2json output format
Hello, 
I know there are already a couple of issues and some discussions related to that topic however I wanted to ask this question. Since v2 Ginkgo produces its own json format as an output of the test results.

We are using Sonarqube to report back test coverage and test reports and unfortunately, at least as far as I know, Sonar expects the test reports in the test2json format produced by `go test`: 

> Path to test execution reports generated by Go with '-json' key, available since go1.10 (e.g.: go test -json > test-report.out).

One the one hand we could only use `go test` to get the desired outputs for test reports & coverage however on the other hand the output Ginkgo produces is just much more detailed that we also would like to see (in our CI pipeline). Therefore, our mitigation currently is to execute both steps (`go test` & `ginkgo`) to have the best of both but as said, should only be a mitigation.

So, my questions would be: 
1. Someone else have already faced & solved this issue? Could a `ReportAfterSuite` convert the report back into the test2json format?
2. Would it be a feasible that Ginkgo can bring back the support for a test2json output format?


Discussion:
@onsi: hey there @stotz89 

I don't have a lot of experience/context with the `test2json` format though looking through it it does seem to be oriented towards streaming test information vs generating the comprehensive final report.  With that said, I could imagine custom code in `ReportAfterSuite` generating ` test2json` formatted report that probably gets close enough.  I could also imagine a simple post-processing tool that can take a ginkgo generated JSON file and produce a `test2json` formatted file.

> Would it be a feasible that Ginkgo can bring back the support for a test2json output format?

Just to clarify - there never has been support for the `test2json` format.  Ginkgo's JSON format is new to V2 and did not displace/replace anything prior to v2.  You can actually run `go test -json > test-report.out` with a Gingko suite but you'll simply get one test in the report: the test function in the suite's [bootstrap](https://onsi.github.io/ginkgo/#bootstrapping-a-suite).

As for how to add support for this in Ginkgo I think I'd lean twoards something like a new function in the `reporters` package that looks like:

```
func GenerateGoTestJSONReport(report types.Report, filename string)  error {
    // walk report and generate test2json-compatible objects
    // JSON-encode the objects into filename
}
```

Then users who want a `test2json` formatted report could simply add:

```
ReportAfterSuite("test2json", func(report types.Report) {
   Expect( reporters.GenerateGoTestJSONReport(report, "path/to/report.json")).To(Succeed())
})
```

Would you be interested in implementing something like `GenerateGoTestJSONReport`?@stotz89: Hi @onsi,
thanks for getting back. Sounds good to me - let me see if I find the time to write such a function!
@pseudo-su: Would it not make sense to support this via an opition like `--gojson-report=xxx` or `--test2json-report=xx` to mirror/match `--junit-report=xx.junit.xml ` and `--teamcity-report=xx.teamcity`?

Solution patch:
diff --git a/docs/index.md b/docs/index.md
index 6895608..c92db76 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -3716,22 +3716,25 @@ The resulting JSON file encodes an array of `types.Report`.  Each entry in that
 
 When possible, we recommend building tooling on top of Ginkgo's JSON format and using Ginkgo's `types` package directly to access the suite and spec reports.  The structs in the package include several helper functions to interpret the report.
 
-Ginkgo also supports generating JUnit reports with
+Ginkgo also supports generating output in additional formats, however Ginkgo specs carry much more metadata than can easily be mapped onto different output reports formats, so information may be lost and/or a bit harder to decode than using Ginkgo's native JSON format.
 
 ```bash
+# JUnit report
 ginkgo --junit-report=report.xml
+# Teamcity report
+ginkgo --teamcity-report=report.teamcity
+# Go json report (same format as `go test -json`)
+ginkgo --gojson-report=report.go.json
 ```
 
-The JUnit report is compatible with the JUnit specification, however Ginkgo specs carry much more metadata than can be easily mapped onto the JUnit spec so some information is lost and/or a bit harder to decode than using Ginkgo's native JSON format.  Nonetheless, Ginkgo does its best to populate as much of the JUnit report as possible.  This includes adding additional metadata using [labels](#spec-labels) - in particular if you provide a label of the form `Label("owner:XYZ")`, the generating JUnit spec will set the `Owner` attribute to `XYZ`.
+Ginkgo does it's best to populate relevant fields and attributes across different report formats. This includes adding additional metadata using [labels](#spec-labels), in particular if you provide a label of the form `Label("owner:XYZ")`, the generated JUnit report will set the `Owner` attribute to `XYZ`.
 
-Ginkgo also supports Teamcity reports with `ginkgo --teamcity-report=report.teamcity` though, again, the Teamcity spec makes it difficult to capture all the spec metadata.
-
-All the machine-readable reports include the full `-vv` version of the timeline for all specs.  This allows you to run Ginkgo in CI with the normal verbosity setting but still get all the detailed information in the machine-readable format.
+All the machine-readable reports include the full `-vv` version of the timeline for all specs. This allows you to run Ginkgo in CI with the normal verbosity setting but still get all the detailed information in the machine-readable format.
 
 Of course, you can generate multiple formats simultaneously by passing in multiple flags:
 
 ```bash
-ginkgo --json-report=report.json --junit-report=report.xml
+ginkgo --json-report=report.json --junit-report=report.xml --gojson-report=report.go.json
 ```
 
 By default, when any of these command-line report flags are provided Ginkgo will generate a single report file, per format, at the passed-in file name.  If Ginkgo is running multiple suites (e.g. `ginkgo -r --json-report=report.json`) then _all_ the suite reports will be encoded in the single report file.
diff --git a/ginkgo/internal/profiles_and_reports.go b/ginkgo/internal/profiles_and_reports.go
index 8e16d2b..f3439a3 100644
--- a/ginkgo/internal/profiles_and_reports.go
+++ b/ginkgo/internal/profiles_and_reports.go
@@ -90,6 +90,9 @@ func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIC
 	if reporterConfig.JSONReport != "" {
 		reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JSONReport, GenerateFunc: reporters.GenerateJSONReport, MergeFunc: reporters.MergeAndCleanupJSONReports})
 	}
+	if reporterConfig.GoJSONReport != "" {
+		reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.GoJSONReport, GenerateFunc: reporters.GenerateGoTestJSONReport, MergeFunc: reporters.MergeAndCleanupGoTestJSONReports})
+	}
 	if reporterConfig.JUnitReport != "" {
 		reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JUnitReport, GenerateFunc: reporters.GenerateJUnitReport, MergeFunc: reporters.MergeAndCleanupJUnitReports})
 	}
diff --git a/ginkgo/internal/run.go b/ginkgo/internal/run.go
index 41052ea..30d8096 100644
--- a/ginkgo/internal/run.go
+++ b/ginkgo/internal/run.go
@@ -107,6 +107,9 @@ func runSerial(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig t
 	if reporterConfig.JSONReport != "" {
 		reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0)
 	}
+	if reporterConfig.GoJSONReport != "" {
+		reporterConfig.GoJSONReport = AbsPathForGeneratedAsset(reporterConfig.GoJSONReport, suite, cliConfig, 0)
+	}
 	if reporterConfig.JUnitReport != "" {
 		reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0)
 	}
@@ -179,6 +182,9 @@ func runParallel(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig
 	if reporterConfig.JSONReport != "" {
 		reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0)
 	}
+	if reporterConfig.GoJSONReport != "" {
+		reporterConfig.GoJSONReport = AbsPathForGeneratedAsset(reporterConfig.GoJSONReport, suite, cliConfig, 0)
+	}
 	if reporterConfig.JUnitReport != "" {
 		reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0)
 	}
diff --git a/go.mod b/go.mod
index 9866575..431219c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,12 @@ go 1.23.0
 
 require (
 	github.com/Masterminds/semver/v3 v3.4.0
+	github.com/gkampitakis/go-snaps v0.5.14
 	github.com/go-logr/logr v1.4.3
 	github.com/go-task/slim-sprig/v3 v3.0.0
 	github.com/google/pprof v0.0.0-20250403155104-27863c87afa6
+	github.com/joshdk/go-junit v1.0.0
+	github.com/mfridman/tparse v0.18.0
 	github.com/onsi/gomega v1.38.2
 	go.uber.org/automaxprocs v1.6.0
 	golang.org/x/sys v0.35.0
@@ -14,10 +17,22 @@ require (
 )
 
 require (
+	github.com/gkampitakis/ciinfo v0.3.2 // indirect
+	github.com/gkampitakis/go-diff v1.3.2 // indirect
+	github.com/goccy/go-yaml v1.18.0 // indirect
 	github.com/google/go-cmp v0.7.0 // indirect
+	github.com/kr/pretty v0.3.1 // indirect
 	github.com/kr/text v0.2.0 // indirect
+	github.com/maruel/natural v1.1.1 // indirect
+	github.com/rogpeppe/go-internal v1.13.1 // indirect
+	github.com/tidwall/gjson v1.18.0 // indirect
+	github.com/tidwall/match v1.1.1 // indirect
+	github.com/tidwall/pretty v1.2.1 // indirect
+	github.com/tidwall/sjson v1.2.5 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/mod v0.27.0 // indirect
 	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sync v0.16.0 // indirect
 	golang.org/x/text v0.28.0 // indirect
 	google.golang.org/protobuf v1.36.7 // indirect
 )
diff --git a/go.sum b/go.sum
index 36a23df..ebc3bc8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,34 +1,69 @@
 github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
+github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
+github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
+github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
+github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo=
+github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
 github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
+github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
 github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
+github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
+github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
+github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
+github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
 github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
 github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
 go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
 go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
+golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
 golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
 golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
 golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
@@ -40,5 +75,6 @@ google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/reporters/gojson.go b/internal/reporters/gojson.go
new file mode 100644
index 0000000..8b7a9ce
--- /dev/null
+++ b/internal/reporters/gojson.go
@@ -0,0 +1,158 @@
+package reporters
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/onsi/ginkgo/v2/types"
+	"golang.org/x/tools/go/packages"
+)
+
+func ptr[T any](in T) *T {
+	return &in
+}
+
+type encoder interface {
+	Encode(v any) error
+}
+
+// gojsonEvent matches the format from go internals
+// https://github.com/golang/go/blob/master/src/cmd/internal/test2json/test2json.go#L31-L41
+// https://pkg.go.dev/cmd/test2json
+type gojsonEvent struct {
+	Time        *time.Time `json:",omitempty"`
+	Action      GoJSONAction
+	Package     string   `json:",omitempty"`
+	Test        string   `json:",omitempty"`
+	Elapsed     *float64 `json:",omitempty"`
+	Output      *string  `json:",omitempty"`
+	FailedBuild string   `json:",omitempty"`
+}
+
+type GoJSONAction string
+
+const (
+	// start  - the test binary is about to be executed
+	GoJSONStart GoJSONAction = "start"
+	// run    - the test has started running
+	GoJSONRun GoJSONAction = "run"
+	// pause  - the test has been paused
+	GoJSONPause GoJSONAction = "pause"
+	// cont   - the test has continued running
+	GoJSONCont GoJSONAction = "cont"
+	// pass   - the test passed
+	GoJSONPass GoJSONAction = "pass"
+	// bench  - the benchmark printed log output but did not fail
+	GoJSONBench GoJSONAction = "bench"
+	// fail   - the test or benchmark failed
+	GoJSONFail GoJSONAction = "fail"
+	// output - the test printed output
+	GoJSONOutput GoJSONAction = "output"
+	// skip   - the test was skipped or the package contained no tests
+	GoJSONSkip GoJSONAction = "skip"
+)
+
+func goJSONActionFromSpecState(state types.SpecState) GoJSONAction {
+	switch state {
+	case types.SpecStateInvalid:
+		return GoJSONFail
+	case types.SpecStatePending:
+		return GoJSONSkip
+	case types.SpecStateSkipped:
+		return GoJSONSkip
+	case types.SpecStatePassed:
+		return GoJSONPass
+	case types.SpecStateFailed:
+		return GoJSONFail
+	case types.SpecStateAborted:
+		return GoJSONFail
+	case types.SpecStatePanicked:
+		return GoJSONFail
+	case types.SpecStateInterrupted:
+		return GoJSONFail
+	case types.SpecStateTimedout:
+		return GoJSONFail
+	default:
+		panic("unexpected state should not happen")
+	}
+}
+
+// gojsonReport wraps types.Report and calcualtes extra fields requires by gojson
+type gojsonReport struct {
+	o types.Report
+	// Extra calculated fields
+	goPkg string
+	elapsed float64
+}
+
+func newReport(in types.Report) *gojsonReport {
+	return &gojsonReport{
+		o: in,
+	}
+}
+
+func (r *gojsonReport) Fill() error {
+	// NOTE: could the types.Report include the go package name?
+	goPkg, err := suitePathToPkg(r.o.SuitePath)
+	if err != nil {
+		return err
+	}
+	r.goPkg = goPkg
+	r.elapsed = r.o.RunTime.Seconds()
+	return nil
+}
+
+// gojsonSpecReport wraps types.SpecReport and calculates extra fields required by gojson
+type gojsonSpecReport struct {
+	o types.SpecReport
+	// extra calculated fields
+	testName string
+	elapsed float64
+	action GoJSONAction
+}
+
+func newSpecReport(in types.SpecReport) *gojsonSpecReport {
+	return &gojsonSpecReport{
+		o: in,
+	}
+}
+
+func (sr *gojsonSpecReport) Fill() error {
+	sr.elapsed = sr.o.RunTime.Seconds()
+	sr.testName = createTestName(sr.o)
+	sr.action = goJSONActionFromSpecState(sr.o.State)
+	return nil
+}
+
+func suitePathToPkg(dir string) (string, error) {
+	cfg := &packages.Config{
+		Mode: packages.NeedFiles | packages.NeedSyntax,
+	}
+	pkgs, err := packages.Load(cfg, dir)
+	if err != nil {
+		return "", err
+	}
+	if len(pkgs) != 1 {
+		return "", errors.New("error")
+	}
+	return pkgs[0].ID, nil
+}
+
+func createTestName(spec types.SpecReport) string {
+		name := fmt.Sprintf("[%s]", spec.LeafNodeType)
+		if spec.FullText() != "" {
+			name = name + " " + spec.FullText()
+		}
+		labels := spec.Labels()
+		if len(labels) > 0 {
+			name = name + " [" + strings.Join(labels, ", ") + "]"
+		}
+		semVerConstraints := spec.SemVerConstraints()
+		if len(semVerConstraints) > 0 {
+			name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
+		}
+		name = strings.TrimSpace(name)
+		return name
+}
diff --git a/internal/reporters/gojson_event_writer.go b/internal/reporters/gojson_event_writer.go
new file mode 100644
index 0000000..ec5311d
--- /dev/null
+++ b/internal/reporters/gojson_event_writer.go
@@ -0,0 +1,111 @@
+package reporters
+
+type GoJSONEventWriter struct {
+	enc encoder
+	specSystemErrFn specSystemExtractFn
+	specSystemOutFn specSystemExtractFn
+}
+
+func NewGoJSONEventWriter(enc encoder, errFn specSystemExtractFn, outFn specSystemExtractFn) *GoJSONEventWriter {
+	return &GoJSONEventWriter{
+		enc: enc,
+		specSystemErrFn: errFn,
+		specSystemOutFn: outFn,
+	}
+}
+
+func (r *GoJSONEventWriter) writeEvent(e *gojsonEvent) error {
+	return r.enc.Encode(e)
+}
+
+func (r *GoJSONEventWriter) WriteSuiteStart(report *gojsonReport) error {
+	e := &gojsonEvent{
+		Time:        &report.o.StartTime,
+		Action:      GoJSONStart,
+		Package:     report.goPkg,
+		Output:      nil,
+		FailedBuild: "",
+	}
+	return r.writeEvent(e)
+}
+
+func (r *GoJSONEventWriter) WriteSuiteResult(report *gojsonReport) error {
+	var action GoJSONAction
+	switch {
+	case report.o.PreRunStats.SpecsThatWillRun == 0:
+		action = GoJSONSkip
+	case report.o.SuiteSucceeded:
+		action = GoJSONPass
+	default:
+		action = GoJSONFail
+	}
+	e := &gojsonEvent{
+		Time:        &report.o.EndTime,
+		Action:      action,
+		Package:     report.goPkg,
+		Output:      nil,
+		FailedBuild: "",
+		Elapsed:     ptr(report.elapsed),
+	}
+	return r.writeEvent(e)
+}
+
+func (r *GoJSONEventWriter) WriteSpecStart(report *gojsonReport, specReport *gojsonSpecReport) error {
+	e := &gojsonEvent{
+		Time:        &specReport.o.StartTime,
+		Action:      GoJSONRun,
+		Test:        specReport.testName,
+		Package:     report.goPkg,
+		Output:      nil,
+		FailedBuild: "",
+	}
+	return r.writeEvent(e)
+}
+
+func (r *GoJSONEventWriter) WriteSpecOut(report *gojsonReport, specReport *gojsonSpecReport) error {
+	events := []*gojsonEvent{}
+
+	stdErr := r.specSystemErrFn(specReport.o)
+	if stdErr != "" {
+		events = append(events, &gojsonEvent{
+			Time:        &specReport.o.EndTime,
+			Action:      GoJSONOutput,
+			Test:        specReport.testName,
+			Package:     report.goPkg,
+			Output:      ptr(stdErr),
+			FailedBuild: "",
+		})
+	}
+	stdOut := r.specSystemOutFn(specReport.o)
+	if stdOut != "" {
+		events = append(events, &gojsonEvent{
+			Time:        &specReport.o.EndTime,
+			Action:      GoJSONOutput,
+			Test:        specReport.testName,
+			Package:     report.goPkg,
+			Output:      ptr(stdOut),
+			FailedBuild: "",
+		})
+	}
+
+	for _, ev := range events {
+		err := r.writeEvent(ev)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (r *GoJSONEventWriter) WriteSpecResult(report *gojsonReport, specReport *gojsonSpecReport) error {
+	e := &gojsonEvent{
+		Time:        &specReport.o.EndTime,
+		Action:      specReport.action,
+		Test:        specReport.testName,
+		Package:     report.goPkg,
+		Elapsed:     ptr(specReport.elapsed),
+		Output:      nil,
+		FailedBuild: "",
+	}
+	return r.writeEvent(e)
+}
diff --git a/internal/reporters/gojson_reporter.go b/internal/reporters/gojson_reporter.go
new file mode 100644
index 0000000..633e49b
--- /dev/null
+++ b/internal/reporters/gojson_reporter.go
@@ -0,0 +1,45 @@
+package reporters
+
+import (
+	"github.com/onsi/ginkgo/v2/types"
+)
+
+type GoJSONReporter struct {
+	ev *GoJSONEventWriter
+}
+
+type specSystemExtractFn func (spec types.SpecReport) string
+
+func NewGoJSONReporter(enc encoder, errFn specSystemExtractFn, outFn specSystemExtractFn) *GoJSONReporter {
+	return &GoJSONReporter{
+		ev: NewGoJSONEventWriter(enc, errFn, outFn),
+	}
+}
+
+func (r *GoJSONReporter) Write(originalReport types.Report) error {
+	// suite start events
+	report := newReport(originalReport)
+	err := report.Fill()
+	if err != nil {
+		return err
+	}
+	r.ev.WriteSuiteStart(report)
+	for _, originalSpecReport := range originalReport.SpecReports {
+		specReport := newSpecReport(originalSpecReport)
+		err := specReport.Fill()
+		if err != nil {
+			return err
+		}
+		if specReport.o.LeafNodeType == types.NodeTypeIt {
+			// handle any It leaf node as a spec
+			r.ev.WriteSpecStart(report, specReport)
+			r.ev.WriteSpecOut(report, specReport)
+			r.ev.WriteSpecResult(report, specReport)
+		} else {
+			// handle any other leaf node as generic output
+			r.ev.WriteSpecOut(report, specReport)
+		}
+	}
+	r.ev.WriteSuiteResult(report)
+	return nil
+}
diff --git a/reporters/__snapshots__/gojson_report_test.snap b/reporters/__snapshots__/gojson_report_test.snap
new file mode 100755
index 0000000..8d58143
--- /dev/null
+++ b/reporters/__snapshots__/gojson_report_test.snap
@@ -0,0 +1,82 @@
+
+[GoJSONReport when configured to write the report inside a folder creates the folder and the report file - 1]
+{"Time":"0001-01-01T00:00:00Z","Action":"start","Package":"/path/to/suite"}
+{"Time":"0001-01-01T00:00:00Z","Action":"run","Package":"/path/to/suite","Test":"[It] A B C [dolphin, gorilla, cow, cat, dog]"}
+{"Time":"0001-01-01T00:00:00Z","Action":"output","Package":"/path/to/suite","Test":"[It] A B C [dolphin, gorilla, cow, cat, dog]","Output":"STEP: a by step - cl0.go:12 @ 09/09/25 10:50:00\n\u003e Enter [It] C - cl2.go:80 @ 09/09/25 10:50:00\nginkgowriter\n[TIMEDOUT] failure\nmessage\nIn [It] at: cl3.go:103 @ 09/09/25 10:50:00\noutput\n[PANICKED] \nIn [It] at: cl4.go:144 @ 09/09/25 10:50:00\n\nthe panic!\n\nFull Stack Trace\n  full-trace\n  cl-4\n\u003c Exit [It] C - cl2.go:80 @ 09/09/25 10:50:00 (87ms)\na report entry - cl1.go:37 @ 09/09/25 10:50:00\na hidden report entry - cl1.go:37 @ 09/09/25 10:50:00\ncleanup!\n[FAILED] a subsequent failure\nIn [AfterEach] at: :0 @ 09/09/25 10:50:00\n"}
+{"Time":"0001-01-01T00:00:00Z","Action":"output","Package":"/path/to/suite","Test":"[It] A B C [dolphin, gorilla, cow, cat, dog]","Output":"some captured stdout\n"}
+{"Time":"0001-01-01T00:00:00Z","Action":"fail","Package":"/path/to/suite","Test":"[It] A B C [dolphin, gorilla, cow, cat, dog]","Elapsed":1}
+{"Time":"0001-01-01T00:00:00Z","Action":"run","Package":"/path/to/suite","Test":"[It] A [cat, owner:frank, OWNer:bob]"}
+{"Time":"0001-01-01T00:00:00Z","Action":"output","Package":"/path/to/suite","Test":"[It] A [cat, owner:frank, OWNer:bob]","Output":"\u003e Enter [It] A - cl0.go:12 @ 09/09/25 10:50:00\nsome GinkgoWriter\nmy progress report\n  A (Spec Runtime: 5s)\n    cl0.go:12\nSTEP: My Step - cl1.go:37 @ 09/09/25 10:50:00\noutput is interspersed\nmy entry - cl1.go:37 @ 09/09/25 10:50:00\nmy hidden entry - cl1.go:37 @ 09/09/25 10:50:00\nEND STEP: My Step - cl1.go:37 @ 09/09/25 10:50:00 (200ms)\nhere and there\n\u003c Exit [It] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)\n"}
+{"Time":"0001-01-01T00:00:00Z","Action":"output","Package":"/path/to/suite","Test":"[It] A [cat, owner:frank, OWNer:bob]","Output":"some captured stdout\n"}
+{"Time":"0001-01-01T00:00:00Z","Action":"pass","Package":"/path/to/suite","Test":"[It] A [cat, owner:frank, OWNer:bob]","Elapsed":1}
+{"Time":"0001-01-01T00:00:00Z","Action":"run","Package":"/path/to/suite","Test":"[It] A [owner:org, owner:team]"}
+{"Time":"0001-01-01T00:00:00Z","Action":"skip","Package":"/path/to/suite","Test":"[It] A [owner:org, owner:team]","Elapsed":1}
+{"Time":"0001-01-01T00:00:00Z","Action":"run","Package":"/path/to/suite","Test":"[It] A [owner:org]"}
+{"Time":"0001-01-01T00:00:00Z","Action":"output","Package":"/path/to/suite","Test":"[It] A [owner:org]","Output":"\u003e Enter [It] A - cl0.go:12 @ 09/09/25 10:50:00\n[PANICKED] failure\nmessage\nIn [It] at: cl1.go:37 @ 09/09/25 10:50:00\n\nthe panic\n\nFull Stack Trace\n  full-trace\n  cl-1\n\u003c Exit [It] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)\n"}
+{"Time":"0001-01-01T00:00:00Z","Action":"output","Package":"/path/to/suite","Test":"[It] A [owner:org]","Output":"some captured stdout\n"}
+{"Time":"0001-01-01T00:00:00Z","Action":"fail","Package":"/path/to/suite","Test":"[It] A [owner:org]","Elapsed":1}
+{"Time":"0001-01-01T00:00:00Z","Action":"fail","Package":"/path/to/suite","Elapsed":60}
+
+---
+
+[GoJSONReport when configured to write the report inside a folder creates the folder and the report file - 2]
+STEP: a by step - cl0.go:12 @ 09/09/25 10:50:00
+> Enter [It] C - cl2.go:80 @ 09/09/25 10:50:00
+ginkgowriter
+[TIMEDOUT] failure
+message
+In [It] at: cl3.go:103 @ 09/09/25 10:50:00
+output
+[PANICKED] 
+In [It] at: cl4.go:144 @ 09/09/25 10:50:00
+
+the panic!
+
+Full Stack Trace
+  full-trace
+  cl-4
+< Exit [It] C - cl2.go:80 @ 09/09/25 10:50:00 (87ms)
+a report entry - cl1.go:37 @ 09/09/25 10:50:00
+a hidden report entry - cl1.go:37 @ 09/09/25 10:50:00
+cleanup!
+[FAILED] a subsequent failure
+In [AfterEach] at: :0 @ 09/09/25 10:50:00
+some captured stdout
+> Enter [It] A - cl0.go:12 @ 09/09/25 10:50:00
+some GinkgoWriter
+my progress report
+  A (Spec Runtime: 5s)
+    cl0.go:12
+STEP: My Step - cl1.go:37 @ 09/09/25 10:50:00
+output is interspersed
+my entry - cl1.go:37 @ 09/09/25 10:50:00
+my hidden entry - cl1.go:37 @ 09/09/25 10:50:00
+END STEP: My Step - cl1.go:37 @ 09/09/25 10:50:00 (200ms)
+here and there
+< Exit [It] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)
+some captured stdout
+> Enter [It] A - cl0.go:12 @ 09/09/25 10:50:00
+[PANICKED] failure
+message
+In [It] at: cl1.go:37 @ 09/09/25 10:50:00
+
+the panic
+
+Full Stack Trace
+  full-trace
+  cl-1
+< Exit [It] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)
+some captured stdout
+
+output should match
+---
+
+[GoJSONReport when configured to write the report inside a folder creates the folder and the report file - 3]
+SUITE: /path/to/suite, tests: 4 
+---- TEST: [It] A B C [dolphin, gorilla, cow, cat, dog], status: fail
+---- TEST: [It] A [cat, owner:frank, OWNer:bob], status: pass
+---- TEST: [It] A [owner:org, owner:team], status: skip
+---- TEST: [It] A [owner:org], status: fail
+
+package summary output match
+---
diff --git a/reporters/__snapshots__/junit_report_test.snap b/reporters/__snapshots__/junit_report_test.snap
new file mode 100755
index 0000000..0e36629
--- /dev/null
+++ b/reporters/__snapshots__/junit_report_test.snap
@@ -0,0 +1,59 @@
+
+[JunitReport when configured to write the report inside a folder creates the folder and the report file - 1]
+<?xml version="1.0" encoding="UTF-8"?>
+  <testsuites tests="5" disabled="1" errors="1" failures="1" time="60">
+      <testsuite name="My Suite" package="/path/to/suite" tests="5" disabled="1" skipped="0" errors="1" failures="1" time="60" timestamp="0001-01-01T00:00:00">
+          <properties>
+              <property name="SuiteSucceeded" value="false"></property>
+              <property name="SuiteHasProgrammaticFocus" value="false"></property>
+              <property name="SpecialSuiteFailureReason" value=""></property>
+              <property name="SuiteLabels" value="[]"></property>
+              <property name="SuiteSemVerConstraints" value="[]"></property>
+              <property name="RandomSeed" value="17"></property>
+              <property name="RandomizeAllSpecs" value="false"></property>
+              <property name="LabelFilter" value=""></property>
+              <property name="SemVerFilter" value=""></property>
+              <property name="FocusStrings" value=""></property>
+              <property name="SkipStrings" value=""></property>
+              <property name="FocusFiles" value=""></property>
+              <property name="SkipFiles" value=""></property>
+              <property name="FailOnPending" value="false"></property>
+              <property name="FailOnEmpty" value="false"></property>
+              <property name="FailFast" value="false"></property>
+              <property name="FlakeAttempts" value="0"></property>
+              <property name="DryRun" value="false"></property>
+              <property name="ParallelTotal" value="1"></property>
+              <property name="OutputInterceptorMode" value=""></property>
+          </properties>
+          <testcase name="[It] A B C [dolphin, gorilla, cow, cat, dog]" classname="My Suite" status="timedout" time="1">
+              <failure message="failure&#xA;message" type="timedout">[TIMEDOUT] failure&#xA;message&#xA;In [It] at: cl3.go:103 @ 09/09/25 10:50:00&#xA;&#xA;[PANICKED] &#xA;In [It] at: cl4.go:144 @ 09/09/25 10:50:00&#xA;&#xA;the panic!&#xA;&#xA;Full Stack Trace&#xA;  full-trace&#xA;  cl-4&#xA;&#xA;There were additional failures detected after the initial failure. These are visible in the timeline&#xA;</failure>
+              <system-out>some captured stdout&#xA;</system-out>
+              <system-err>STEP: a by step - cl0.go:12 @ 09/09/25 10:50:00&#xA;&gt; Enter [It] C - cl2.go:80 @ 09/09/25 10:50:00&#xA;ginkgowriter&#xA;[TIMEDOUT] failure&#xA;message&#xA;In [It] at: cl3.go:103 @ 09/09/25 10:50:00&#xA;output&#xA;[PANICKED] &#xA;In [It] at: cl4.go:144 @ 09/09/25 10:50:00&#xA;&#xA;the panic!&#xA;&#xA;Full Stack Trace&#xA;  full-trace&#xA;  cl-4&#xA;&lt; Exit [It] C - cl2.go:80 @ 09/09/25 10:50:00 (87ms)&#xA;a report entry - cl1.go:37 @ 09/09/25 10:50:00&#xA;a hidden report entry - cl1.go:37 @ 09/09/25 10:50:00&#xA;cleanup!&#xA;[FAILED] a subsequent failure&#xA;In [AfterEach] at: :0 @ 09/09/25 10:50:00&#xA;</system-err>
+          </testcase>
+          <testcase name="[It] A [cat, owner:frank, OWNer:bob]" classname="My Suite" status="passed" time="1" owner="bob">
+              <system-out>some captured stdout&#xA;</system-out>
+              <system-err>&gt; Enter [It] A - cl0.go:12 @ 09/09/25 10:50:00&#xA;some GinkgoWriter&#xA;my progress report&#xA;  A (Spec Runtime: 5s)&#xA;    cl0.go:12&#xA;STEP: My Step - cl1.go:37 @ 09/09/25 10:50:00&#xA;output is interspersed&#xA;my entry - cl1.go:37 @ 09/09/25 10:50:00&#xA;my hidden entry - cl1.go:37 @ 09/09/25 10:50:00&#xA;END STEP: My Step - cl1.go:37 @ 09/09/25 10:50:00 (200ms)&#xA;here and there&#xA;&lt; Exit [It] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)&#xA;</system-err>
+          </testcase>
+          <testcase name="[It] A [owner:org, owner:team]" classname="My Suite" status="pending" time="1" owner="team">
+              <skipped message="pending"></skipped>
+          </testcase>
+          <testcase name="[It] A [owner:org]" classname="My Suite" status="panicked" time="1" owner="org">
+              <error message="the panic" type="panicked">[PANICKED] failure&#xA;message&#xA;In [It] at: cl1.go:37 @ 09/09/25 10:50:00&#xA;&#xA;the panic&#xA;&#xA;Full Stack Trace&#xA;  full-trace&#xA;  cl-1&#xA;</error>
+              <system-out>some captured stdout&#xA;</system-out>
+              <system-err>&gt; Enter [It] A - cl0.go:12 @ 09/09/25 10:50:00&#xA;[PANICKED] failure&#xA;message&#xA;In [It] at: cl1.go:37 @ 09/09/25 10:50:00&#xA;&#xA;the panic&#xA;&#xA;Full Stack Trace&#xA;  full-trace&#xA;  cl-1&#xA;&lt; Exit [It] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)&#xA;</system-err>
+          </testcase>
+          <testcase name="[BeforeSuite] A" classname="My Suite" status="passed" time="1"></testcase>
+      </testsuite>
+  </testsuites>
+---
+
+[JunitReport when configured to write the report inside a folder creates the folder and the report file - 2]
+SUITE: /path/to/suite, tests: 5 
+---- TEST: [It] A B C [dolphin, gorilla, cow, cat, dog], status: failed
+---- TEST: [It] A [cat, owner:frank, OWNer:bob], status: passed
+---- TEST: [It] A [owner:org, owner:team], status: skipped
+---- TEST: [It] A [owner:org], status: error
+---- TEST: [BeforeSuite] A, status: passed
+
+package summary output match
+---
diff --git a/reporters/__snapshots__/teamcity_report_test.snap b/reporters/__snapshots__/teamcity_report_test.snap
new file mode 100755
index 0000000..cfdeec8
--- /dev/null
+++ b/reporters/__snapshots__/teamcity_report_test.snap
@@ -0,0 +1,29 @@
+
+[TeamCityReport when configured to write the report inside a folder creates the folder and the report file - 1]
+##teamcity[testSuiteStarted name='My Suite']
+##teamcity[testStarted name='|[It|] A B C |[dolphin, gorilla, cow, cat, dog|]']
+##teamcity[testFailed name='|[It|] A B C |[dolphin, gorilla, cow, cat, dog|]' message='timedout - failure|nmessage' details='|[TIMEDOUT|] failure|nmessage|nIn |[It|] at: cl3.go:103 @ 09/09/25 10:50:00|n|n|[PANICKED|] |nIn |[It|] at: cl4.go:144 @ 09/09/25 10:50:00|n|nthe panic!|n|nFull Stack Trace|n  full-trace|n  cl-4|n|nThere were additional failures detected after the initial failure. These are visible in the timeline|n']
+##teamcity[testStdOut name='|[It|] A B C |[dolphin, gorilla, cow, cat, dog|]' out='some captured stdout|n']
+##teamcity[testStdErr name='|[It|] A B C |[dolphin, gorilla, cow, cat, dog|]' out='STEP: a by step - cl0.go:12 @ 09/09/25 10:50:00|n> Enter |[It|] C - cl2.go:80 @ 09/09/25 10:50:00|nginkgowriter|n|[TIMEDOUT|] failure|nmessage|nIn |[It|] at: cl3.go:103 @ 09/09/25 10:50:00|noutput|n|[PANICKED|] |nIn |[It|] at: cl4.go:144 @ 09/09/25 10:50:00|n|nthe panic!|n|nFull Stack Trace|n  full-trace|n  cl-4|n< Exit |[It|] C - cl2.go:80 @ 09/09/25 10:50:00 (87ms)|na report entry - cl1.go:37 @ 09/09/25 10:50:00|na hidden report entry - cl1.go:37 @ 09/09/25 10:50:00|ncleanup!|n|[FAILED|] a subsequent failure|nIn |[AfterEach|] at: :0 @ 09/09/25 10:50:00|n']
+##teamcity[testFinished name='|[It|] A B C |[dolphin, gorilla, cow, cat, dog|]' duration='1000']
+##teamcity[testStarted name='|[It|] A']
+##teamcity[testStdOut name='|[It|] A' out='some captured stdout|n']
+##teamcity[testStdErr name='|[It|] A' out='> Enter |[It|] A - cl0.go:12 @ 09/09/25 10:50:00|nsome GinkgoWriter|nmy progress report|n  A (Spec Runtime: 5s)|n    cl0.go:12|nSTEP: My Step - cl1.go:37 @ 09/09/25 10:50:00|noutput is interspersed|nmy entry - cl1.go:37 @ 09/09/25 10:50:00|nmy hidden entry - cl1.go:37 @ 09/09/25 10:50:00|nEND STEP: My Step - cl1.go:37 @ 09/09/25 10:50:00 (200ms)|nhere and there|n< Exit |[It|] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)|n']
+##teamcity[testFinished name='|[It|] A' duration='1000']
+##teamcity[testStarted name='|[It|] A']
+##teamcity[testIgnored name='|[It|] A' message='pending']
+##teamcity[testStdOut name='|[It|] A' out='']
+##teamcity[testStdErr name='|[It|] A' out='']
+##teamcity[testFinished name='|[It|] A' duration='1000']
+##teamcity[testStarted name='|[It|] A']
+##teamcity[testFailed name='|[It|] A' message='panicked - the panic' details='|[PANICKED|] failure|nmessage|nIn |[It|] at: cl1.go:37 @ 09/09/25 10:50:00|n|nthe panic|n|nFull Stack Trace|n  full-trace|n  cl-1|n']
+##teamcity[testStdOut name='|[It|] A' out='some captured stdout|n']
+##teamcity[testStdErr name='|[It|] A' out='> Enter |[It|] A - cl0.go:12 @ 09/09/25 10:50:00|n|[PANICKED|] failure|nmessage|nIn |[It|] at: cl1.go:37 @ 09/09/25 10:50:00|n|nthe panic|n|nFull Stack Trace|n  full-trace|n  cl-1|n< Exit |[It|] A - cl0.go:12 @ 09/09/25 10:50:00 (300ms)|n']
+##teamcity[testFinished name='|[It|] A' duration='1000']
+##teamcity[testStarted name='|[BeforeSuite|] A']
+##teamcity[testStdOut name='|[BeforeSuite|] A' out='']
+##teamcity[testStdErr name='|[BeforeSuite|] A' out='']
+##teamcity[testFinished name='|[BeforeSuite|] A' duration='1000']
+##teamcity[testSuiteFinished name='My Suite']
+
+---
diff --git a/reporters/gojson_report.go b/reporters/gojson_report.go
new file mode 100644
index 0000000..4d1814e
--- /dev/null
+++ b/reporters/gojson_report.go
@@ -0,0 +1,60 @@
+package reporters
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"path"
+
+	"github.com/onsi/ginkgo/v2/internal/reporters"
+	"github.com/onsi/ginkgo/v2/types"
+)
+
+// GenerateGoTestJSONReport produces a JSON-formatted in the test2json format used by `go test -json`
+func GenerateGoTestJSONReport(report types.Report, destination string) error {
+	// walk report and generate test2json-compatible objects
+	// JSON-encode the objects into filename
+	if err := os.MkdirAll(path.Dir(destination), 0770); err != nil {
+		return err
+	}
+	f, err := os.Create(destination)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	enc := json.NewEncoder(f)
+	r := reporters.NewGoJSONReporter(
+		enc,
+		systemErrForUnstructuredReporters,
+		systemOutForUnstructuredReporters,
+	)
+	return r.Write(report)
+}
+
+// MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources
+// It skips over reports that fail to decode but reports on them via the returned messages []string
+func MergeAndCleanupGoTestJSONReports(sources []string, destination string) ([]string, error) {
+	messages := []string{}
+	if err := os.MkdirAll(path.Dir(destination), 0770); err != nil {
+		return messages, err
+	}
+	f, err := os.Create(destination)
+	if err != nil {
+		return messages, err
+	}
+	defer f.Close()
+
+	for _, source := range sources {
+		data, err := os.ReadFile(source)
+		if err != nil {
+			messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error()))
+			continue
+		}
+		_, err = f.Write(data)
+		if err != nil {
+			messages = append(messages, fmt.Sprintf("Could not write to %s:\n%s", destination, err.Error()))
+			continue
+		}
+	}
+	return messages, nil
+}
diff --git a/reporting_dsl.go b/reporting_dsl.go
index 5bf2e62..dd13505 100644
--- a/reporting_dsl.go
+++ b/reporting_dsl.go
@@ -165,7 +165,7 @@ ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Co
 When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across
 all parallel nodes
 
-In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, JUnit, and Teamcity formatted reports using the --json-report, --junit-report, and --teamcity-report ginkgo CLI flags.
+In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, GoJSON, JUnit, and Teamcity formatted reports using the --json-report, --gojson-report, --junit-report, and --teamcity-report ginkgo CLI flags.
 
 You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure.
 You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically
@@ -188,6 +188,12 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re
 				Fail(fmt.Sprintf("Failed to generate JSON report:\n%s", err.Error()))
 			}
 		}
+		if reporterConfig.GoJSONReport != "" {
+			err := reporters.GenerateGoTestJSONReport(report, reporterConfig.GoJSONReport)
+			if err != nil {
+				Fail(fmt.Sprintf("Failed to generate Go JSON report:\n%s", err.Error()))
+			}
+		}
 		if reporterConfig.JUnitReport != "" {
 			err := reporters.GenerateJUnitReport(report, reporterConfig.JUnitReport)
 			if err != nil {
@@ -206,6 +212,9 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re
 	if reporterConfig.JSONReport != "" {
 		flags = append(flags, "--json-report")
 	}
+	if reporterConfig.GoJSONReport != "" {
+		flags = append(flags, "--gojson-report")
+	}
 	if reporterConfig.JUnitReport != "" {
 		flags = append(flags, "--junit-report")
 	}
diff --git a/types/config.go b/types/config.go
index b99a9e1..f847036 100644
--- a/types/config.go
+++ b/types/config.go
@@ -96,6 +96,7 @@ type ReporterConfig struct {
 	ForceNewlines  bool
 
 	JSONReport     string
+	GoJSONReport   string
 	JUnitReport    string
 	TeamcityReport string
 }
@@ -112,7 +113,7 @@ func (rc ReporterConfig) Verbosity() VerbosityLevel {
 }
 
 func (rc ReporterConfig) WillGenerateReport() bool {
-	return rc.JSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != ""
+	return rc.JSONReport != "" || rc.GoJSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != ""
 }
 
 func NewDefaultReporterConfig() ReporterConfig {
@@ -359,6 +360,8 @@ var ReporterConfigFlags = GinkgoFlags{
 
 	{KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output",
 		Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."},
+	{KeyPath: "R.GoJSONReport", Name: "gojson-report", UsageArgument: "filename.json", SectionKey: "output",
+		Usage: "If set, Ginkgo will generate a Go JSON-formatted test report at the specified location."},
 	{KeyPath: "R.JUnitReport", Name: "junit-report", UsageArgument: "filename.xml", SectionKey: "output", DeprecatedName: "reportFile", DeprecatedDocLink: "improved-reporting-infrastructure",
 		Usage: "If set, Ginkgo will generate a conformant junit test report in the specified file."},
 	{KeyPath: "R.TeamcityReport", Name: "teamcity-report", UsageArgument: "filename", SectionKey: "output",



Test patch:
diff --git a/integration/profiling_test.go b/integration/profiling_test.go
index c4a9068..99030e7 100644
--- a/integration/profiling_test.go
+++ b/integration/profiling_test.go
@@ -475,6 +475,7 @@ var _ = Describe("Profiling Specs", func() {
 				"--mutexprofile=mutex.out",
 				"--coverprofile=cover.out",
 				"--json-report=report.json",
+				"--gojson-report=report.go.json",
 				"--junit-report=report.xml",
 				"--teamcity-report=report.tm",
 				"--procs=2",
@@ -515,6 +516,7 @@ var _ = Describe("Profiling Specs", func() {
 				"--mutexprofile=mutex.out",
 				"--coverprofile=cover.out",
 				"--json-report=report.json",
+				"--gojson-report=report.go.json",
 				"--junit-report=report.xml",
 				"--teamcity-report=report.tm",
 				"--procs=2",
diff --git a/integration/reporting_test.go b/integration/reporting_test.go
index c382d17..754b2e4 100644
--- a/integration/reporting_test.go
+++ b/integration/reporting_test.go
@@ -345,7 +345,7 @@ var _ = Describe("Reporting", func() {
 
 		Context("the default behavior", func() {
 			BeforeEach(func() {
-				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "-seed=17")
+				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--gojson-report=out.go.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "-seed=17")
 				Eventually(session).Should(gexec.Exit(1))
 				Ω(session).ShouldNot(gbytes.Say("Could not open"))
 			})
@@ -368,7 +368,7 @@ var _ = Describe("Reporting", func() {
 
 		Context("with -output-dir", func() {
 			BeforeEach(func() {
-				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--output-dir=./reports", "-seed=17")
+				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--gojson-report=out.go.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--output-dir=./reports", "-seed=17")
 				Eventually(session).Should(gexec.Exit(1))
 				Ω(session).ShouldNot(gbytes.Say("Could not open"))
 			})
@@ -391,7 +391,7 @@ var _ = Describe("Reporting", func() {
 
 		Context("with -keep-separate-reports", func() {
 			BeforeEach(func() {
-				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--keep-separate-reports", "-seed=17")
+				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--gojson-report=out.go.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--keep-separate-reports", "-seed=17")
 				Eventually(session).Should(gexec.Exit(1))
 				Ω(session).ShouldNot(gbytes.Say("Could not open"))
 			})
@@ -423,7 +423,7 @@ var _ = Describe("Reporting", func() {
 
 		Context("with -keep-separate-reports and -output-dir", func() {
 			BeforeEach(func() {
-				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--keep-separate-reports", "--output-dir=./reports", "-seed=17")
+				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--gojson-report=out.go.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--keep-separate-reports", "--output-dir=./reports", "-seed=17")
 				Eventually(session).Should(gexec.Exit(1))
 				Ω(session).ShouldNot(gbytes.Say("Could not open"))
 			})
@@ -453,7 +453,7 @@ var _ = Describe("Reporting", func() {
 
 		Context("when keep-going is not set and a suite fails", func() {
 			BeforeEach(func() {
-				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "-coverprofile=cover.out", "-cpuprofile=cpu.out", "-seed=17", "--output-dir=./reports")
+				session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--procs=2", "--json-report=out.json", "--gojson-report=out.go.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "-coverprofile=cover.out", "-cpuprofile=cpu.out", "-seed=17", "--output-dir=./reports")
 				Eventually(session).Should(gexec.Exit(1))
 				Ω(session).ShouldNot(gbytes.Say("Could not open"))
 			})
diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go
index 4754598..294aa8f 100644
--- a/reporters/default_reporter_test.go
+++ b/reporters/default_reporter_test.go
@@ -29,7 +29,8 @@ var cl1 = types.CodeLocation{FileName: "cl1.go", LineNumber: 37, FullStackTrace:
 var cl2 = types.CodeLocation{FileName: "cl2.go", LineNumber: 80, FullStackTrace: "full-trace\ncl-2"}
 var cl3 = types.CodeLocation{FileName: "cl3.go", LineNumber: 103, FullStackTrace: "full-trace\ncl-3"}
 var cl4 = types.CodeLocation{FileName: "cl4.go", LineNumber: 144, FullStackTrace: "full-trace\ncl-4"}
-var now = time.Now()
+// simulate current time ast static time
+var now = time.Date(2025, time.September, 9, 10, 50, 0, 0, time.UTC)
 
 func CLS(cls ...types.CodeLocation) []types.CodeLocation { return cls }
 func CTS(componentTexts ...string) []string              { return componentTexts }
diff --git a/reporters/gojson_report_test.go b/reporters/gojson_report_test.go
new file mode 100644
index 0000000..ed46b73
--- /dev/null
+++ b/reporters/gojson_report_test.go
@@ -0,0 +1,108 @@
+package reporters_test
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/mfridman/tparse/parse"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/gkampitakis/go-snaps/snaps"
+	"github.com/onsi/ginkgo/v2/reporters"
+	"github.com/onsi/ginkgo/v2/types"
+)
+
+var _ = Describe("GoJSONReport", func() {
+	var report types.Report
+
+	BeforeEach(func() {
+		report = types.Report{
+			SuiteDescription: "My Suite",
+			SuitePath:        "/path/to/suite",
+			PreRunStats:      types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20},
+			SuiteConfig:      types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1},
+			RunTime:          time.Minute,
+			SpecReports: types.SpecReports{
+				S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0),
+					SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)),
+					F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))),
+					SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230),
+					RE("a report entry", cl1, TL("ginkgowriter\noutput\n")),
+					RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever),
+					AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")),
+				),
+				S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), Label("cat", "owner:frank", "OWNer:bob"),
+					SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
+					PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")),
+					SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")),
+					RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")),
+					RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")),
+					SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")),
+					SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
+				),
+				S(types.NodeTypeIt, "A", cl0, types.SpecStatePending, CLabels(Label("owner:org")), Label("owner:team")),
+				S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, CLabels(Label("owner:org")), STD("some captured stdout\n"),
+					SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
+					F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")),
+					SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
+				),
+				S(types.NodeTypeBeforeSuite, "A", cl0, types.SpecStatePassed),
+			},
+		}
+	})
+
+	Describe("when configured to write the report inside a folder", func() {
+		var folderPath string
+		var filePath string
+
+		BeforeEach(func() {
+			folderPath = filepath.Join(fmt.Sprintf("test_outputs_%d", GinkgoParallelProcess()))
+			fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess())
+			filePath = filepath.Join(folderPath, fileName)
+
+			Ω(reporters.GenerateGoTestJSONReport(report, filePath)).Should(Succeed())
+			DeferCleanup(os.RemoveAll, folderPath)
+		})
+
+		It("creates the folder and the report file", func(ctx SpecContext) {
+			_, err := os.Stat(folderPath)
+			Ω(err).Should(Succeed(), "Parent folder should be created")
+			_, err = os.Stat(filePath)
+			Ω(err).Should(Succeed(), "Report file should be created")
+			reportBytes, err := os.ReadFile(filePath)
+			Ω(err).Should(Succeed(), "Report file should be read")
+			snaps.MatchSnapshot(GinkgoT(), string(reportBytes))
+			// Read report and snapshot
+			reader, err := os.Open(filePath)
+			Ω(err).Should(Succeed(), "Report file should be opened")
+			var testOutput bytes.Buffer
+			summary, err := parse.Process(
+				reader,
+				parse.WithFollowOutput(true),
+				parse.WithFollowVersboseOutput(true),
+				parse.WithWriter(&testOutput),
+			)
+			Ω(err).Should(Succeed(), "gojson output should be parsed")
+			snaps.MatchSnapshot(GinkgoT(), testOutput.String(), "output should match")
+
+			var summaryOutput bytes.Buffer
+			writeSummary := func(in []byte) {
+				_, err := summaryOutput.Write(in)
+				Ω(err).Should(Succeed(), "writing to summary should succeed")
+			}
+			packages := summary.GetSortedPackages(parse.SortByPackageName)
+			for _, pkg := range packages {
+				writeSummary([]byte(fmt.Sprintf("SUITE: %s, tests: %d \n", pkg.Summary.Package, len(pkg.Tests))))
+				for _, test := range pkg.Tests {
+					writeSummary([]byte(fmt.Sprintf("---- TEST: %s, status: %s\n", test.Name, test.Status())))
+				}
+				writeSummary([]byte(""))
+			}
+			snaps.MatchSnapshot(GinkgoT(), summaryOutput.String(), "package summary output match")
+		})
+	})
+})
diff --git a/reporters/json_report_test.go b/reporters/json_report_test.go
index 23433e1..fdad6d3 100644
--- a/reporters/json_report_test.go
+++ b/reporters/json_report_test.go
@@ -70,6 +70,11 @@ var _ = Describe("JSONReport", func() {
 			Ω(err).Should(Succeed(), "Parent folder should be created")
 			_, err = os.Stat(filePath)
 			Ω(err).Should(Succeed(), "Report file should be created")
+			reportBytes, err := os.ReadFile(filePath)
+			Ω(err).Should(Succeed(), "Report file should be read")
+			Ω(reportBytes).ShouldNot(BeEmpty(), "report should contain output")
+			// snapshot is non-deterministic
+			// snaps.MatchSnapshot(GinkgoT(), string(reportBytes))
 		})
 	})
 })
diff --git a/reporters/junit_report_test.go b/reporters/junit_report_test.go
index 9c71df4..ded25eb 100644
--- a/reporters/junit_report_test.go
+++ b/reporters/junit_report_test.go
@@ -1,12 +1,15 @@
 package reporters_test
 
 import (
+	"bytes"
 	"encoding/xml"
 	"fmt"
 	"os"
 	"path/filepath"
 	"time"
 
+	"github.com/gkampitakis/go-snaps/snaps"
+	"github.com/joshdk/go-junit"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 
@@ -396,6 +399,25 @@ var _ = Describe("JunitReport", func() {
 			Ω(err).Should(Succeed(), "Parent folder should be created")
 			_, err = os.Stat(filePath)
 			Ω(err).Should(Succeed(), "Report file should be created")
+			reportBytes, err := os.ReadFile(filePath)
+			Ω(err).Should(Succeed(), "Report file should be read")
+			snaps.MatchSnapshot(GinkgoT(), string(reportBytes))
+			suites, err := junit.IngestFile(filePath)
+			Ω(err).Should(Succeed(), "Report file should be parsed")
+
+			var summaryOutput bytes.Buffer
+			writeSummary := func(in []byte) {
+				_, err := summaryOutput.Write(in)
+				Ω(err).Should(Succeed(), "writing to summary should succeed")
+			}
+			for _, suite := range suites {
+				writeSummary([]byte(fmt.Sprintf("SUITE: %s, tests: %d \n", suite.Package, len(suite.Tests))))
+				for _, test := range suite.Tests {
+					writeSummary([]byte(fmt.Sprintf("---- TEST: %s, status: %s\n", test.Name, test.Status)))
+				}
+				writeSummary([]byte(""))
+			}
+			snaps.MatchSnapshot(GinkgoT(), summaryOutput.String(), "package summary output match")
 		})
 	})
 })
diff --git a/reporters/teamcity_report_test.go b/reporters/teamcity_report_test.go
index a7fe55e..6f3b593 100644
--- a/reporters/teamcity_report_test.go
+++ b/reporters/teamcity_report_test.go
@@ -6,6 +6,7 @@ import (
 	"path/filepath"
 	"time"
 
+	"github.com/gkampitakis/go-snaps/snaps"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 
@@ -70,6 +71,9 @@ var _ = Describe("TeamCityReport", func() {
 			Ω(err).Should(Succeed(), "Parent folder should be created")
 			_, err = os.Stat(filePath)
 			Ω(err).Should(Succeed(), "Report file should be created")
+			reportBytes, err := os.ReadFile(filePath)
+			Ω(err).Should(Succeed(), "Report file should be read")
+			snaps.MatchSnapshot(GinkgoT(), string(reportBytes))
 		})
 	})
 })
diff --git a/types/config_test.go b/types/config_test.go
index 330b839..7201559 100644
--- a/types/config_test.go
+++ b/types/config_test.go
@@ -37,6 +37,9 @@ var _ = Describe("Config", func() {
 				repConf = types.ReporterConfig{JSONReport: "foo"}
 				Ω(repConf.WillGenerateReport()).Should(BeTrue())
 
+				repConf = types.ReporterConfig{GoJSONReport: "foo"}
+				Ω(repConf.WillGenerateReport()).Should(BeTrue())
+
 				repConf = types.ReporterConfig{JUnitReport: "foo"}
 				Ω(repConf.WillGenerateReport()).Should(BeTrue())