Public/Format-Pester.ps1

Function Format-Pester {
<#
    .SYNOPSIS
    Document Pester's tests results into the selected format (HTML, Word, Text).
    
    .DESCRIPTION
    Create documents in formats: HTML, Word, Text using PScribo PowerShell module. Documents are preformated to be human friendly.
    Local Word installation is not needed to be installed on the computers were documents.
     
    Additional languages (other than en-US) can be used - please read info for translator on the project web page.
     
    .PARAMETER PesterResult
    Specifies the Pester results Object
         
    .PARAMETER Format
    Specifies the document format. Might be:
    - Text
    - HTML
    - Word
     
    .PARAMETER Path
    Specifies where the documents will be stored. Default is the path where is executed this function.
     
    .PARAMETER BaseFileName
    Specifies the document name. Default is 'Pester_Results'.
     
    .PARAMETER Order
    Specify what results need to be evaluated first - passed or failed - means that will be included on the top of report.
    By default failed tests are evaluated first.
     
    .PARAMETER GroupResultsBy
    Select how results should be groupped. Available options: Result, Result-Describe, Result-Describe-Context.
     
    .PARAMETER PassedOnly
    Select to return information about passed tests only.
     
    .PARAMETER FailedOnly
    Select to return information about failed tests only.
     
    .PARAMETER SummaryOnly
    Select to return only summaries for tests only (sums of numbers passed/failed/etc. tests).
     
    .PARAMETER SkipTableOfContent
    Select to skip adding table of content at the begining of document(s).
         
    .PARAMETER SkipSummary
    Select to skip adding table with test summaries (sums of numbers passed/failed/etc. tests).
     
    .PARAMETER Language
    Select language what need to be used for generated reports.
    By default language is detected by Get-Culture with fallback to en-US if translation is not available.
     
    .PARAMETER Version
    Use that parameter to display version of Format-Pester only.
    This parameter can be used to verify translations.
     
    .PARAMETER DumpPScriboObject
    When DumpPscriboObject is used the result of the function is custom object containing PScribo Document.
    Use this parameter for prepare tests or debug of document generation.
     
    .INPUTS
    An expected input is the result of the command Invoke-Pester with the parameter -PassThru.
    With that command Invoke-Pester returns a custom object (PSCustomObject) that contains the test results.
     
    .OUTPUTS
    Files what contain results of test. Files format and structure is based on values of parameters used.
     
    .EXAMPLE
    Invoke-Pester -PassThru | Format-Pester -Path . -Format HTML,Word,Text -BaseFileName 'PesterResults'
 
    This command will document the results of the Pester's tests.
    Documents will be stored in the current path and they will be available in 3 formats (.html,.docx and .txt).
     
    .LINK
    https://github.com/equelin/Format-Pester
         
    .NOTES
    Initial author: Erwan Quelin
     
    Credits/coauthors:
    - Travis Plunk, github[at]ez13[dot]net
    - Wojciech Sciesinski, wojciech[at]sciesinski[dot]net
     
    LICENSE
    Licensed under the MIT License - https://github.com/equelin/Format-Pester/blob/master/LICENSE
 
    TODO
    - Pester test need to be updated - yes, post factum TDD ;-)
             
  #>

    
    [CmdletBinding(DefaultParameterSetName = 'AllParamSet')]
    [OutputType([IO.FileInfo])]
    Param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True, HelpMessage = 'Pester results Object', ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True, HelpMessage = 'Pester results Object', ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True, HelpMessage = 'Pester results Object', ParameterSetName = 'FailedOnlyParamSet')]
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True, HelpMessage = 'Pester results Object', ParameterSetName = 'SummaryOnlyParamSet')]
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True, HelpMessage = 'Pester results Object', ParameterSetName = 'VersionOnlyParamSet')]
        [Array]$PesterResult,
        [Parameter(Mandatory = $true, HelpMessage = 'PScribo export format', ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $true, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FailedOnlyParamSet')]        
        [Parameter(Mandatory = $true, ParameterSetName = 'SummaryOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'VersionOnlyParamSet')]
        [ValidateSet('Text', 'Word', 'HTML')]
        [String[]]$Format,
        [Parameter(Mandatory = $false, HelpMessage = 'PScribo export path', ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SummaryOnlyParamSet')]
        [ValidateNotNullorEmpty()]
        [String]$Path = (Get-Location -PSProvider FileSystem),
        [ValidateNotNullorEmpty()]
        [string]$BaseFileName = 'Pester_Results',
        [Parameter(Mandatory = $false, ParameterSetName = 'AllParamSet')]
        [ValidateSet('FailedFirst', 'PassedFirst')]
        [String]$Order = 'FailedFirst',
        [Parameter(Mandatory = $false, ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [ValidateSet('Result', 'Result-Describe', 'Result-Describe-Context')]
        [String]$GroupResultsBy = 'Result',
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Switch]$PassedOnly,
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [Switch]$FailedOnly,
        [Parameter(Mandatory = $false, ParameterSetName = 'SummaryOnlyParamSet')]
        [switch]$SummaryOnly,
        [Parameter(Mandatory = $false, ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SummaryOnlyParamSet')]
        [Switch]$SkipTableOfContent,
        [Parameter(Mandatory = $false, ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [Switch]$SkipSummary,
        [Parameter(Mandatory = $false, ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SummaryOnlyParamSet')]
        [String]$Language = $($(Get-Culture).Name),
        [Parameter(Mandatory = $false, ParameterSetName = 'VersionOnlyParamSet')]
        [Switch]$Version,
        [Parameter(Mandatory = $false, ParameterSetName = 'AllParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'PassedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FailedOnlyParamSet')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SummaryOnlyParamSet')]
        [Switch]$DumpPScriboObject
    )
    
    [Version]$ScriptVersion = "1.4.0"
    
    If ($Version.IsPresent) {
        
        Return $ScriptVersion.ToString()
        
        Break
        
    }
    Else {
        
        if ($null -eq $PesterResult) {
            
            $MessageText = "Value of the parameter PesterResult can't be null or empty."
            
            Throw $MessageText
            
        }
        
    }
    
    Import-LocalizedData -FileName Format-Pester.psd1 -BindingVariable LocalizedStrings -UICulture $Language -ErrorAction SilentlyContinue
    
    If ([String]::IsNullOrEmpty($LocalizedStrings)) {
        
        Import-LocalizedData -FileName Format-Pester.psd1 -BindingVariable LocalizedStrings -UICulture 'en-US' -ErrorAction Stop
        
        [String]$MessageText = "{0} {1} {2}" -f $LocalizedStrings.msg27, $Language, $LocalizedStrings.msg28
        
        Write-Verbose -Message $MessageText
        
    }
    
    If ($LocalizedStrings.msg00 -lt $ScriptVersion) {
        
        [String]$MessageText = "{0}" -f $LocalizedStrings.msg29
        
        Write-Warning -Message $MessageText
        
    }
    
    $TextFileEncoding = $LocalizedStrings.msg32 
    
    #LocalizedStrings are not sorted alphabeticaly -even if you are using Sort-Object !
    #$LocalizedStrings
    
    $exportParams = @{ }
    if ($Format.Count -eq 1 -and $Format -eq 'HTML') {
        
        $exportParams += @{
            
            Options = @{ NoPageLayoutStyle = $true }
        }
        
        
    }
    elseif ($Format -contains 'text' -and $TextFileEncoding -ne 'ASCII') {
        
        $exportParams += @{
            
            Options = @{ Encoding = $TextFileEncoding }
            
        }
        
        
    }
    
    
    $PScriboObject = Document $BaseFileName {
        
        # Global options
        GlobalOption -PageSize A4
        
        
        #Variables used to create numbers for TOC and subsections
        $Head1Counter = 1
        
        If (-not $SkipTableOfContent.ispresent) {
            
            # Table of content header text
            [String]$TOCName = $LocalizedStrings.msg01
            
            TOC -Name $TOCName
            
        }
        
        If (-not $SkipSummary.IsPresent) {
            
            # Columns used for the summary table
            
            #This variable can't be translated
            $SummaryColumnsData = @('TotalCount', 'PassedCount', 'FailedCount', 'SkippedCount', 'PendingCount')
            
            $SummaryColumnsHeaders = @($LocalizedStrings.msg02, $LocalizedStrings.msg03, $LocalizedStrings.msg04, $LocalizedStrings.msg05, $LocalizedStrings.msg06)
            
            # Style definitions used for the summary table
            Style -Name Total -Color White -BackgroundColor Blue
            Style -Name Passed -Color White -BackgroundColor Green
            Style -Name Failed -Color White -BackgroundColor Red
            Style -Name Other -Color White -BackgroundColor Gray
            
            # Results Summary
            
            $ResultsSummaryTitle = "{0}.`t{1}" -f $Head1Counter, $LocalizedStrings.msg07
            
            $Head1Counter++
            
            $ValidResults = $PesterResult | Where-Object { $null -ne $_.TotalCount } | Sort-Object -Property FailedCount -Descending
            Section -Name $ResultsSummaryTitle -Style Heading2 -ScriptBlock {
                
                $ValidResults | Set-Style -Style 'Total' -Property 'TotalCount'
                $ValidResults | Set-Style -Style 'Passed' -Property 'PassedCount'
                $ValidResults | Set-Style -Style 'Failed' -Property 'FailedCount'
                $ValidResults | Set-Style -Style 'Other' -Property 'SkippedCount'
                $ValidResults | Set-Style -Style 'Other' -Property 'PendingCount'
                $ValidResults | Table -Columns $SummaryColumnsData -Headers $SummaryColumnsHeaders -Width 90
                
            }
            
        }
        
        If (-not $SummaryOnly.IsPresent) {
            
            #Expanding Pester summary to receive all tests results
            $PesterTestsResults = $PesterResult | Select-Object -ExpandProperty TestResult
            
            [Array]$EvaluateResults = $null
            
            If ((-not $PassedOnly.IsPresent) -and $PesterResult.FailedCount -gt 0) {
                
                $EvaluateResults += 'Failed'
                
            }
            
            If ((-not $FailedOnly.IsPresent) -and $PesterResult.PassedCount -gt 0) {
                
                $EvaluateResults += 'Passed'
                
                If ($Order -eq 'PassedFirst') {
                    
                    $EvaluateResults = $($EvaluateResults | Sort-Object -Descending)
                    
                }
                
            }
                        
            foreach ($CurrentResultType in $EvaluateResults) {
                
                switch ($CurrentResultType) {
                    
                    'Passed' {
                        
                        $CurrentResultTypeLocalized = $LocalizedStrings.msg09
                        
                        $Head1SectionTitle = $LocalizedStrings.msg25
                        
                        $Header1TitlePart = $LocalizedStrings.msg10
                        
                        $Header2TitlePart = $LocalizedStrings.msg11
                        
                        $Header3TitlePart = $LocalizedStrings.msg12
                        
                        $VerboseMsgHeader2Part = $LocalizedStrings.msg13
                        
                        $VerboseMsgHeader3Part = $LocalizedStrings.msg14
                        
                        #This variable can't be translated
                        $TestsResultsColumnsData = @('Describe', 'Context', 'Name')
                        
                        $TestsResultsColumnsHeaders = @($LocalizedStrings.msg15, $LocalizedStrings.msg16, $LocalizedStrings.msg17)
                        
                    }
                    
                    
                    'Failed' {
                        
                        $CurrentResultTypeLocalized = $LocalizedStrings.msg18
                        
                        $Head1SectionTitle = $LocalizedStrings.msg26
                        
                        $Header1TitlePart = $LocalizedStrings.msg19
                        
                        $Header2TitlePart = $LocalizedStrings.msg20
                        
                        $Header3TitlePart = $LocalizedStrings.msg21
                        
                        $VerboseMsgHeader2Part = $LocalizedStrings.msg22
                        
                        $VerboseMsgHeader3Part = $LocalizedStrings.msg23
                        
                        #This variable can't be translated
                        $TestsResultsColumnsData = @('Context', 'Name', 'FailureMessage')
                        
                        $TestsResultsColumnsHeaders = @($LocalizedStrings.msg16, $LocalizedStrings.msg17, $LocalizedStrings.msg24)
                        
                    }
                    
                }
                
                $VerboseMsgMainLoop = $LocalizedStrings.msg08
                
                [String]$MessageText = "{0} {1} " -f $VerboseMsgMainLoop, $CurrentResultTypeLocalized
                
                Write-Verbose -Message $MessageText
                
                $Head2counter = 1
                
                $Head3counter = 1
                
                If (-not $PassedOnly.IsPresent) {
                    
                    $CurrentPesterTestResults = $PesterTestsResults | Where-object -FilterScript { $_.Result -eq $CurrentResultType }
                    
                    If ($GroupResultsBy -eq 'Result') {
                        
                        [String]$Header1Title = "{0}.`t {1}" -f $Head1counter, $Header1TitlePart
                        
                        Section -Name $Header1Title -Style Heading1   {
                            
                            $CurrentPesterTestResults |
                            Table -Columns $TestsResultsColumnsData -Headers $TestsResultsColumnsHeaders -Width 90
                            
                        }
                        
                        $Head1counter++
                        
                    }
                    
                    Else {
                        
                        Section -Name "$Head1Counter.`t $Head1SectionTitle " -Style Heading1 -ScriptBlock {
                            
                            #Get unique 'Describe' from Pester results
                            [Array]$Headers2 = $CurrentPesterTestResults | Select-Object -Property Describe -Unique
                            
                            # Tests results details - Grouped by Describe
                            foreach ($Header2 in $Headers2) {
                                
                                [String]$MessageText = "{0}: {1} " -f $VerboseMsgHeader2Part, $($Header2.Describe)
                                
                                Write-Verbose -Message $MessageText
                                
                                $SubHeader2Number = "{0}.{1}" -f $Head1Counter, $Head2counter
                                
                                [String]$Header2Title = "{0}.`t {1} {2}" -f $SubHeader2Number, $Header2TitlePart, $($Header2.Describe)
                                
                                Section -Name $Header2Title -Style Heading2 -ScriptBlock {
                                    
                                    $CurrentPesterTestResults2 = $CurrentPesterTestResults | Where-Object -FilterScript { $_.Describe -eq $Header2.Describe }                                    
                                    
                                    $CurrentPesterTestResultsCount2 = ($CurrentPesterTestResults2 | Measure-Object).Count
                                    
                                    If ($GroupResultsBy -eq 'Result-Describe-Context') {
                                        
                                        [Array]$Headers3 = $CurrentPesterTestResults2 | Select-Object -Property Context -Unique
                                        
                                        foreach ($Header3 in $Headers3) {
                                            
                                            [String]$MessageText = "{0}: {1} " -f $VerboseMsgHeader3Part, $($Header3.Context)
                                            
                                            Write-Verbose -Message $MessageText
                                            
                                            $CurrentPesterTestResults3 = $CurrentPesterTestResults2 | Where-Object -FilterScript { $_.Context -eq $Header3.Context }
                                            
                                            $CurrentPesterTestResultsCount3 = ($CurrentPesterTestResults3 | Measure-Object).Count
                                            
                                            $SubHeader3Number = "{0}.{1}.{2}" -f $Head1Counter, $Head2counter, $Head3counter
                                            
                                            [String]$Header3Title = "{0}.`t {1} {2}" -f $SubHeader3Number, $Header3TitlePart, $($Header3.Context)
                                            
                                            Section -Name $Header3Title -Style Heading3 -ScriptBlock {
                                                
                                                $MessageText = "{0} {1} {2}, {3} {4}" -f $LocalizedStrings.msg30, $Header3TitlePart, $($Header3.Context), $LocalizedStrings.msg31, $CurrentPesterTestResultsCount3
                                                
                                                Write-Verbose -Message $MessageText
                                                
                                                $CurrentPesterTestResults3 |
                                                Table -Columns $TestsResultsColumnsData -Headers $TestsResultsColumnsHeaders -Width 90
                                            }
                                            
                                            $Head3Counter++
                                            
                                        }
                                        
                                    } #$GroupResultsBy -eq 'Result-Describe-Context'
                                    Else {
                                        
                                        $MessageText = "{0} {1} {2}, {3}: {4}" -f $LocalizedStrings.msg30, $Header3TitlePart, $($Header3.Context), $LocalizedStrings.msg31, $CurrentPesterTestResultsCount3
                                        
                                        Write-Verbose -Message $MessageText
                                        
                                        $CurrentPesterTestResults2 |
                                        Table -Columns $TestsResultsColumnsData -Headers $TestsResultsColumnsHeaders -Width 90
                                        
                                    }
                                    
                                }
                                
                                $Head2counter++
                                
                            } #end foreach ($Header2 in $Headers2)
                            
                        }
                        
                        $Head1Counter++
                        
                    } #end $GroupResultsBy -ne 'Result'
                    
                }
                
            }
            
        }
        
    }
    
    If ($DumpPScriboObject.IsPresent) {
        
        Return $PScriboObject
        
    }
        
        $PScriboObject | Export-Document -Path $Path -Format $Format @exportParams
     
}