PesterLove.psm1
#Generated at 07/06/2023 11:16:50 by Stephane van Gulick #Thanks June Blender for this tip #https://www.sapien.com/blog/2014/10/21/a-better-tostring-method-for-hash-tables/ Update-TypeData -TypeName System.Collections.HashTable ` -MemberType ScriptMethod ` -ErrorAction SilentlyContinue ` -MemberName ToString ` -Value { $hashstr = "@{"; $keys = $this.keys; foreach ($key in $keys) { $v = $this[$key]; if ($key -match "\s") { $hashstr += "`"$key`"" + "=" + $v} else { $hashstr += $key + "=" + $v } }; $hashstr += "}"; return $hashstr } Enum PesterType { It Describe Context InModuleScope } Class PesterBlockFactory { [PesterBlock[]] Static Create([System.IO.FileInfo]$File){ $Blocks = [ASTHelper]::ConvertToPesterASTBlocks($File) $PesterBlocks = @() Foreach($Block in $Blocks){ $PesterBlocks += [PesterBlockFactory]::Create($Block) } return $PesterBlocks } [PesterBlock[]] Static Create([System.Management.Automation.Language.ConstantExpressionAst]$Block){ switch($Block.Value){ 'Describe'{ $Hash = @{value='';content=''} $Hash.ElementType = $Block.Value $Hash.Content = $Block.Parent.Extent.Text $Hash.Name = $Block.Parent.commandElements.Value[1] $Hash.Value = $Block.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text $tags = @() #$Obj = [PesterDescribeBlock]::New($Hash.Name,$Hash.ItBlocks,[PesterType]::Describe,$Hash.Content,[String[]]$Tags) return [PesterDescribeBlock]::New($Block) ;Break } 'It'{ $Hash = @{value='';content=''} $Hash.ElementType = $Block.Value $Hash.Content = $Block.Parent.Extent.Text $Hash.Name = $Block.Parent.commandElements.Value[1] $Hash.Value = $Block.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text #$d.Parent.CommandElements.Scriptblock.Extent If($block.Parent.Commandelements | ? {$_.ParameterName -eq 'skip'}){ $Hash.IsSkipped = $True }else{ $Hash.IsSkipped = $False } If($block.Parent.Commandelements | ? {$_.ParameterName -eq 'Pending'}){ $Hash.IsPending = $True }else{ $Hash.IsPending = $False } $PassedTestCases = $null $TestCasesFound = $False Foreach($par in $Block.Parent.Commandelements){ If($TestCasesFound -Eq $False){ If($par.ParameterName -eq 'TestCases'){ $TestCasesFound = $True } continue } If($TestCasesFound){ $PassedTestCases = $par } } If(!($PassedTestCases)){ $Hash.TestCases = $Null }Else{ $Text = $PassedTestCases $Regex = "(@\{(?<key>\w+)\s=\s(?<value>\s*( |'|`")\w*( |'|`"))}){1,}" $AllMatches = [Regex]::Matches($Text,$Regex) [HashTable[]]$ArrHash = @() Foreach($mat in $AllMatches){ $h = @{} $key = ($mat.groups | ? {$_.Name -eq 'key'}).Value If(!($key)){ #Not correct capturing group Continue } $Value = ($mat.groups | ? {$_.Name -eq 'Value'}).Value $h.$Key = $value $ArrHash += $h } $Hash.TestCases = $ArrHash } #$Hash.TestCases = $d.Parent.Parent.PipelineElements.CommandElements[-1].Elements $Obj = [PesterItBlock]::New($Hash.Name,$Hash.Content,[PesterType]::It,$Hash.Value,$Hash.TestCases) $Obj.SetPending($Hash['IsPending']) $Obj.SetSkipped($Hash['IsSkipped']) $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($Block)) $PB = @() foreach($Child in $AllChilds){ $PB += [PesterBlockFactory]::Create($Child) } $obj.AddChild($PB) REturn $Obj } 'Context'{ #$ContextName = $Block.Parent.commandElements.Value[1] #$ContextValue = $Block.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text Return [PesterContextBlock]::New($Block) } 'InModuleScope'{ Return [PesterInModuleScopeBlock]::New($Block) } Default { Throw "Block $($Block.Value) is of unsupported type." } } Throw "Parameter Block value is currently unsupported." } } Class PesterBlock { Hidden $Raw [System.Management.Automation.Language.ConstantExpressionAst[]]$RawBlocks = @() [PesterType]$Type PesterBlock(){} PesterBlock([String]$String){ $this.ConvertToAST($String) } #converts input file or string to AST blocks ConvertToAST([String]$String){ $P = Get-Item -path $String -ErrorAction SilentlyContinue IF($P.Exists){ $ParsedAST = [System.Management.Automation.Language.Parser]::ParseFile($P.FullName, [ref]$null, [ref]$Null) }else{ $ParsedAST = [System.Management.Automation.Language.Parser]::ParseInput($String,[ref]$null, [ref]$Null) } $This.Raw = $ParsedAST.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true) $This.SetRawPesterBlocks() #fills in $this.RawBlocks variable } #Sorts the AST blocks to only return Describe,IT, or Context AST blocks. SetRawPesterBlocks(){ If(!($this.Raw)){ Throw "No pester code found." } $PesterBlocks = $this.Raw | ? {($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Describe') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'It') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'InModuleScope')} $This.RawBlocks = $PesterBlocks } [System.Management.Automation.Language.ConstantExpressionAst[]] GetRawPesterBlocks(){ return $This.RawBlocks } [String] GetChildBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block){ return ($block.Parent.CommandElements | ? {$_.StaticType.Name -eq 'ScriptBlock' }).ScriptBlock.EndBlock.Extent.Text } } Class PesterInModuleScopeBlock : PesterBlock { [String]$Name [PesterType]$Type = [PesterType]::InModuleScope [PesterBlock[]]$Childs PesterInModuleScopeBlock(){ } PesterInModuleScopeBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block){ $this.Name = $Block.Parent.commandElements.Value[1] #A move dans base constructor? $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($block)) $PB = @() foreach($Child in $AllChilds){ $PB += [PesterBlockFactory]::Create($Child) } $This.Childs = $PB } [String] ToString() { $sb = [System.Text.StringBuilder]::new() $FormatedString = 'InModuleScope "{0} " {1}' -f $this.Name, "-ScriptBlock { " [void]$sb.AppendLine($FormatedString) $EndString = "} " foreach($Child in $this.Childs){ $Sb.AppendLine() $sb.AppendLine($Child.ToString()) } [void]$Sb.AppendLine($EndString) return $sb.ToString() } } } Class PesterItBlock :PesterBlock{ [String]$Name [String]$Value [PesterType]$Type = [PesterType]::It [String]$Test [PesterBlock[]]$Childs [HashTable[]]$TestCases = @() [Bool]$Pending = $false [Bool]$Skipped = $False PesterITBlock([String]$Name,[String]$TestContent){ $this.Name = $Name $this.Test = $TestContent } PesterITBlock([String]$Name,[String]$Value,[PesterType]$Type,[String]$TestContent,[HashTable[]]$TestCases){ $this.Name = $Name $this.Value = $Value $this.Type = $Type $this.Test = $TestContent $This.TestCases = $TestCases } SetPending([Bool]$IsPending){ $this.Pending = $IsPending } [Bool] IsPending(){ return $This.Pending } SetSkipped([Bool]$IsSkipped){ $this.Skipped = $IsSkipped } [Bool] IsSkipped(){ return $This.Skipped } [String]ToString(){ $sb = [System.Text.StringBuilder]::new() $FormatedString = 'It "{0}" {1}' -f $this.Name,"{" [void]$sb.AppendLine($FormatedString) $EndString = "} " If($this.TestCases){ $EndString += '-TestCases {0} ' -f $this.ConvertTestCasesToHashTableToString() } If($this.IsPending()){ $EndString += "{0}" -f "-Pending " } If($This.IsSkipped()){ $EndString += "{0}" -f "-Skip " } [void]$Sb.AppendLine($this.Test) [void]$Sb.AppendLine($EndString) return $sb.ToString() } Hidden [String] ConvertTestCasesToHashTableToString(){ If($this.TestCases){ $TestCaseString = "" Foreach($ht in $this.TestCases){ $TestCaseString = $TestCaseString + $ht.ToString() + "," } $TestCaseString = $TestCaseString.TrimEnd(",") return $TestCaseString }else{ return "" } } AddChild([PesterBlock[]]$Child){ $this.Childs += $Child } AddTestCase([HashTable[]]$Testcase){ $this.TestCases += $Testcase } } Class PesterDescribeBlock : PesterBlock { [String]$Name [PesterBlock[]]$Childs [PesterType]$Type [String]$Fixture [String[]]$Tags PesterDescribeBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block){ $this.Name = $Block.Parent.commandElements.Value[1] $This.Fixture = $Block.Parent.Extent.Text $this.Type = [PesterType]::Describe #A move dans base constructor? $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($block)) $PB = @() foreach($Child in $AllChilds){ $PB += [PesterBlockFactory]::Create($Child) } $This.Childs = $PB } PesterDescribeBlock([String]$Name,[PesterBlock[]]$Childs,[PesterType]$Type,[String]$Fixture,[String[]]$Tags){ $this.Name = $Name $this.Childs = $Childs $this.Type = $Type $this.Fixture = $Fixture $This.Tags = $Tags } [String[]] GetTag(){ return $this.Tags } [String[]] GetTag([String]$Tag){ return $this.Tags | ? {$_ -eq $Tag} } [String]ToString() { $sb = [System.Text.StringBuilder]::new() $FormatedString = 'Describe "{0}" {1} {2}' -f $this.Name, "-Fixture ", "{" [void]$sb.AppendLine($FormatedString) $EndString = "} " If ($this.GetTag()) { $EndString += "{0}" -f "-Tag " } If ($This.Childs()) { Foreach ($Child in $this.Childs) { $sb.AppendLine() $Sb.AppendLine($Child.ToString()) } } [void]$Sb.AppendLine($this.Test) [void]$Sb.AppendLine($EndString) return $sb.ToString() } } Class PesterScript { [System.IO.FileInfo]$path [PesterBlock[]]$PesterBlocks PesterScript([System.Io.FileInfo]$Path){ $this.Path = $Path $This.PesterBlocks = [PesterBlockFactory]::Create($This.path.FullName) } } Class PesterContextBlock : PesterBlock { [String]$Name [PesterBlock[]]$Childs PesterContextBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block){ $this.Name = $Block.Parent.commandElements.Value[1] $this.Type = [PesterType]::Context #A move dans base constructor? $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($block)) $PB = @() foreach($Child in $AllChilds){ $PB += [PesterBlockFactory]::Create($Child) } $This.Childs = $PB #$this.PesterChildBlock = [PesterBlockFactory]::New($Object) } PesterContextBlock([String]$Name,[System.Management.Automation.Language.StringConstantExpressionAst]$Object){ $this.Name = $Name $PB = [PesterBlockFactory]::Create($Object) $This.PesterBlock = $PB } PesterContextBlock([String]$Name,[PesterBlock[]]$ChildBlock){ $this.Name = $Name $this.PesterChildBlock = [PesterBlockFactory]::Create($ChildBlock) } } Class ASTHelper { static [System.Management.Automation.Language.StringConstantExpressionAst[]] ConvertToAST([String]$String){ $Arr = @() $P = Get-Item -path $String -ErrorAction SilentlyContinue IF($P.Exists){ $ParsedAST = [System.Management.Automation.Language.Parser]::ParseFile($P.FullName, [ref]$null, [ref]$Null) }else{ $ParsedAST = [System.Management.Automation.Language.Parser]::ParseInput($String,[ref]$null, [ref]$Null) } $arr += $ParsedAST.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true) return $Arr } static [System.Management.Automation.Language.StringConstantExpressionAst[]] ConvertToPesterASTBlocks([String]$String){ $bl = [astHelper]::ConvertToAST($string) $arr = @() foreach($b in $bl){ $arr += [astHelper]::GetPesterBlock($b) } return $arr } static [System.Management.Automation.Language.StringConstantExpressionAst[]] GetPesterBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block){ $PesterBlocks = @() $PesterBlocks += $Block | ? {($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Describe') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'It') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'InModuleScope')} Return $PesterBlocks } static [String] GetChildBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block){ return ($block.Parent.CommandElements | ? {$_.StaticType.Name -eq 'ScriptBlock' }).ScriptBlock.EndBlock.Extent.Text } } Class PesterResultsDocument { [System.IO.FileInfo]$Path $Data PesterResultsDocument([String]$Path) { $this.Path = $Path $This.Data = $this.GetData() } [Object]GetData() { $Item = Get-Item $this.Path.FullName If ($Item -is [System.IO.FileInfo]) { If ($Item.Extension -eq '.xml') { [xml]$x = Get-Content -Path $Item.FullName Return $x.'test-results' | ConvertTo-Json -Depth 8 }ElseIf ($Item.Extension -eq '.clixml') { Return Import-Clixml -path $Item.FullName } } Throw 'Folder type is currently not supported' } } Function Get-PLPesterBlock { [CmdletBinding()] Param( $Path, $InputObject ) if($Path){ $P = Get-Item -path $Path $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null) }elseif($InputObject){ $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null) } $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true) $Data = @() $ContextData = @() $BareWords = @() $Blocks += $String | ? {$_.StringConstantType -eq "BareWord"} foreach($block in $Blocks){ switch($block.Value){ 'Describe'{ ;Break } 'It'{;Break} 'Context'{;Break} } } $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'it'} $ContextData += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'} $AllItBlocks = @() Foreach($d in $data){ $Hash = @{value='';content=''} $Hash.ElementType = $d.Value $Hash.Content = $d.Parent.Extent.Text $Hash.Name = $d.Parent.commandElements.Value[1] $Hash.Value = $d.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text #$d.Parent.CommandElements.Scriptblock.Extent If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'skip'}){ $Hash.IsSkipped = $True }else{ $Hash.IsSkipped = $False } If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'Pending'}){ $Hash.IsPending = $True }else{ $Hash.IsPending = $False } $PassedTestCases = $null $TestCasesFound = $False Foreach($par in $d.Parent.Commandelements){ If($TestCasesFound -Eq $False){ If($par.ParameterName -eq 'TestCases'){ $TestCasesFound = $True } continue } If($TestCasesFound){ $PassedTestCases = $par } } #$PassedTestCases = $d.Parent.Commandelements | ? {$_.ParameterName -eq 'TestCases'} #$d.Parent.CommandElements[4].Extent.Text If(!($PassedTestCases)){ $Hash.TestCases = $Null }Else{ $Text = $PassedTestCases $Regex = "(@\{(?<key>\w+)\s=\s(?<value>\s*( |'|`")\w*( |'|`"))}){1,}" $AllMatches = [Regex]::Matches($Text,$Regex) [HAshTable[]]$ArrHash = @() Foreach($mat in $AllMatches){ $h = @{} $key = ($mat.groups | ? {$_.Name -eq 'key'}).Value If(!($key)){ #Not correct capturing group Continue } $Value = ($mat.groups | ? {$_.Name -eq 'Value'}).Value $h.$Key = $value $ArrHash += $h } $Hash.TestCases = $ArrHash } #$Hash.TestCases = $d.Parent.Parent.PipelineElements.CommandElements[-1].Elements $Obj = [PesterItBlock]::New($Hash.Name,$Hash.Value,[PesterType]::It,$Hash.Content,$Hash.TestCases) $Obj.SetPending($Hash['IsPending']) $Obj.SetSkipped($Hash['IsSkipped']) $AllItBlocks += $Obj } return $AllItBlocks } Function Get-PLPesterContextBlock { <# .SYNOPSIS Get all context blocks present in a pester script. .DESCRIPTION Returns all context blocks, and it's child blocks. .EXAMPLE PS C:\> <example usage> Explanation of what the example does .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES Author: Stéphane van Gulick #> [CmdletBinding()] Param( $Path, $InputObject ) if($Path){ $P = Get-Item -path $Path $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null) }elseif($InputObject){ $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null) } $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true) $Data = @() $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'} $AllDescribeBlocks = @() Foreach($d in $data){ $Hash = @{} $Hash.ElementType = $d.Value $Hash.ItBlocks = @() $Hash.Tags = "" $Hash.ItBlocks += Get-PLPesterITBlock -InputObject $d.Parent.Extent.Text $Hash.ContextBlocks = @() $Hash.ContextBlocks += Get-PLPesterContextBlock -InputObject $d.Parent.Extent.Text $Hash.Content = $d.Parent.Extent.Text $Obj = [PesterDescribeBlock]::New($Hash.Name,$Hash.ItBlocks,[PesterType]::Describe,$Hash.Content,[String[]]$Tags) $Pattern = '^.*-tag(?<Tags>.*$)' $Options = @() $Options += [System.Text.RegularExpressions.RegexOptions]::Multiline $Options += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase $rgx = [regex]::New($Pattern,$Options) $MyMatches = $rgx.Match($d.Parent.Extent.Text) $Hash.Tags = $MyMatches.Groups['Tags'].Value $Obj.Tags = $Hash.Tags $AllDescribeBlocks += $Obj } Return $AllDescribeBlocks } Function Get-PLPesterDescribeBlock { <# .SYNOPSIS Rturns all the Describe Bloks present in a pester script. .DESCRIPTION Will get all script blocks and it's child blocks. .EXAMPLE Get-PLPesterDescribeBlock -Path C:\Code\001.ps1 Name : Childs : {} Type : Describe Fixture : Describe "My Describe Block"{ it "simple it block"{ $true | should be $false } it "Second it block"{ $true | should be $false } -Pending } Tags : {} RawBlocks : {} .PARAMETER Path Path to the script file to process .PARAMETER INPUTObject Send content directly via this parameter. .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES Author: Stéphane van Gulick #> [CmdletBinding()] Param( $Path, $InputObject ) if($Path){ $P = Get-Item -path $Path $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null) }elseif($InputObject){ $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null) } $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true) $Data = @() $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Describe'} $AllDescribeBlocks = @() Foreach($d in $data){ $Hash = @{} $Hash.ElementType = $d.Value $Hash.ItBlocks = @() $Hash.Tags = "" $Hash.ItBlocks += Get-PLPesterITBlock -InputObject $d.Parent.Extent.Text $Hash.ContextBlocks = @() $Hash.ContextBlocks += Get-PLPesterContextBlock -InputObject $d.Parent.Extent.Text $Hash.Content = $d.Parent.Extent.Text $Hash.NAme = $d.Parent.commandElements.Value[1] $Obj = [PesterDescribeBlock]::New($Hash.Name,$Hash.ItBlocks,[PesterType]::Describe,$Hash.Content,[String[]]$Tags) $Pattern = '^.*-tag(?<Tags>.*$)' $Options = @() $Options += [System.Text.RegularExpressions.RegexOptions]::Multiline $Options += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase $rgx = [regex]::New($Pattern,$Options) $MyMatches = $rgx.Match($d.Parent.Extent.Text) $Hash.Tags = $MyMatches.Groups['Tags'].Value $Obj.Tags = $Hash.Tags $AllDescribeBlocks += $Obj } Return $AllDescribeBlocks } Function Get-PLPesterITBlock { <# .SYNOPSIS Get all the Pester IT blocks. .DESCRIPTION Returns all the Pester IT blocks, with any child pester blocks which it may contain. .EXAMPLE Get-PLPesterITBlock -Path C:\Code\Test01.ps1 Name : Context Block Value : Describe "My Describe Block"{ it "simple it block"{ $true | should be $false } it "Second it block"{ $true | should be $false } -Pending } Type : It Test : Context "Context Block"{ Describe "My Describe Block"{ it "simple it block"{ $true | should be $false } it "Second it block"{ $true | should be $false } -Pending } } Childs : TestCases : Pending : False Skipped : False RawBlocks : {} .PARAMETER Path Path to the script file to process .PARAMETER INPUTObject Send content directly via this parameter. .INPUTS Inputs (if any) .OUTPUTS PesterITBlock .NOTES Author: Stéphane van Gulick #> [CmdletBinding()] Param( [Parameter(ParameterSetName='path')] $Path, [Parameter(ParameterSetName='InputObject')] $InputObject ) if($Path){ $P = Get-Item -path $Path $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null) }elseif($InputObject){ $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null) } $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true) $Data = @() $ContextData = @() $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'it' -or $_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'} #$ContextData += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'} $AllItBlocks = @() Foreach($d in $data){ $Hash = @{value='';content=''} $Hash.ElementType = $d.Value $Hash.Content = $d.Parent.Extent.Text $Hash.Name = $d.Parent.commandElements.Value[1] $Hash.Value = $d.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text #$d.Parent.CommandElements.Scriptblock.Extent If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'skip'}){ $Hash.IsSkipped = $True }else{ $Hash.IsSkipped = $False } If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'Pending'}){ $Hash.IsPending = $True }else{ $Hash.IsPending = $False } $PassedTestCases = $null $TestCasesFound = $False Foreach($par in $d.Parent.Commandelements){ If($TestCasesFound -Eq $False){ If($par.ParameterName -eq 'TestCases'){ $TestCasesFound = $True } continue } If($TestCasesFound){ $PassedTestCases = $par } } #$PassedTestCases = $d.Parent.Commandelements | ? {$_.ParameterName -eq 'TestCases'} #$d.Parent.CommandElements[4].Extent.Text If(!($PassedTestCases)){ $Hash.TestCases = $Null }Else{ $Text = $PassedTestCases $Regex = "(@\{(?<key>\w+)\s=\s(?<value>\s*( |'|`")\w*( |'|`"))}){1,}" $AllMatches = [Regex]::Matches($Text,$Regex) [HAshTable[]]$ArrHash = @() Foreach($mat in $AllMatches){ $h = @{} $key = ($mat.groups | ? {$_.Name -eq 'key'}).Value If(!($key)){ #Not correct capturing group Continue } $Value = ($mat.groups | ? {$_.Name -eq 'Value'}).Value $h.$Key = $value $ArrHash += $h } $Hash.TestCases = $ArrHash } #$Hash.TestCases = $d.Parent.Parent.PipelineElements.CommandElements[-1].Elements $Obj = [PesterItBlock]::New($Hash.Name,$Hash.Value,[PesterType]::It,$Hash.Content,$Hash.TestCases) $Obj.SetPending($Hash['IsPending']) $Obj.SetSkipped($Hash['IsSkipped']) $AllItBlocks += $Obj } return $AllItBlocks } function Get-PLPesterResultsDocument { <# .SYNOPSIS Returns a PesterResultsDocument object. .example Get-PLPesterResultsDocument -Path C:\Code\001.ps1 #> [CmdletBinding()] param ( [String]$Path ) begin { $Items = Get-ITem $Path $Documents = @() } process { Foreach($Item in $Items){ $Documents += [PesterResultsDocument]::New($Item) } } end { return $Documents } } Function Get-PLPesterScript { <# .SYNOPSIS Gets the contents of a pester script .DESCRIPTION Allows to returns the contents of a pester script. It gets the following elements of a pester script: InmoduleScope,Context,Describe and IT blocks. It will also return recursiveley get any child pester block that a parent pester block contains. .EXAMPLE path PesterBlocks ---- ------------ C:\Code\PSHTML\Tests\Address.Tests.ps1 {Testing PSHTML, Testing Address, It "Should contain opening and closing tags" {. .INPUTS System.IO.FileInfo .OUTPUTS PesterScript .NOTES Author: Stéphane van Gulick #> [CmdletBinding()] Param( [ValidateScript({ test-Path $_ })] $Path ) $e = [PesterScript]::New($Path) return $e } Function Write-PLReport { [CmdletBinding()] Param( $Path, [Parameter(Mandatory=$true,ParameterSetName='ExportToFile')] $ExportPath, [Parameter(ParameterSetName='ExportToHTML')] [Switch]$ReturnHTMLOnly, [Parameter(ParameterSetName='ExportToFile')] [Switch] $Show ) #Check PSHTML try{ Import-Module 'PSHTML' -Force }Catch{ Write-warning "PSHTML is a prerequisite for this cmdlet to work. It was not found on this system and therefor cannot work. You can add it to the system using 'install-module PSHTML'" Exit 1 } #Grab NunitFiles $PesterDoc = Get-PLPesterResultsDocument -Path $Path #Support for other files types?? (JSON?) $Html = html -Attributes @{lang = "en" } -Content { head { meta -charset 'UTF-8' meta -name 'author' -content "Stephane van Gulick" Title -Content "Pester Report" Write-PSHTMLAsset -Name JQuery Write-PSHTMLAsset -Name Bootstrap Write-PSHTMLAsset -Name ChartJs Style { $chartColors = @{ red = 'rgb(255, 99, 132)' orange = 'rgb(255, 159, 64)' yellow = 'rgb(255, 205, 86)' green = 'rgb(75, 192, 192)' blue = 'rgb(54, 162, 235)' purple = 'rgb(153, 102, 255)' grey = 'rgb(231,233,237)' } @" .theadfailed { color: #401500; background-color: #FFDDCC; border-color: #792700; } td{ word-break: break-all; } "@ } } $TableClasses = "table table-bordered table-hover" $TableHeaders = "thead-dark" $CanvasFixturesId = 'Chart_FixtureID' Body { Div -Class 'container' { Div -Class 'Jumbotron' { H1 -Class "display-4" { "Pester Report Summary" } p -Class 'lead' { "Detail run statistics from last pester runs. " } } Div -Class 'Summary' { H1 { "Overview" } Table -Class $TableClasses -content { tr { td { 'Source File' } td { $($PEsterDoc.Path.FullName) } } } ConvertTo-PSHTMLTable -Object $PesterDoc.Data -Properties TotalCount, PassedCount, FailedCount, SkippedCount, PendingCount, InconclusiveCount -TableClass $TableClasses -TheadClass $TableHeaders Div -Class "Container" -Style "text-align:center" -Content { Div -Id 'ChartSum' -Style 'display:inline-block' -content { $CanvasId = "Chart_Summary" canvas -Id $CanvasId -Height 400px -Width 400px -Content { } } } } Div -id 'Time' { p { "Pester Run took {0} days {1} hours {2} minutes {3} seconds to execute" -f $PesterDoc.Data.Time.Days, $PesterDoc.Data.Time.hours, $PesterDoc.Data.Time.minutes, $PesterDoc.Data.time.Seconds } } -Class "alert alert-primary" $GroupedResults = $PesterDoc.Data.TestResult | Group-Object Result #$Failed = $GroupedResults | Where-Object { $_.Name -eq 'Failed' } #$Passed = $GroupedResults | Where-Object { $_.Name -eq 'Passed' } Div -Class 'FailedTests' -Id "TestDetails" -Content { h1 { "Test results: Details" } p { "Total failed tests: {0}" -f $Failed.Count } $TableTop = @('Name', 'Result', 'Describe', 'Parameters', 'FailureMessage', 'Time', 'StackTrace') Table -Class $TableClasses -Id 'table_failed_tests' { tr -Content { Foreach ($t in $TableTop) { th { $t } } } Foreach ($ftest in $PesterDoc.Data.TestResult) { #ConvertTo-PSHTMLTable -Object $ftest -Properties Name,Result,Describe,Parameters,FailureMessage,Time,StackTrace -TableClass $TableClasses -TheadClass $TableHeaders #$ft | gm -MemberType Noteproperty | select Name tr { foreach ($ft in $ftest) { foreach ($th in $TableTop) { if ($th -eq 'Result') { $ResultType = $null If ($ft.$th -eq 'Failed') { $ResultType = 'badge badge-danger' } else { $ResultType = 'badge badge-success' } td -Class $ResultType -Content { $ft.$th } } elseIf($th -eq 'Time') { td { [Math]::Round($ft.$Th.TotalSeconds,2) } }else { td { $ft.$th } } } } } } } } script -content { $PieData = @($PesterDoc.Data.PassedCount, $PesterDoc.Data.FailedCount, $PesterDoc.Data.SkippedCount, $PesterDoc.Data.InconclusiveCount) $Labels = @("Passed", "Failed", "Skipped", "Inconclusive") $colors = @("LightGreen", "Red", "Yellow", "Orange") $BarDataSet = New-PSHTMLChartPieDataSet -Data $PieData -label "Pester Data" -backgroundColor $Colors New-PSHTMLChart -Type Pie -DataSet $BarDataSet -Labels $Labels -CanvasID "Chart_Summary" -Title "Pester run summary" } } Footer { $PSHTMLlink = a {"PSHTML"} -href "https://github.com/Stephanevg/PSHTML" h6 "Generated with ❤ using $($PSHTMLlink)" -Class "text-center" } } } If($ReturnHTMLOnly){ Return $Html }Else{ out-File -InputObject $html -Encoding utf8 -FilePath $ExportPath -Force If($Show){ Invoke-Item $ExportPath } } } #$Path = '/Users/stephanevg/github/PesterLove/DevCode/pshtml.TestsResults.xml' $Path = "/Users/stephanevg/github/PesterLove/DevCode/psclassutils.TestsResults.xml" Write-PLReport -Path $Path -ExportPath ./plop.html -Show $d = Get-PLPesterResultsDocument -Path $Path $d.Data |