Public/Elements.ps1

function Set-MonocleElementValue
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Value,

        [switch]
        $Mask,

        [switch]
        $NoClear
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element

    if ($Mask) {
        Write-MonocleHost -Message "Setting $($id) element value to: ********"
    }
    else {
        Write-MonocleHost -Message "Setting $($id) element value to: $Value"
    }

    # set the value of the element
    if ($Element.TagName -ieq 'select') {
        $select = [OpenQA.Selenium.Support.UI.SelectElement]::new($Element)
        try {
            $select.SelectByText($Value)
        }
        catch {
            $select.SelectByValue($Value)
        }
    }
    else {
        if (!$NoClear) {
            $Element.Clear()
        }

        $Element.SendKeys($Value)
    }
}

function Clear-MonocleElementValue
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element
    Write-MonocleHost -Message "Clearing $($id) element value"

    # clear the value of the element
    $Element.Clear()
}

function Get-MonocleElementAttribute
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name
    )

    return $Element.GetAttribute($Name)
}

function Test-MonocleElementAttribute
{
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Value
    )

    try {
        return ((Get-MonocleElementAttribute -Element $Element -Name $Name) -ieq $Value)
    }
    catch {
        return $false
    }
}

function Set-MonocleElementAttribute
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter(Mandatory=$true)]
        [string]
        $Value
    )

    Invoke-MonocleJavaScript -Script 'arguments[0].setAttribute(arguments[1], arguments[2])' -Arguments $Element, $Name, $Value | Out-Null
}

function Add-MonocleElementClass
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name
    )

    Invoke-MonocleJavaScript -Script 'arguments[0].classList.add(arguments[1])' -Arguments $Element, $Name | Out-Null
}

function Remove-MonocleElementClass
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name
    )

    Invoke-MonocleJavaScript -Script 'arguments[0].classList.remove(arguments[1])' -Arguments $Element, $Name | Out-Null
}

function Test-MonocleElementClass
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name
    )

    return (Invoke-MonocleJavaScript -Script 'return arguments[0].classList.contains(arguments[1])' -Arguments $Element, $Name)
}

function Submit-MonocleForm
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [switch]
        $WaitUrl
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element
    Write-MonocleHost -Message "Submitting form block: $($id)"

    $url = Get-MonocleUrl
    $Element.Submit() | Out-Null
    Start-MonocleSleepWhileBusy

    # check if we should wait until the url is different
    if ($WaitUrl) {
        Wait-MonocleUrlDifferent -FromUrl $url
    }
}

function Get-MonocleElementValue
{
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [switch]
        $Mask
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element

    # get the value of the element
    $value = $Element.Text
    if ($Element.TagName -ieq 'select') {
        $value = [OpenQA.Selenium.Support.UI.SelectElement]::new($Element).SelectedOption.Text
    }

    if ($Mask) {
        Write-MonocleHost -Message "Value of $($id) element: ********"
    }
    else {
        Write-MonocleHost -Message "Value of $($id) element: $value"
    }

    return $value
}

