Functions/DslKeywords/Step.ps1
<#
.SYNOPSIS A report portal step. .DESCRIPTION This DSL keyword will invoke the real test step, specified in the $Test script block. The test step supports the same parameter as a Pester It block, because internally we call the Pester It block to invoke the test. #> function Step { [CmdletBinding()] param ( # Name of the test step. [Parameter(Mandatory = $true, Position = 0)] [System.String] $Name, # Test definition. [Parameter(Mandatory = $false, Position = 1)] [ScriptBlock] $Test = {}, # Optionally use test cases. [Parameter(Mandatory = $false)] [System.Collections.IDictionary[]] $TestCases, # Tags used as attributes. [Parameter(Mandatory = $false)] [Alias('Tags')] [System.String[]] $Tag = @(), # Test is pending. [Parameter(Mandatory = $false)] [Switch] $Pending, # Test is skipped. [Parameter(Mandatory = $false)] [Alias('Ignore')] [Switch] $Skip, # Test is hidden and not executed at all. [Parameter(Mandatory = $false)] [Switch] $Hide ) $ErrorActionPreference = 'Stop' # If the test is hidden, just return and do nothing with this test. if ($Hide.IsPresent) { return } # Invoke the test without the report portal, only native Pester. if ($null -ne $Script:RPContext -and $Script:RPContext.Mode -eq 'None') { try { $Script:RPContext.PesterPath.Push($Name) $PSBoundParameters.Remove('Tag') | Out-Null $PSBoundParameters.Remove('Hide') | Out-Null if ($Pending.IsPresent -and $Skip.IsPresent) { $PSBoundParameters.Remove('Pending') | Out-Null } # Decide if the test step contains some test cases. if ($PSBoundParameters.ContainsKey('TestCases') -and $TestCases.Count -gt 0) { # Iterate through the test cases to ensure the test suppression # can be used with the test cases. The Skip parameter must be # stored in a dedicated variable, as we need to respect the # initial step decision of the test. $PSBoundParameters.Remove('TestCases') | Out-Null $PSBoundParameters.Remove('Skip') | Out-Null foreach ($testCase in $TestCases) { $testCaseSkip = $Skip.IsPresent if (Test-RPDslSuppression -Context $Script:RPContext -TestCase $testCase) { $testCaseSkip = $true } Pester\It @PSBoundParameters -Skip:$testCaseSkip -TestCases $testCase } } else { # No test cases are specified, just invoke it 1:1 after # checking for any suppression. if (Test-RPDslSuppression -Context $Script:RPContext) { $PSBoundParameters.Skip = $true } Pester\It @PSBoundParameters } } finally { $Script:RPContext.PesterPath.Pop() | Out-Null } return } # Quit the function if not part of a suite. if ($null -eq $Script:RPContext -or $null -eq $Script:RPContext.Launch -or $null -eq $Script:RPContext.Suite -or $null -eq $Script:RPContext.Tests -or $Script:RPContext.Tests.Count -eq 0) { throw 'Step block must be placed within a Test block!' } # Recursive call if we use test cases and have more than one case. If this # is true, we will remote the TestCases from the bound parameters and call # the Step function recursively, once for each test case. With this, we # simply have one test per Step to run and we can leverage the test suite # implementation of Pester. if ($PSBoundParameters.ContainsKey('TestCases') -and $TestCases.Count -gt 1) { $PSBoundParameters.Remove('TestCases') | Out-Null foreach ($testCase in $TestCases) { Step @PSBoundParameters -TestCases $testCase } return } try { # Sum up tags $Tag += $Script:RPContext.Suite.Attributes $Tag += $Script:RPContext.Tests.Attributes $Tag = $Tag | Select-Object -Unique # Now call the Pester It block, but without the tag parameter. This # block won't throw any exceptions, because they are handled inside the # Pester block. This is why we have to access the internal Pester # varialbe to get and log the exception to the report portal in the # finally block. $PSBoundParameters.Remove('Tag') | Out-Null $PSBoundParameters.Remove('Hide') | Out-Null if ($Pending.IsPresent -and $Skip.IsPresent) { $PSBoundParameters.Remove('Pending') | Out-Null } Pester\It @PSBoundParameters # After invoking the It block of Pester, get the result from the # internal state variable. $pesterTestResult = (& (Get-Module 'Pester') Get-Variable -Name 'Pester' -ValueOnly).CurrentTestGroup.Actions[-1] try { $step = Start-RPTestItem -Launch $Script:RPContext.Launch -Parent $Script:RPContext.Tests.Peek() -Type 'Step' -Name $pesterTestResult.Name -Attribute $Tag try { $Script:RPContext.Tests.Push($step) Write-RPDslVerbose -Context $Script:RPContext -Message 'Start' # For each test, we add the definition of the It block to the # report portal log, so that we easy can troubleshoot the # errors. The description as info and the code as debug. Add-RPLog -TestItem $step -Level 'Info' -Message (Format-RPDslItComment) -Encoding 'UTF8' Add-RPLog -TestItem $step -Level 'Debug' -Message (Format-RPDslItBlock -Name $pesterTestResult.Name -Test $Test) # If the test fails, add this error as log entry to the test step. if ($pesterTestResult.Result -eq 'Failed') { Add-RPLog -TestItem $step -Level 'Error' -Message ("{0}`n{1}" -f $pesterTestResult.FailureMessage, $pesterTestResult.StackTrace) } if (Test-RPDslSuppression -Context $Script:RPContext) { $status = 'Skipped' } else { # Set the status of the test. This is derived from the # Pester result status into a report portal result status. switch ($pesterTestResult.Result) { 'Passed' { $status = 'Passed' } 'Failed' { $status = 'Failed' } 'Skipped' { $status = 'Skipped' } 'Pending' { $status = 'Skipped' } default { $status = 'Failed' } } } } finally { Write-RPDslVerbose -Context $Script:RPContext -Message 'Stop' $Script:RPContext.Tests.Pop() | Out-Null } } finally { if ($null -ne $step) { # If status has not been set, fix it if ($null -eq $status) { $status = 'Failed' } Stop-RPTestItem -TestItem $step -Status $status } } } catch { Add-RPDslErrorStep -Context $Script:RPContext -ErrorRecord $_ } } |