Ginkgo provides rich reporting capabilities including report entries, current spec information, and lifecycle reporting hooks for custom reporters.
Adds a custom report entry to the current spec. Report entries are included in all generated reports.
func AddReportEntry(name string, args ...any)Parameters:
name: Entry nameargs: Optional value and ReportEntryVisibility (or CodeLocation, Offset)Example:
It("performs complex operation", func() {
AddReportEntry("Starting operation")
result := performOperation()
AddReportEntry("Operation result", result)
AddReportEntry("Debug info", map[string]any{
"duration": result.Duration,
"items": result.ItemCount,
})
// Only visible on failure or in verbose mode
AddReportEntry("Detailed logs", verboseLogs,
ReportEntryVisibilityFailureOrVerbose)
Expect(result.Success).To(BeTrue())
})Control when report entries are shown in output.
const ReportEntryVisibilityAlways
const ReportEntryVisibilityFailureOrVerbose
const ReportEntryVisibilityNever
type ReportEntryVisibility uintExample:
It("has conditional reporting", func() {
// Always visible
AddReportEntry("Critical info", data, ReportEntryVisibilityAlways)
// Visible only on failure or with -v
AddReportEntry("Debug info", debugData,
ReportEntryVisibilityFailureOrVerbose)
// Never shown in console, but included in JSON/JUnit reports
AddReportEntry("Internal metric", metric,
ReportEntryVisibilityNever)
})Returns the report for the currently running spec. Available during spec execution and in ReportAfterEach.
func CurrentSpecReport() SpecReportExample:
It("uses spec report", func() {
report := CurrentSpecReport()
fmt.Printf("Running: %s\n", report.FullText())
fmt.Printf("Location: %s\n", report.LeafNodeLocation)
// Check labels
if slices.Contains(report.LeafNodeLabels, "slow") {
// Adjust behavior for slow tests
}
})
AfterEach(func() {
if CurrentSpecReport().Failed() {
// Capture additional debug info on failure
captureScreenshot()
dumpLogs()
}
})Returns report information during tree construction phase (when Describe, Context, etc. are being evaluated).
func CurrentTreeConstructionNodeReport() ConstructionNodeReportExample:
var suiteConfig types.SuiteConfig
var _ = Describe("Suite", func() {
// Access construction phase information
report := CurrentTreeConstructionNodeReport()
suiteConfig = report.SuiteConfig
It("uses suite config", func() {
fmt.Printf("Random seed: %d\n", suiteConfig.RandomSeed)
})
})Reporting nodes allow you to observe and report on spec execution without affecting test outcomes.
Runs before each spec for reporting purposes. Does not cause spec failure if it fails.
func ReportBeforeEach(body any, args ...any) boolParameters:
body: Function accepting CurrentSpecReport() and/or SpecContextargs: Optional decoratorsExample:
ReportBeforeEach(func(report SpecReport) {
fmt.Printf("Starting: %s\n", report.FullText())
startTimer(report.LeafNodeText)
})
It("does something", func() {
// test code
})Runs after each spec for reporting purposes. Has access to spec results.
func ReportAfterEach(body any, args ...any) boolExample:
ReportAfterEach(func(report SpecReport) {
fmt.Printf("Finished: %s\n", report.FullText())
fmt.Printf("Duration: %v\n", report.RunTime)
fmt.Printf("State: %s\n", report.State)
if report.Failed() {
fmt.Printf("Failure: %s\n", report.Failure.Message)
fmt.Printf("Location: %s\n", report.Failure.Location)
}
// Log report entries
for _, entry := range report.ReportEntries {
fmt.Printf("Entry: %s = %v\n", entry.Name, entry.Value)
}
})Runs before suite execution starts for reporting.
func ReportBeforeSuite(body any, args ...any) boolExample:
ReportBeforeSuite(func(report Report) {
fmt.Printf("Suite: %s\n", report.SuiteDescription)
fmt.Printf("Path: %s\n", report.SuitePath)
fmt.Printf("Total specs: %d\n", report.PreRunStats.TotalSpecs)
fmt.Printf("Will run: %d\n", report.PreRunStats.SpecsThatWillRun)
})Runs after suite completes for reporting. Has access to full suite results.
func ReportAfterSuite(text string, body any, args ...any) boolParameters:
text: Description for the report hookbody: Function accepting Reportargs: Optional decoratorsExample:
ReportAfterSuite("suite report", func(report Report) {
fmt.Printf("Suite: %s\n", report.SuiteDescription)
fmt.Printf("Success: %v\n", report.SuiteSucceeded)
fmt.Printf("Duration: %v\n", report.RunTime)
// Count results
passed := report.SpecReports.CountWithState(types.SpecStatePassed)
failed := report.SpecReports.CountOfFailed()
skipped := report.SpecReports.CountOfSkipped()
pending := report.SpecReports.CountOfPending()
fmt.Printf("Passed: %d, Failed: %d, Skipped: %d, Pending: %d\n",
passed, failed, skipped, pending)
// Report flaky specs
for _, spec := range report.SpecReports {
if spec.NumAttempts > 1 {
fmt.Printf("Flaky: %s (took %d attempts)\n",
spec.FullText(), spec.NumAttempts)
}
}
})Complete report for a single spec execution.
type SpecReport struct {
// Hierarchy and identification
ContainerHierarchyTexts []string
ContainerHierarchyLocations []CodeLocation
ContainerHierarchyLabels [][]string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeText string
// Execution information
State SpecState
StartTime time.Time
EndTime time.Time
RunTime time.Duration
ParallelProcess int
// Failure information
Failure Failure
// Retry information
NumAttempts int
MaxFlakeAttempts int
MaxMustPassRepeatedly int
// Output and entries
CapturedGinkgoWriterOutput string
CapturedStdOutErr string
ReportEntries ReportEntries
SpecEvents SpecEvents
}Methods:
func (report SpecReport) FullText() string
func (report SpecReport) Failed() bool
func (report SpecReport) IsSerial() bool
func (report SpecReport) IsInOrderedContainer() bool
func (report SpecReport) CombinedOutput() stringExample:
ReportAfterEach(func(report SpecReport) {
if report.Failed() {
fmt.Printf("Failed: %s\n", report.FullText())
fmt.Printf("At: %s\n", report.Failure.Location)
fmt.Printf("Message: %s\n", report.Failure.Message)
// Check if it's in an ordered container
if report.IsInOrderedContainer() {
fmt.Println("Part of ordered specs")
}
// Print all output
fmt.Println("Output:")
fmt.Println(report.CombinedOutput())
}
})Report available during tree construction.
type ConstructionNodeReport struct {
CodeLocation CodeLocation
ContainerHierarchyTexts []string
ContainerHierarchyLocations []CodeLocation
ContainerHierarchyLabels [][]string
SuiteConfig SuiteConfig
}Methods:
func (report ConstructionNodeReport) FullText() string
func (report ConstructionNodeReport) Labels() []stringComplete suite execution report.
type Report struct {
// Suite identification
SuiteDescription string
SuitePath string
SuiteSucceeded bool
// Timing
StartTime time.Time
EndTime time.Time
RunTime time.Duration
// Configuration and stats
SuiteConfig SuiteConfig
PreRunStats PreRunStats
SpecReports SpecReports
// Special conditions
SuiteHasProgrammaticFocus bool
SpecialSuiteFailureReasons []string
}Methods:
func (report Report) Add(other Report) ReportIndividual report entry attached to a spec.
type ReportEntry struct {
Visibility ReportEntryVisibility
Location CodeLocation
Time time.Time
TimelineLocation TimelineLocation
Name string
Value any
}Methods:
func (entry ReportEntry) GetRawValue() any
func (entry ReportEntry) StringRepresentation() stringCollection of report entries with query methods.
type ReportEntries []ReportEntryMethods:
func (entries ReportEntries) HasVisibility(
visibilities ...ReportEntryVisibility,
) ReportEntries
func (entries ReportEntries) WithName(name string) ReportEntryExample:
ReportAfterEach(func(report SpecReport) {
// Get all entries visible on failure
failureEntries := report.ReportEntries.HasVisibility(
types.ReportEntryVisibilityFailureOrVerbose,
)
for _, entry := range failureEntries {
fmt.Printf("%s: %v\n", entry.Name, entry.Value)
}
// Find specific entry
debugEntry := report.ReportEntries.WithName("debug-info")
if debugEntry.Name != "" {
fmt.Printf("Debug: %v\n", debugEntry.Value)
}
})var timings = map[string]time.Duration{}
ReportBeforeEach(func(report SpecReport) {
timings[report.FullText()] = time.Now()
})
ReportAfterEach(func(report SpecReport) {
start := timings[report.FullText()]
duration := time.Since(start)
AddReportEntry("Precise duration", duration)
})AfterEach(func() {
report := CurrentSpecReport()
if report.Failed() {
// Add debug information only on failure
AddReportEntry("System State", captureSystemState())
AddReportEntry("Recent Logs", getRecentLogs())
AddReportEntry("Memory Usage", getMemoryStats())
}
})var totalSpecs int
var completedSpecs int
ReportBeforeSuite(func(report Report) {
totalSpecs = report.PreRunStats.SpecsThatWillRun
completedSpecs = 0
})
ReportAfterEach(func(report SpecReport) {
completedSpecs++
progress := float64(completedSpecs) / float64(totalSpecs) * 100
fmt.Printf("Progress: %.1f%% (%d/%d)\n",
progress, completedSpecs, totalSpecs)
})var slowSpecs []SpecReport
ReportAfterEach(func(report SpecReport) {
threshold := 5 * time.Second
if report.RunTime > threshold {
slowSpecs = append(slowSpecs, report)
AddReportEntry("Slow spec warning",
fmt.Sprintf("Took %v", report.RunTime))
}
})
ReportAfterSuite("slow specs report", func(report Report) {
if len(slowSpecs) > 0 {
fmt.Println("Slow specs:")
for _, spec := range slowSpecs {
fmt.Printf(" %s: %v\n", spec.FullText(), spec.RunTime)
}
}
})ReportAfterEach(func(report SpecReport) {
// Send metrics to monitoring system
metrics.RecordSpecDuration(
report.FullText(),
report.RunTime.Seconds(),
)
if report.Failed() {
metrics.IncrementFailureCount(report.FullText())
// Send alert for critical tests
if slices.Contains(report.LeafNodeLabels, "critical") {
alerting.SendAlert("Critical test failed: " + report.FullText())
}
}
})ReportAfterSuite("CSV report generator", func(report Report) {
// Generate custom CSV report
file, _ := os.Create("test-results.csv")
defer file.Close()
writer := csv.NewWriter(file)
writer.Write([]string{"Spec", "State", "Duration", "Attempts"})
for _, spec := range report.SpecReports {
writer.Write([]string{
spec.FullText(),
spec.State.String(),
spec.RunTime.String(),
fmt.Sprintf("%d", spec.NumAttempts),
})
}
writer.Flush()
})