Functions/DslKeywords/Step.ps1

<#
    .SYNOPSIS
        A report portal test 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(DefaultParameterSetName = 'Normal')]
    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,

        # Attributes or tags.
        [Parameter(Mandatory = $false)]
        [Alias('Tags')]
        [System.String[]]
        $Tag = @(),

        # Test is pending.
        [Parameter(ParameterSetName = 'Pending')]
        [Switch]
        $Pending,

        # Test is skipped.
        [Parameter(ParameterSetName = 'Skip')]
        [Alias('Ignore')]
        [Switch]
        $Skip
    )

    # Quit the function if not part of a launch and suite or test.
    if ($null -eq $Script:RPLaunch -or $null -eq $Script:RPStack -or $Script:RPStack.Count -eq 0)
    {
        throw 'Step block must be placed inside a Launch and a Suite or 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 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
    {
        # We can't start the report portal step because we don't know the name
        # of the test step yet, if we use test cases. That's why we store the
        # time before we invoke the Pester block
        $startTime = Get-Date

        # Now call the Pester It block. 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.
        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]
        # $pesterTestResult | format-list * -Force | Out-String | Write-host

        # Start the step within the report portal
        $step = Start-RPTestItem -Launch $Script:RPLaunch -Parent $Script:RPStack.Peek() -Type 'Step' -StartTime $startTime -Name $pesterTestResult.Name -ErrorAction 'Stop'
        $Script:RPStack.Push($step)

        Write-RPDslInformation -Launch $Script:RPLaunch -Stack $Script:RPStack -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.
        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)
        }

        # Set the status of the test. This is derived from the Pester result
        # status into a report portal result status.
        $stepStatus = $pesterTestResult.Result
        if ($stepStatus -notin 'Passed', 'Failed', 'Skipped')
        {
            $stepStatus = 'Failed'
        }
    }
    catch
    {
        Write-RPDslInternalError -Launch $Script:RPLaunch -Parent $Script:RPStack.Peek() -Scope 'Step' -ErrorRecord $_
    }
    finally
    {
        Write-RPDslInformation -Launch $Script:RPLaunch -Stack $Script:RPStack -Message 'Stop'

        switch ($pesterTestResult.Result)
        {
            'Passed'  { $status = 'Passed' }
            'Failed'  { $status = 'Failed' }
            'Skipped' { $status = 'Skipped' }
            'Pending' { $status = 'Skipped' }
            default   { $status = 'Failed' }
        }

        # Try to stop the test, ignore any error
        Stop-RPTestItem -TestItem $step -Status $status -ErrorAction 'SilentlyContinue'

        # Remove the step from the stack
        if ($null -ne $step -and $Script:RPStack.Count -gt 0 -and $Script:RPStack.Peek().Guid -eq $step.Guid)
        {
            $Script:RPStack.Pop() | Out-Null
        }
    }
}