function Test-MonocleElement
{
    [CmdletBinding(DefaultParameterSetName='Id')]
    [OutputType([bool])]
    param (
        [Parameter(Mandatory=$true, ParameterSetName='Id')]
        [string]
        $Id,

        [Parameter(Mandatory=$true, ParameterSetName='Tag')]
        [string]
        $TagName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeValue,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $ElementValue,

        [Parameter(ParameterSetName='XPath')]
        [string]
        $XPath,

        [Parameter(ParameterSetName='Selector')]
        [string]
        $Selector,

        [Parameter(ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Scope = $null
    )

    $result = $null

    try {
        $result = Get-MonocleElementInternal `
            -FilterType $PSCmdlet.ParameterSetName `
            -Id $Id `
            -TagName $TagName `
            -AttributeName $AttributeName `
            -AttributeValue $AttributeValue `
            -ElementValue $ElementValue `
            -XPath $XPath `
            -Selector $Selector `
            -Scope $Scope
    }
    catch { }

    return (($null -ne $result) -and ($null -ne $result.Element))
}

function Wait-MonocleElement
{
    [CmdletBinding(DefaultParameterSetName='Id')]
    param (
        [Parameter(Mandatory=$true, ParameterSetName='Id')]
        [string]
        $Id,

        [Parameter(Mandatory=$true, ParameterSetName='Tag')]
        [string]
        $TagName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeValue,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $ElementValue,

        [Parameter(ParameterSetName='XPath')]
        [string]
        $XPath,

        [Parameter(ParameterSetName='Selector')]
        [string]
        $Selector,

        [Parameter(ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Scope = $null,

        [Parameter()]
        [int]
        $Timeout = 600,

        [switch]
        $WaitVisible,

        [switch]
        $All
    )

    $result = Get-MonocleElementInternal `
        -FilterType $PSCmdlet.ParameterSetName `
        -Id $Id `
        -TagName $TagName `
        -AttributeName $AttributeName `
        -AttributeValue $AttributeValue `
        -ElementValue $ElementValue `
        -XPath $XPath `
        -Selector $Selector `
        -Scope $Scope `
        -Timeout $Timeout `
        -All:$All

    # set the meta id on the element
    @($result.Element) | ForEach-Object {
        Set-MonocleElementId -Element $_ -Id $result.Id
    }

    # wait for the elements to be visible
    if ($WaitVisible) {
        @($result.Element) | ForEach-Object {
            $_ | Wait-MonocleElementVisible | Out-Null
        }
    }

    # return the element
    return $result.Element
}

function Get-MonocleElement
{
    [CmdletBinding(DefaultParameterSetName='Id')]
    [OutputType([OpenQA.Selenium.IWebElement[]])]
    param (
        [Parameter(Mandatory=$true, ParameterSetName='Id')]
        [string]
        $Id,

        [Parameter(Mandatory=$true, ParameterSetName='Tag')]
        [string]
        $TagName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeValue,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $ElementValue,

        [Parameter(ParameterSetName='XPath')]
        [string]
        $XPath,

        [Parameter(ParameterSetName='Selector')]
        [string]
        $Selector,

        [Parameter(ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Scope = $null,

        [switch]
        $WaitVisible,

        [switch]
        $All
    )

    # attempt to get the monocle element
    $result = Get-MonocleElementInternal `
        -FilterType $PSCmdlet.ParameterSetName `
        -Id $Id `
        -TagName $TagName `
        -AttributeName $AttributeName `
        -AttributeValue $AttributeValue `
        -ElementValue $ElementValue `
        -XPath $XPath `
        -Selector $Selector `
        -Scope $Scope `
        -All:$All

    # set the meta id on the element
    @($result.Element) | ForEach-Object {
        Set-MonocleElementId -Element $_ -Id $result.Id
    }

    # wait for the elements to be visible
    if ($WaitVisible) {
        @($result.Element) | ForEach-Object {
            $_ | Wait-MonocleElementVisible | Out-Null
        }
    }

    # return the element
    return $result.Element
}

function Wait-MonocleElementVisible
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter()]
        [int]
        $Timeout = 30
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element
    Write-MonocleHost -Message "Waiting for element to be visible: $($id)"

    # wait for the element to be visible
    $seconds = 0

    while ($seconds -le $Timeout) {
        if (($Element | Test-MonocleElementVisible)) {
            return $Element
        }

        $seconds++
        Start-Sleep -Seconds 1
    }

    throw "Element '$($id)' was not visible after $($Timeout) seconds"
}

function Measure-MonocleElement
{
    [CmdletBinding(DefaultParameterSetName='Id')]
    [OutputType([int])]
    param (
        [Parameter(Mandatory=$true, ParameterSetName='Id')]
        [string]
        $Id,

        [Parameter(Mandatory=$true, ParameterSetName='Tag')]
        [string]
        $TagName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeName,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $AttributeValue,

        [Parameter(ParameterSetName='Tag')]
        [string]
        $ElementValue,

        [Parameter(ParameterSetName='XPath')]
        [string]
        $XPath,

        [Parameter(ParameterSetName='Selector')]
        [string]
        $Selector,

        [Parameter(ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Scope = $null
    )

    # attempt to get the monocle element
    $result = Get-MonocleElementInternal `
        -FilterType $PSCmdlet.ParameterSetName `
        -Id $Id `
        -TagName $TagName `
        -AttributeName $AttributeName `
        -AttributeValue $AttributeValue `
        -ElementValue $ElementValue `
        -XPath $XPath `
        -Selector $Selector `
        -Scope $Scope `
        -NoThrow `
        -All

    if ($null -eq $result.Element) {
        return 0
    }

    return @($result.Element).Length
}

function Wait-MonocleValue
{
    [CmdletBinding(DefaultParameterSetName='Value')]
    param (
        [Parameter(Mandatory=$true, ParameterSetName='Value')]
        [string]
        $Value,

        [Parameter(Mandatory=$true, ParameterSetName='Pattern')]
        [string]
        $Pattern
    )

    $count = 0
    $timeout = Get-MonocleTimeout

    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant())
    {
        'pattern' {
            Write-MonocleHost -Message "Waiting for value to match pattern: $Pattern"

            while ($Browser.PageSource -inotmatch $Pattern) {
                if ($count -ge $timeout) {
                    throw "Expected value to match pattern: $($Pattern)`nBut found nothing`nOn: $(Get-MonocleUrl)"
                }

                $count++
                Start-Sleep -Seconds 1
            }
        }

        'value' {
            Write-MonocleHost -Message "Waiting for value: $Value"

            while ($Browser.PageSource -ine $Value) {
                if ($count -ge $timeout) {
                    throw "Expected value: $($Value)`nBut found nothing`nOn: $(Get-MonocleUrl)"
                }

                $count++
                Start-Sleep -Seconds 1
            }
        }
    }

    Write-MonocleHost -Message "Expected value loaded after $count second(s)"
    Start-MonocleSleepWhileBusy
}

function Invoke-MonocleElementClick
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [switch]
        $WaitUrl
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element
    Write-MonocleHost -Message "Clicking element: $($id)"

    $url = Get-MonocleUrl

    # attempt to click the element, if it fails for "another element would receive" then try clikcing use javascript
    try {
        $Element.Click() | Out-Null
    }
    catch {
        if ($_.Exception.Message -ilike '*other element would receive the click*') {
            Invoke-MonocleJavaScript -Script 'arguments[0].click()' -Arguments $Element | Out-Null
        }
    }

    Start-MonocleSleepWhileBusy

    # check if we should wait until the url is different
    if ($WaitUrl) {
        Wait-MonocleUrlDifferent -FromUrl $url
    }
}

function Invoke-MonocleElementCheck
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [switch]
        $Uncheck
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element

    if ($Uncheck) {
        Write-MonocleHost -Message "Unchecking element: $($id)"
        if ($Element.Selected) {
            $Element.Click() | Out-Null
        }
    }
    else {
        Write-MonocleHost -Message "Checking element: $($id)"
        if (!$Element.Selected) {
            $Element.Click() | Out-Null
        }
    }

    Start-MonocleSleepWhileBusy
}

function Test-MonocleElementChecked
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element
    )

    return $Element.Selected
}

function Test-MonocleElementVisible
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element
    )

    # run js to check element visibility
    $js = @"
        var doc = document.documentElement
        var isInViewport = true
        var element = arguments[0]
 
        while (element.parentNode && element.parentNode.getBoundingClientRect) {
            var elemDimension = element.getBoundingClientRect()
            var elemComputedStyle = window.getComputedStyle(element)
            var viewportDimension = {
                width: doc.clientWidth,
                height: doc.clientHeight
            }
 
            isInViewport = isInViewport &&
                            (elemComputedStyle.display !== 'none' &&
                            elemComputedStyle.visibility === 'visible' &&
                            parseFloat(elemComputedStyle.opacity, 10) > 0 &&
                            elemDimension.bottom > 0 &&
                            elemDimension.right > 0 &&
                            elemDimension.top < viewportDimension.height &&
                            elemDimension.left < viewportDimension.width)
 
            element = element.parentNode
        }
 
        return isInViewport
"@


        return (Invoke-MonocleJavaScript -Script $js -Arguments $Element)
}

function Set-MonocleElementCSS
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Value
    )

    Invoke-MonocleJavaScript -Script 'arguments[0].style[arguments[1]] = arguments[2]' -Arguments $Element, $Name, $Value | Out-Null
}

function Remove-MonocleElementCSS
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name
    )

    Invoke-MonocleJavaScript -Script 'arguments[0].style[arguments[1]] = ""' -Arguments $Element, $Name | Out-Null
}

function Get-MonocleElementCSS
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name
    )

    return (Invoke-MonocleJavaScript -Script 'return arguments[0].style[arguments[1]]' -Arguments $Element, $Name)
}

function Test-MonocleElementCSS
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Value
    )

    return (Invoke-MonocleJavaScript -Script 'arguments[0].style[arguments[1]] == arguments[2]' -Arguments $Element, $Name, $Value)
}

function Enter-MonocleFrame
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [scriptblock]
        $ScriptBlock
    )

    # get the meta id of the element
    $id = Get-MonocleElementId -Element $Element

    try {
        # enter the iframe
        Write-MonocleHost -Message "Entering iFrame: $($id)"
        $Browser.SwitchTo().Frame($Element) | Out-Null

        # update the depth of output
        Add-MonocleOutputDepth

        # run the scriptblock
        . $ScriptBlock
    }
    finally {
        # reset the depth
        Remove-MonocleOutputDepth

        # exit the iframe back to the default frame
        Write-MonocleHost -Message "Exiting iFrame: $($id)"
        $Browser.SwitchTo().ParentFrame() | Out-Null
    }
}

function Get-MonocleElementSibling
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [ValidateSet('Previous', 'Next')]
        [string]
        $Type,

        [Parameter()]
        [int]
        $Depth = 1
    )

    if ($Depth -le 0) {
        throw "Sibling depth must be >=1"
    }

    ((1..$Depth) | ForEach-Object {
        $Element = (Invoke-MonocleJavaScript -Script "return arguments[0].$($Type.ToLowerInvariant())ElementSibling" -Arguments $Element)
    })

    return $Element
}

function Get-MonocleElementParent
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter()]
        [int]
        $Depth = 1
    )

    if ($Depth -le 0) {
        throw "Parent depth must be >=1"
    }

    ((1..$Depth) | ForEach-Object {
        $Element = (Invoke-MonocleJavaScript -Script "return arguments[0].parentElement" -Arguments $Element)
    })

    return $Element
}

function Test-MonocleElementChild
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element
    )

    return (Invoke-MonocleJavaScript -Script "return arguments[0].hasChildNodes()" -Arguments $Element)
}

function Measure-MonocleElementChild
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element
    )

    return (Invoke-MonocleJavaScript -Script "return arguments[0].childElementCount" -Arguments $Element)
}

function Get-MonocleElementChild
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]
        $Element,

        [Parameter(Mandatory=$true)]
        [ValidateSet('First', 'Last')]
        [string]
        $Type,

        [Parameter()]
        [int]
        $Depth = 1
    )

    if ($Depth -le 0) {
        throw "Child depth must be >=1"
    }

    ((1..$Depth) | ForEach-Object {
        $Element = (Invoke-MonocleJavaScript -Script "return arguments[0].$($Type.ToLowerInvariant())ElementChild" -Arguments $Element)
    })

    return $Element
}