Selenium.psm1

 $Script:SeKeys = [OpenQA.Selenium.Keys] | Get-Member -MemberType Property -Static  |
        Select-Object -Property Name, @{N = "ObjectString"; E = { "[OpenQA.Selenium.Keys]::$($_.Name)" } }

#region Set path to assemblies on Linux and MacOS and Grant Execution permissions on them
if($IsLinux){
    $AssembliesPath = "$PSScriptRoot/assemblies/linux"
}
elseif($IsMacOS){
    $AssembliesPath = "$PSScriptRoot/assemblies/macos"
}

# Grant Execution permission to assemblies on Linux and MacOS
if($AssembliesPath){
    # Check if powershell is NOT running as root
    Get-Item -Path "$AssembliesPath/chromedriver", "$AssembliesPath/geckodriver" | ForEach-Object {
        if($IsLinux)    {$FileMod          = stat -c "%a" $_.FullName}
        elseif($IsMacOS){$FileMod = /usr/bin/stat -f "%A" $_.FullName}
        Write-Verbose "$($_.FullName) $Filemod"
        if($FileMod[2] -ne '5' -and $FileMod[2] -ne '7' ){
            Write-Host "Granting $($AssemblieFile.fullname) Execution Permissions ..."
            chmod +x $_.fullname
        }
    }
}

#endregion
function ValidateURL {
    [Alias("Validate-Url")]
    param(
        [Parameter(Mandatory=$true)]$URL
    )
    $Out = $null
    [uri]::TryCreate($URL,[System.UriKind]::Absolute, [ref]$Out)
}

function Start-SeNewEdge {
    [cmdletbinding(DefaultParameterSetName='default')]
    [Alias('CrEdge','NewEdge')]
    param(
        [ValidateURIAttribute()]
        [Parameter(Position=0)]
        [string]$StartURL,
        [switch]$HideVersionHint,
        [parameter(ParameterSetName='min', Mandatory=$true)]
        [switch]$Minimized,
        [parameter(ParameterSetName='max', Mandatory=$true)]
        [switch]$Maximized,
        [parameter(ParameterSetName='ful', Mandatory=$true)]
        [switch]$FullScreen,
        [parameter(ParameterSetName='hl',  Mandatory=$true)]
        [switch]$Headless,
        $BinaryPath = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
        $ProfileDirectoryPath,
        $DefaultDownloadPath,
        [switch]$AsDefaultDriver,
        [switch]$Quiet,
        [Alias('Incognito')]
        [switch]$PrivateBrowsing,
        [int]$ImplicitWait = 10,
        $WebDriverDirectory = $env:EdgeWebDriver
    )
    $OptionSettings =  @{ browserName=''}
    #region check / set paths for browser and web driver and edge options
    if($PSBoundParameters['BinaryPath'] -and -not (Test-Path -Path $BinaryPath)) {
        throw "Could not find $BinaryPath"; return
    }

    #Were we given a driver location and is msedgedriver there ?
    #If were were given a location (which might be from an environment variable) is the driver THERE ?
    # if not, were we given a path for the browser executable, and is the driver THERE ?
    # and if not there either, is there one in the assemblies sub dir ? And if not bail
    if(     $WebDriverDirectory -and -not (Test-Path -Path (Join-Path -Path $WebDriverDirectory -ChildPath 'msedgedriver.exe' ))) {
        throw "Could not find msedgedriver.exe in $WebDriverDirectory"; return
    }
    elseif( $WebDriverDirectory                 -and (Test-Path (Join-Path -Path $WebDriverDirectory        -ChildPath 'msedge.exe' ))) {
            Write-Verbose -Message "Using browser from $WebDriverDirectory"
            $optionsettings['BinaryLocation']   =                Join-Path -Path $WebDriverDirectory        -ChildPath 'msedge.exe'
    }
    elseif( $BinaryPath) {
            $optionsettings['BinaryLocation'] = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BinaryPath)
            $binaryDir = Split-Path -Path $BinaryPath -Parent
            Write-Verbose -Message "Will request $($OptionSettings['BinaryLocation']) as the browser"
    }
    if(-not $WebDriverDirectory -and $binaryDir -and (Test-Path (Join-Path -Path $binaryDir                 -ChildPath 'msedgedriver.exe' ))) {
            $WebDriverDirectory =    $binaryDir
    }
    # No linux or mac driver to test for yet
    if(-not $WebDriverDirectory -and                 (Test-Path (Join-path -Path "$PSScriptRoot\Assemblies\" -ChildPath 'msedgedriver.exe' ))) {
            $WebDriverDirectory =   "$PSScriptRoot\Assemblies\"
            Write-Verbose -Message "Using Web driver from the default location"
    }
    if(-not $WebDriverDirectory)  {throw "Could not find msedgedriver.exe"; return}

    # The "credge" web driver will work with the edge selenium objects, but works better with the chrome ones.
    $service = [OpenQA.Selenium.Chrome.ChromeDriverService]::CreateDefaultService($WebDriverDirectory, 'msedgedriver.exe')
    $options = New-Object -TypeName OpenQA.Selenium.Chrome.ChromeOptions -Property $OptionSettings
    #The command line args may now be --inprivate --headless but msedge driver V81 does not pass them
    if($PrivateBrowsing)       {$options.AddArguments('InPrivate')}
    if($Headless)              {$options.AddArguments('headless')}
    if($Quiet)                 {$service.HideCommandPromptWindow = $true}
    if($ProfileDirectoryPath)  {
        $ProfileDirectoryPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ProfileDirectoryPath)
        Write-Verbose "Setting Profile directory: $ProfileDirectoryPath"
        $options.AddArgument("user-data-dir=$ProfileDirectoryPath")
    }
    if($DefaultDownloadPath)   {
        $DefaultDownloadPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DefaultDownloadPath)
        Write-Verbose "Setting Default Download directory: $DefaultDownloadPath"
        $Options.AddUserProfilePreference('download', @{'default_directory' = $DefaultDownloadPath; 'prompt_for_download' = $false; })
    }
    #endregion

    $Driver = New-Object -TypeName OpenQA.Selenium.Chrome.ChromeDriver  -ArgumentList $service, $options

    #region post driver checks and option checks If we have a version know to have problems with passing arguments, generate a warning if we tried to send any.
    if(-not $Driver) {
            Write-Warning "Web driver was not created"; return
    }
    else {
            $driverversion = $Driver.Capabilities.ToDictionary().msedge.msedgedriverVersion -replace '^([\d.]+).*$','$1'
            if (-not $driverversion) {$driverversion = $driver.Capabilities.ToDictionary().chrome.chromedriverVersion -replace '^([\d.]+).*$','$1'}
            Write-Verbose  "Web Driver version $driverversion"
            Write-Verbose ("Browser: {0,9} {1}" -f $Driver.Capabilities.ToDictionary().browserName,
                                                   $Driver.Capabilities.ToDictionary().browserVersion)
            if(!$HideVersionHint){
                Write-Verbose "You can download the right webdriver from 'https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/'"
            }
            $browserCmdline = (Get-CimInstance -Verbose:$false -Query (
                "Select * From win32_process " +
                "Where parentprocessid = $($service.ProcessId) "+
                "And name = 'msedge.exe'") ).commandline
            $options.arguments | Where-Object {$browserCmdline -notlike "*$_*"} | ForEach-Object {
                Write-Warning "Argument $_ was not passed to the Browser. This is a known issue with some web driver versions."
            }
    }

    $Driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($ImplicitWait)
    if($StartURL) {$Driver.Navigate().GoToUrl($StartURL)}

    if($Minimized){
        $Driver.Manage().Window.Minimize();
    }
    if($Maximized){
        $Driver.Manage().Window.Maximize()
    }
    if ($FullScreen) {
        $Driver.Manage().Window.FullScreen()
    }
    #endregion

    if($AsDefaultDriver) {
        if($Global:SeDriver) {$Global:SeDriver.Dispose()}
        $Global:SeDriver = $Driver
    }
    else {$Driver}
}

function Start-SeChrome {
    [cmdletbinding(DefaultParameterSetName='default')]
    [Alias('SeChrome')]
    param(
        [ValidateURIAttribute()]
        [Parameter(Position=0)]
        [string]$StartURL,
        [Parameter(Mandatory = $false)]
        [array]$Arguments,
        [switch]$HideVersionHint,
        [System.IO.FileInfo]$DefaultDownloadPath,
        [System.IO.FileInfo]$ProfileDirectoryPath,
        [Parameter(DontShow)]
        [bool]$DisableBuiltInPDFViewer=$true,
        [switch]$EnablePDFViewer,
        [Alias('PrivateBrowsing')]
        [switch]$Incognito,
        [parameter(ParameterSetName='hl',  Mandatory=$true)]
        [switch]$Headless,
        [parameter(ParameterSetName='Min',Mandatory=$true)]
        [switch]$Maximized,
        [parameter(ParameterSetName='Max',Mandatory=$true)]
        [switch]$Minimized,
        [parameter(ParameterSetName='Ful',Mandatory=$true)]
        [switch]$Fullscreen,
        [switch]$DisableAutomationExtension,
        [Alias('ChromeBinaryPath')]
        $BinaryPath,
        $WebDriverDirectory = $env:ChromeWebDriver,
        [switch]$Quiet,
        [switch]$AsDefaultDriver,
        [int]$ImplicitWait = 10
    )

    process {
        #region chrome set-up options
        $Chrome_Options = New-Object -TypeName "OpenQA.Selenium.Chrome.ChromeOptions"

        if($DefaultDownloadPath){
            Write-Verbose "Setting Default Download directory: $DefaultDownloadPath"
            $Chrome_Options.AddUserProfilePreference('download', @{'default_directory' = $($DefaultDownloadPath.FullName); 'prompt_for_download' = $false; })
        }

        if($ProfileDirectoryPath){
            Write-Verbose "Setting Profile directory: $ProfileDirectoryPath"
            $Chrome_Options.AddArgument("user-data-dir=$ProfileDirectoryPath")
        }

        if($BinaryPath){
            Write-Verbose "Setting Chrome Binary directory: $BinaryPath"
            $Chrome_Options.BinaryLocation ="$BinaryPath"
        }

        if($DisableBuiltInPDFViewer -and -not $EnablePDFViewer){
            $Chrome_Options.AddUserProfilePreference('plugins', @{'always_open_pdf_externally' =  $true;})
        }

        if($Headless){
            $Chrome_Options.AddArguments('headless')
        }

        if($Incognito){
            $Chrome_Options.AddArguments('Incognito')
        }

        if($Maximized){
            $Chrome_Options.AddArguments('start-maximized')
        }

        if($Fullscreen){
            $Chrome_Options.AddArguments('start-fullscreen')
        }
        
        if($DisableAutomationExtension){
            $Chrome_Options.AddAdditionalCapability('useAutomationExtension', $false)
            $Chrome_Options.AddExcludedArgument('enable-automation')
        }

        if($Arguments){
            foreach ($Argument in $Arguments){
                $Chrome_Options.AddArguments($Argument)
            }
        }

        if(!$HideVersionHint){
            Write-Verbose "Download the right chromedriver from 'http://chromedriver.chromium.org/downloads'"
        }

        if($WebDriverDirectory) {$service = [OpenQA.Selenium.Chrome.ChromeDriverService]::CreateDefaultService($WebDriverDirectory)}
        elseif($AssembliesPath) {$service = [OpenQA.Selenium.Chrome.ChromeDriverService]::CreateDefaultService($AssembliesPath)}
        else                    {$service = [OpenQA.Selenium.Chrome.ChromeDriverService]::CreateDefaultService()}
        if ($Quiet)             {$service.HideCommandPromptWindow = $true}
        #endregion

        $Driver = New-Object -TypeName "OpenQA.Selenium.Chrome.ChromeDriver" -ArgumentList $service,$Chrome_Options
        if(-not $Driver)        {Write-Warning "Web driver was not created"; return}

        #region post creation options
        $Driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($ImplicitWait)

        if($Minimized){
            $Driver.Manage().Window.Minimize();
        }

        if($Headless -and $DefaultDownloadPath){
            $HeadlessDownloadParams = New-Object 'system.collections.generic.dictionary[[System.String],[System.Object]]]'
            $HeadlessDownloadParams.Add('behavior', 'allow')
            $HeadlessDownloadParams.Add('downloadPath', $DefaultDownloadPath.FullName)
            $Driver.ExecuteChromeCommand('Page.setDownloadBehavior', $HeadlessDownloadParams)
        }

        if($StartURL) {$Driver.Navigate().GoToUrl($StartURL)}
        #endregion

        if($AsDefaultDriver) {
            if($Global:SeDriver) {$Global:SeDriver.Dispose()}
            $Global:SeDriver = $Driver
        }
        else {$Driver}
    }
}

function Start-SeInternetExplorer {
    [cmdletbinding(DefaultParameterSetName='Default')]
    [Alias('SeInternetExplorer','SeIE')]
    param(
        [ValidateURIAttribute()]
        [Parameter(Position=0)]
        [string]$StartURL,
        [switch]$Quiet,
        [switch]$AsDefaultDriver,
        [parameter(ParameterSetName='Max', Mandatory=$true)]
        [switch]$Maximized,
        [parameter(ParameterSetName='Min', Mandatory=$true)]
        [switch]$Minimized,
        [parameter(ParameterSetName='Min', Mandatory=$true)]
        [switch]$FullScreen,
        [Parameter(DontShow)]
        [parameter(ParameterSetName='HL',  Mandatory=$true)]
        [switch]$Headless,
        [Parameter(DontShow)]
        [Alias('Incognito')]
        [switch]$PrivateBrowsing,
        [int]$ImplicitWait = 10,
        $WebDriverDirectory = $env:IEWebDriver
    )
    #region IE set-up options
    if($Headless -or $PrivateBrowsing) {Write-Warning 'The Internet explorer driver does not support headless or Inprivate operation; these switches are ignored'}

    $InternetExplorer_Options = New-Object -TypeName "OpenQA.Selenium.IE.InternetExplorerOptions"
    $InternetExplorer_Options.IgnoreZoomLevel = $true

    if($StartURL)           {$InternetExplorer_Options.InitialBrowserUrl = $StartURL }
    if($WebDriverDirectory) {$Service = [OpenQA.Selenium.IE.InternetExplorerDriverService]::CreateDefaultService($WebDriverDirectory)}
    else                    {$Service = [OpenQA.Selenium.IE.InternetExplorerDriverService]::CreateDefaultService()}
    if($Quiet)              {$Service.HideCommandPromptWindow = $true}
    #endregion

    $Driver = New-Object -TypeName "OpenQA.Selenium.IE.InternetExplorerDriver" -ArgumentList $service, $InternetExplorer_Options
    if(-not $Driver) {Write-Warning "Web driver was not created"; return}

    #region post creation options
    $Driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($ImplicitWait)
    if($Minimized){
        $Driver.Manage().Window.Minimize();
    }
    if($Maximized){
        $Driver.Manage().Window.Maximize()
    }
    if ($FullScreen) {
        $Driver.Manage().Window.FullScreen()
    }
    #endregion

    if($AsDefaultDriver) {
        if($Global:SeDriver) {$Global:SeDriver.Dispose()}
        $Global:SeDriver = $Driver
    }
    else {$Driver}
}

function Start-SeEdge {
    [cmdletbinding(DefaultParameterSetName='default')]
    [Alias('MSEdge','LegacyEdge','Start-SeLegacyEdge')]
    param(
        [ValidateURIAttribute()]
        [Parameter(Position=0)]
        [string]$StartURL,
        [parameter(ParameterSetName='Min', Mandatory=$true)]
        [switch]$Maximized,
        [parameter(ParameterSetName='Max', Mandatory=$true)]
        [switch]$Minimized,
        [parameter(ParameterSetName='Full',Mandatory=$true)]
        [switch]$FullScreen,
        [Alias('Incognito')]
        [switch]$PrivateBrowsing,
        [switch]$Quiet,
        [switch]$AsDefaultDriver,
        [Parameter(DontShow)]
        [switch]$Headless,
        [int]$ImplicitWait = 10
    )
    #region Edge set-up options
    if($Headless) {Write-Warning 'Pre-Chromium Edge does not support headless operation; the Headless switch is ignored'}
    $service = [OpenQA.Selenium.Edge.EdgeDriverService]::CreateDefaultService()
    $options = New-Object -TypeName OpenQA.Selenium.Edge.EdgeOptions
    if($Quiet)           {$service.HideCommandPromptWindow = $true}
    if($PrivateBrowsing) {$options.UseInPrivateBrowsing    = $true}
    if($StartURL)        {$options.StartPage               = $StartURL}
    #endregion

    try {
        $Driver = New-Object -TypeName "OpenQA.Selenium.Edge.EdgeDriver" -ArgumentList $service ,$options
    }
    catch {
        $driverversion  = (Get-Item .\assemblies\MicrosoftWebDriver.exe ).VersionInfo.ProductVersion
        $WindowsVersion = [System.Environment]::OSVersion.Version.ToString()
        Write-Warning -Message "Edge driver is $driverversion. Windows is $WindowsVersion. If the driver is out-of-date, update it as a Windows feature,`r`nand then delete $PSScriptRoot\assemblies\MicrosoftWebDriver.exe"
        throw $_ ; return
    }
    if(-not $Driver) {Write-Warning "Web driver was not created"; return}

    #region post creation options
    $Driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($ImplicitWait)
    if($Minimized)  {$Driver.Manage().Window.Minimize()    }
    if($Maximized)  {$Driver.Manage().Window.Maximize()    }
    if($FullScreen) {$Driver.Manage().Window.FullScreen()}
    #endregion

    if($AsDefaultDriver) {
        if($Global:SeDriver) {$Global:SeDriver.Dispose()}
        $Global:SeDriver = $Driver
    }
    else {$Driver}
}

function Start-SeFirefox {
    [cmdletbinding(DefaultParameterSetName='default')]
    [Alias('SeFirefox')]
    param(
        [ValidateURIAttribute()]
        [Parameter(Position=0)]
        [string]$StartURL,
        [array]$Arguments,
        [System.IO.FileInfo]$DefaultDownloadPath,
        [alias('Incognito')]
        [switch]$PrivateBrowsing,
        [parameter(ParameterSetName='HL', Mandatory=$true)]
        [switch]$Headless,
        [parameter(ParameterSetName='Min',Mandatory=$true)]
        [switch]$Maximized,
        [parameter(ParameterSetName='Max',Mandatory=$true)]
        [switch]$Minimized,
        [parameter(ParameterSetName='Ful',Mandatory=$true)]
        [switch]$Fullscreen,
        [switch]$SuppressLogging,
        [switch]$Quiet,
        [switch]$AsDefaultDriver,
        [int]$ImplicitWait = 10,
        $WebDriverDirectory = $env:GeckoWebDriver
    )
    process {
        #region firefox set-up options
        $Firefox_Options = New-Object -TypeName "OpenQA.Selenium.Firefox.FirefoxOptions"

        if($Headless){
            $Firefox_Options.AddArguments('-headless')
        }

        if($DefaultDownloadPath){
            Write-Verbose "Setting Default Download directory: $DefaultDownloadPath"
            $Firefox_Options.setPreference("browser.download.folderList",2);
            $Firefox_Options.SetPreference("browser.download.dir", "$DefaultDownloadPath");
        }

        if($PrivateBrowsing){
            $Firefox_Options.SetPreference("browser.privatebrowsing.autostart", $true)
        }

        if($Arguments){
            foreach ($Argument in $Arguments){
                $Firefox_Options.AddArguments($Argument)
            }
        }

        if($SuppressLogging){
            # Sets GeckoDriver log level to Fatal.
            $Firefox_Options.LogLevel = 6
        }

        if($WebDriverDirectory) {$service = [OpenQA.Selenium.Firefox.FirefoxDriverService]::CreateDefaultService($WebDriverDirectory)}
        elseif($AssembliesPath) {$service = [OpenQA.Selenium.Firefox.FirefoxDriverService]::CreateDefaultService($AssembliesPath)}
        else                    {$service = [OpenQA.Selenium.Firefox.FirefoxDriverService]::CreateDefaultService()}
        if($Quiet)              {$service.HideCommandPromptWindow = $true}
        #endregion

        $Driver = New-Object -TypeName "OpenQA.Selenium.Firefox.FirefoxDriver" -ArgumentList $service, $Firefox_Options
        if(-not $Driver)         {Write-Warning "Web driver was not created"; return}

        #region post creation options
        $Driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($ImplicitWait)
        if($Minimized)          {$Driver.Manage().Window.Minimize()    }
        if($Maximized)          {$Driver.Manage().Window.Maximize()    }
        if($Fullscreen)         {$Driver.Manage().Window.FullScreen()  }
        if($StartURL)           {$Driver.Navigate().GoToUrl($StartURL) }
        #endregion

        if($AsDefaultDriver)   {
            if($Global:SeDriver) {$Global:SeDriver.Dispose()}
            $Global:SeDriver = $Driver
        }
        else {$Driver}
    }
}

function Start-SeRemote {
    <#
        .example
        #you can a remote testing account with testing bot at https://testingbot.com/users/sign_up
        #Set $key and $secret and then ...
        #see also https://crossbrowsertesting.com/freetrial / https://help.crossbrowsertesting.com/selenium-testing/getting-started/c-sharp/
        #and https://www.browserstack.com/automate/c-sharp
 
        $RemoteDriverURL = [uri]"http://$key`:$secret@hub.testingbot.com/wd/hub"
        #See https://testingbot.com/support/getting-started/csharp.html for values for different browsers/platforms
        $caps = @{
          platform = 'HIGH-SIERRA'
          version = '11'
          browserName = 'safari'
        }
        Start-SeRemote -RemoteAddress $remoteDriverUrl -DesiredCapabilties $caps
    #>

        [cmdletbinding(DefaultParameterSetName='default')]
        param(
            [string]$RemoteAddress,
            [hashtable]$DesiredCapabilities,
            [ValidateURIAttribute()]
            [Parameter(Position=0)]
            [string]$StartURL,
            [switch]$AsDefaultDriver,
            [int]$ImplicitWait = 10
        )

        $desired = New-Object -TypeName OpenQA.Selenium.Remote.DesiredCapabilities
        if (-not $DesiredCapabilities.Name) {
            $desired.SetCapability('name', [datetime]::now.tostring("yyyyMMdd-hhmmss"))
        }
        foreach ($k in $DesiredCapabilities.keys) {$desired.SetCapability($k,$DesiredCapabilities[$k])}
        $Driver = New-Object -TypeName OpenQA.Selenium.Remote.RemoteWebDriver -ArgumentList $RemoteAddress,$desired

        if(-not $Driver) {Write-Warning "Web driver was not created"; return}

        $Driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($ImplicitWait)
        if ($StartURL) {$Driver.Navigate().GotoUrl($StartURL)}

        if($AsDefaultDriver) {
            if($Global:SeDriver) {$Global:SeDriver.Dispose()}
            $Global:SeDriver = $Driver
        }
        else {$Driver}
    }

<# @jhoneill Shouldn't -default be assumed if not feed a webdriver? see alternate below
function Stop-SeDriver {
    [alias('SeClose')]
    param(
        [Parameter(ValueFromPipeline=$true, position=0,ParameterSetName='Driver')]
        [ValidateIsWebDriverAttribute()]
        $Driver,
        [Parameter(Mandatory=$true, ParameterSetName='Default')]
        [switch]$Default
    )
    if(-not $PSBoundParameters.ContainsKey('Driver') -and $Global:SeDriver -and ($Default -or $MyInvocation.InvocationName -eq 'SeClose')) {
        Write-Verbose -Message "Closing $($Global:SeDriver.Capabilities.browsername)..."
        $Global:SeDriver.Close()
        $Global:SeDriver.Dispose()
        Remove-Variable -Name SeDriver -Scope global
    }
    elseif($Driver) {
           $Driver.Close()
           $Driver.Dispose()
    }
    else {Write-Warning -Message 'No Driver Specified'}
}
#>

function Stop-SeDriver {    
    [alias('SeClose')]
    param(
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [Alias('Driver')]
        [ValidateNotNullOrEmpty()]
        [OpenQA.Selenium.IWebDriver]
        $Target = $Global:SeDriver
    )

    if (  ($null -ne $Target) -and ($Target -is [OpenQA.Selenium.IWebDriver])  )
    {
        Write-Verbose -Message "Closing $($Target.Capabilities.browsername)..."
        $Target.Close()
        $Target.Dispose()
        if ($Target -eq $Global:SeDriver) { Remove-Variable -Name SeDriver -Scope global }
    }
    else { throw "A valid <IWebDriver> Target must be provided." }
}

<#function Enter-SeUrl {
    param($Driver, $Url)
 
    $Driver.Navigate().GoToUrl($Url)
}
#>

function Open-SeUrl {
    [cmdletbinding(DefaultParameterSetName='default')]
    [Alias('SeNavigate',"Enter-SeUrl")]
    param(
        [Parameter(Mandatory=$true, position=0,ParameterSetName='url')]
        [ValidateURIAttribute()]
        [string]$Url,

        [Parameter(Mandatory=$true,ParameterSetName='back')]
        [switch]$Back,

        [Parameter(Mandatory=$true,ParameterSetName='forward')]
        [switch]$Forward,

        [Parameter(Mandatory=$true,ParameterSetName='refresh')]
        [switch]$Refresh,

        [Parameter(ValueFromPipeline=$true)]
        [Alias("Driver")]
        [ValidateIsWebDriverAttribute()]
        $Target = $Global:SeDriver
    )

    switch ($PSCmdlet.ParameterSetName)
    {
        'url'     { $Target.Navigate().GoToUrl($Url); break }
        'back'    { $Target.Navigate().Back();        break }
        'forward' { $Target.Navigate().Forward();     break }
        'refresh' { $Target.Navigate().Refresh();     break }

        default   { throw 'Unexpected ParameterSet' }
    }
}

<#function Find-SeElement {
    param(
        [Parameter()]
        $Driver,
        [Parameter()]
        $Element,
        [Parameter()][Switch]$Wait,
        [Parameter()]$Timeout = 30,
        [Parameter(ParameterSetName = "ByCss")]
        $Css,
        [Parameter(ParameterSetName = "ByName")]
        $Name,
        [Parameter(ParameterSetName = "ById")]
        $Id,
        [Parameter(ParameterSetName = "ByClassName")]
        $ClassName,
        [Parameter(ParameterSetName = "ByLinkText")]
        $LinkText,
        [Parameter(ParameterSetName = "ByPartialLinkText")]
        $PartialLinkText,
        [Parameter(ParameterSetName = "ByTagName")]
        $TagName,
        [Parameter(ParameterSetName = "ByXPath")]
        $XPath
        )
 
 
    process {
 
        if($null -ne $Driver -and $null -ne $Element) {
            throw "Driver and Element may not be specified together."
        }
        elseif($null -ne $Driver) {
            $Target = $Driver
        }
        elseif(-ne $Null $Element) {
            $Target = $Element
        }
        else {
            "Driver or element must be specified"
        }
 
        if($Wait){
            if($PSCmdlet.ParameterSetName -eq "ByName") {
                $TargetElement = [OpenQA.Selenium.By]::Name($Name)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ById") {
                $TargetElement = [OpenQA.Selenium.By]::Id($Id)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByLinkText") {
                $TargetElement = [OpenQA.Selenium.By]::LinkText($LinkText)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByPartialLinkText") {
                $TargetElement = [OpenQA.Selenium.By]::PartialLinkText($PartialLinkText)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByClassName") {
                $TargetElement = [OpenQA.Selenium.By]::ClassName($ClassName)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByTagName") {
                $TargetElement = [OpenQA.Selenium.By]::TagName($TagName)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByXPath") {
                $TargetElement = [OpenQA.Selenium.By]::XPath($XPath)
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByCss") {
                $TargetElement = [OpenQA.Selenium.By]::CssSelector($Css)
            }
 
            $WebDriverWait = New-Object -TypeName OpenQA.Selenium.Support.UI.WebDriverWait($Driver, (New-TimeSpan -Seconds $Timeout))
            $Condition = [OpenQA.Selenium.Support.UI.ExpectedConditions]::ElementExists($TargetElement)
            $WebDriverWait.Until($Condition)
        }
        else{
            if($PSCmdlet.ParameterSetName -eq "ByName") {
                $Target.FindElements([OpenQA.Selenium.By]::Name($Name))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ById") {
                $Target.FindElements([OpenQA.Selenium.By]::Id($Id))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByLinkText") {
                $Target.FindElements([OpenQA.Selenium.By]::LinkText($LinkText))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByPartialLinkText") {
                $Target.FindElements([OpenQA.Selenium.By]::PartialLinkText($PartialLinkText))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByClassName") {
                $Target.FindElements([OpenQA.Selenium.By]::ClassName($ClassName))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByTagName") {
                $Target.FindElements([OpenQA.Selenium.By]::TagName($TagName))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByXPath") {
                $Target.FindElements([OpenQA.Selenium.By]::XPath($XPath))
            }
 
            if($PSCmdlet.ParameterSetName -eq "ByCss") {
                $Target.FindElements([OpenQA.Selenium.By]::CssSelector($Css))
            }
        }
    }
}
#>

function Get-SeElement {
    [Alias('Find-SeElement','SeElement')]
    param(
        #Specifies whether the selction text is to select by name, ID, Xpath etc
        [ValidateSet("CssSelector", "Name", "Id", "ClassName", "LinkText", "PartialLinkText", "TagName", "XPath")]
        [ByTransformAttribute()]
        [string]$By = "XPath",
        #Text to select on
        [Alias("CssSelector","Name", "Id", "ClassName","LinkText", "PartialLinkText", "TagName","XPath")]
        [Parameter(Position=1,Mandatory=$true)]
        [string]$Selection,
        #Specifies a time out
        [Parameter(Position=2)]
        [Int]$Timeout = 0,
        #The driver or Element where the search should be performed.
        [Parameter(Position=3,ValueFromPipeline=$true)]
        [Alias('Element','Driver')]
        $Target = $Global:SeDriver,

        [parameter(DontShow)]
        [Switch]$Wait

    )
    process {
        #if one of the old parameter names was used and BY was NIT specified, look for
        # <cmd/alias name> [anything which doesn't mean end of command] -Param
        # capture Param and set it as the value for by
        $mi = $MyInvocation.InvocationName
        if(-not $PSBoundParameters.ContainsKey("By") -and
          ($MyInvocation.Line -match  "$mi[^>\|;]*-(CssSelector|Name|Id|ClassName|LinkText|PartialLinkText|TagName|XPath)")) {
                $By = $Matches[1]
        }
        if($wait -and $Timeout -eq 0) {$Timeout = 30 }

        if($TimeOut -and $Target -is [OpenQA.Selenium.Remote.RemoteWebDriver]) {
            $TargetElement = [OpenQA.Selenium.By]::$By($Selection)
            $WebDriverWait = New-Object -TypeName OpenQA.Selenium.Support.UI.WebDriverWait -ArgumentList $Target, (New-TimeSpan -Seconds $Timeout)
            $Condition     = [OpenQA.Selenium.Support.UI.ExpectedConditions]::ElementExists($TargetElement)
            $WebDriverWait.Until($Condition)
        }
        elseif($Target -is [OpenQA.Selenium.Remote.RemoteWebElement] -or
               $Target -is [OpenQA.Selenium.Remote.RemoteWebDriver]) {
            if($Timeout) {Write-Warning "Timeout does not apply when searching an Element"}
            $Target.FindElements([OpenQA.Selenium.By]::$By($Selection))
        }
        else {throw "No valid target was provided."}
    }
}

function Invoke-SeClick {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [OpenQA.Selenium.IWebElement]$Element,
        [Parameter()]
        [Switch]$JavaScriptClick,
        [Parameter()]
        $Driver
    )

    if($JavaScriptClick) {
        $Driver.ExecuteScript("arguments[0].click()", $Element)
    }
    else {
        $Element.Click()
    }

}

function Send-SeClick {
    [alias('SeClick')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true,Position=0)]
        [OpenQA.Selenium.IWebElement]$Element,
        [Alias('JS')]
        [Switch]$JavaScriptClick,
        $SleepSeconds = 0 ,
        [Parameter(DontShow)]
        $Driver,
        [Alias('PT')]
        [switch]$PassThru
    )
    Process {
        if($JavaScriptClick) { $Element.WrappedDriver.ExecuteScript("arguments[0].click()", $Element) }
        else                 { $Element.Click() }
        if($SleepSeconds)    { Start-Sleep -Seconds $SleepSeconds}
        if($PassThru)        { $Element}
    }
}

function Get-SeKeys {
    [OpenQA.Selenium.Keys] | Get-Member -MemberType Property -Static | Select-Object -Property Name, @{N = "ObjectString"; E = { "[OpenQA.Selenium.Keys]::$($_.Name)" } }
}

function Send-SeKeys {
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]$Element,
        [Parameter(Mandatory=$true,Position=1)]
        [string]$Keys,
        [Parameter()]
        [Alias('PT')]
        [switch]$PassThru
    )
    foreach ($Key in $Script:SeKeys.Name) {
        $Keys = $Keys -replace "{{$Key}}", [OpenQA.Selenium.Keys]::$Key
    }
    $Element.SendKeys($Keys)
    if($PassThru) { $Element }
}

function Get-SeCookie {
    param(
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [Alias("Driver")]
        [ValidateIsWebDriverAttribute()]
        $Target = $Global:SeDriver
    )
    $Target.Manage().Cookies.AllCookies.GetEnumerator()
}

function Remove-SeCookie {
    param(
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [Alias('Driver')]
        [OpenQA.Selenium.IWebDriver]
        $Target = $Global:SeDriver,
        
        [Parameter(Mandatory=$true, ParameterSetName='DeleteAllCookies')]
        [Alias('Purge')]
        [switch]$DeleteAllCookies,

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

    if($DeleteAllCookies){
        $Target.Manage().Cookies.DeleteAllCookies()
    }
    else{
        $Target.Manage().Cookies.DeleteCookieNamed($Name)
    }
}

function Set-SeCookie {
    [cmdletbinding()]
    param(
        [string]$Name,
        [string]$Value,
        [string]$Path,
        [string]$Domain,
        $ExpiryDate,

        [Parameter(ValueFromPipeline = $true)]
        [Alias("Driver")]
        [ValidateIsWebDriverAttribute()]
        $Target = $Global:SeDriver
    )

    <# Selenium Cookie Information
    Cookie(String, String)
    Initializes a new instance of the Cookie class with a specific name and value.
    Cookie(String, String, String)
    Initializes a new instance of the Cookie class with a specific name, value, and path.
    Cookie(String, String, String, Nullable<DateTime>)
    Initializes a new instance of the Cookie class with a specific name, value, path and expiration date.
    Cookie(String, String, String, String, Nullable<DateTime>)
    Initializes a new instance of the Cookie class with a specific name, value, domain, path and expiration date.
    #>

    begin {
        if($null -ne $ExpiryDate -and $ExpiryDate.GetType().Name -ne 'DateTime'){
            throw '$ExpiryDate can only be $null or TypeName: System.DateTime'
        }
    }

    process {
        if($Name -and $Value -and (!$Path -and !$Domain -and !$ExpiryDate)){
            $cookie = New-Object -TypeName OpenQA.Selenium.Cookie -ArgumentList $Name,$Value
        }
        Elseif($Name -and $Value -and $Path -and (!$Domain -and !$ExpiryDate)){
            $cookie = New-Object -TypeName OpenQA.Selenium.Cookie -ArgumentList $Name,$Value,$Path
        }
        Elseif($Name -and $Value -and $Path -and $ExpiryDate -and !$Domain){
            $cookie = New-Object -TypeName OpenQA.Selenium.Cookie -ArgumentList $Name,$Value,$Path,$ExpiryDate
        }
        Elseif($Name -and $Value -and $Path -and $Domain -and (!$ExpiryDate -or $ExpiryDate)){
            if($Target.Url -match $Domain){
                $cookie = New-Object -TypeName OpenQA.Selenium.Cookie -ArgumentList $Name,$Value,$Domain,$Path,$ExpiryDate
            }
            else{
                Throw 'In order to set the cookie the browser needs to be on the cookie domain URL'
            }
        }
        else{
            Throw "Incorrect Cookie Layout:
            Cookie(String, String)
            Initializes a new instance of the Cookie class with a specific name and value.
            Cookie(String, String, String)
            Initializes a new instance of the Cookie class with a specific name, value, and path.
            Cookie(String, String, String, Nullable<DateTime>)
            Initializes a new instance of the Cookie class with a specific name, value, path and expiration date.
            Cookie(String, String, String, String, Nullable<DateTime>)
            Initializes a new instance of the Cookie class with a specific name, value, domain, path and expiration date."

        }

        $Target.Manage().Cookies.AddCookie($cookie)
    }
}


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

    Process {
        $Element.GetCssValue($Name)
    }
}

function Get-SeElementAttribute {
    param(
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [OpenQA.Selenium.IWebElement]$Element,
        [Parameter(Mandatory = $true)]
        [string]$Attribute
    )
    process {
        $Element.GetAttribute($Attribute)
    }
}

function Invoke-SeScreenshot {
    param(
        [Parameter(ValueFromPipeline=$true)]
        [Alias("Driver")]
        [ValidateIsWebDriverAttribute()]
        $Target = $Global:SeDriver,

        [Parameter(Mandatory = $false)]
        [Switch]$AsBase64EncodedString
    )
    $Screenshot = [OpenQA.Selenium.Support.Extensions.WebDriverExtensions]::TakeScreenshot($Target)
    if($AsBase64EncodedString) {
        $Screenshot.AsBase64EncodedString
    }
    else {
        $Screenshot
    }
}

function Save-SeScreenshot {
    param(
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [OpenQA.Selenium.Screenshot]$Screenshot,
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [Parameter()]
        [OpenQA.Selenium.ScreenshotImageFormat]$ImageFormat = [OpenQA.Selenium.ScreenshotImageFormat]::Png)

    process {
        $Screenshot.SaveAsFile($Path, $ImageFormat)
    }
}

function New-SeScreenshot {
    [Alias('SeScreenshot')]
    [cmdletbinding(DefaultParameterSetName='Path')]
    param(
        [Parameter(ParameterSetName='Path'    ,Position=0,Mandatory=$true)]
        [Parameter(ParameterSetName='PassThru',Position=0)]
        $Path,

        [Parameter(ParameterSetName='Path',    Position=1)]
        [Parameter(ParameterSetName='PassThru',Position=1)]
        [OpenQA.Selenium.ScreenshotImageFormat]$ImageFormat = [OpenQA.Selenium.ScreenshotImageFormat]::Png,

        [Parameter(ValueFromPipeline=$true)]
        [Alias("Driver")]
        [ValidateIsWebDriverAttribute()]
        $Target = $Global:SeDriver ,

        [Parameter(ParameterSetName='Base64',  Mandatory=$true)]
        [Switch]$AsBase64EncodedString,

        [Parameter(ParameterSetName='PassThru',Mandatory=$true)]
        [Alias('PT')]
        [Switch]$PassThru
    )
    $Screenshot = [OpenQA.Selenium.Support.Extensions.WebDriverExtensions]::TakeScreenshot($Target)
    if($AsBase64EncodedString) {$Screenshot.AsBase64EncodedString}
    elseif($Path)              {
        $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
        $Screenshot.SaveAsFile($Path, $ImageFormat) }
    if($Passthru)              {$Screenshot}
}

function Get-SeWindow {
    param(
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [Alias('Driver')]
        [OpenQA.Selenium.IWebDriver]
        $Target = $Global:SeDriver
    )

    process {
        $Target.WindowHandles
    }
}

function Switch-SeWindow {
    param(        
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [Alias('Driver')]
        [OpenQA.Selenium.IWebDriver]
        $Target = $Global:SeDriver,

        [Parameter(Mandatory = $true)]$Window
    )

    process {
        $Target.SwitchTo().Window($Window)|Out-Null
    }
}

function Switch-SeFrame {
    [Alias('SeFrame')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName='Frame', Position=0)]
        $Frame,

        [Parameter(Mandatory = $true, ParameterSetName='Parent')]
        [switch]$Parent,

        [Parameter(Mandatory = $true, ParameterSetName='Root')]
        [Alias('defaultContent')]
        [switch]$Root,

        [Parameter(ValueFromPipeline=$true)]
        [Alias("Driver")]
        [ValidateIsWebDriverAttribute()]
        $Target = $Global:SeDriver
    )
    
    if     ($frame)  {[void]$Target.SwitchTo().Frame($Frame) }
    elseif ($Parent) {[void]$Target.SwitchTo().ParentFrame()}
    elseif ($Root)   {[void]$Target.SwitchTo().defaultContent() }
}

function Clear-SeAlert {
    [Alias('SeAccept','SeDismiss')]
    param (
        [parameter(ParameterSetName='Alert', Position=0,ValueFromPipeline=$true)]
        $Alert,
        [parameter(ParameterSetName='Driver')]
        [ValidateIsWebDriverAttribute()]
        [Alias("Driver")]
        $Target = $Global:SeDriver,
        [ValidateSet('Accept','Dismiss')]
        $Action = 'Dismiss',
        [Alias('PT')]
        [switch]$PassThru
    )
    if ($Target) {
        try   {$Alert = $Target.SwitchTo().alert() }
        catch {Write-warning 'No alert was displayed'; return}
    }
    if (-not $PSBoundParameters.ContainsKey('Action') -and
        $MyInvocation.InvocationName -match 'Accept') {$Action = 'Accept'}
    if ($Alert) {$alert.$action() }
    if ($PassThru) {$Alert}
}

function SeOpen {
    [CmdletBinding()]
    Param(
        [ValidateSet('Chrome','CrEdge','FireFox','InternetExplorer','IE','MSEdge','NewEdge')]
        $In,
        [ValidateURIAttribute()]
        [Parameter(Mandatory=$False,Position=1)]
        $URL,
        [hashtable]$Options =@{'Quiet'=$true},
        [int]$SleepSeconds
    )
    #Allow the browser to specified in an Environment variable if not passed as a parameter
    if ($env:DefaultBrowser -and  -not $PSBoundParameters.ContainsKey('In')) {
        $In = $env:DefaultBrowser
    }
    #It may have been passed as a parameter, in an environment variable, or a parameter default, but if not, bail out
    if (-not $In) {throw 'No Browser was selected'}
    $StartParams  = @{}
    $StartParams += $Options
    $StartParams['AsDefaultDriver']     = $true
    $StartParams['Verbose']             = $false
    $StartParams['ErrorAction']         = 'Stop'
    $StartParams['Quiet']               = $true
    if ($url) {
         $StartParams['StartUrl']       = $url
    }

    switch -regex ($In) {
        'Chrome'   {Start-SeChrome           @StartParams; continue}
        'FireFox'  {Start-SeFirefox          @StartParams; continue}
        'MSEdge'   {Start-SeEdge             @StartParams; continue}
        'Edge$'    {Start-SeNewEdge          @StartParams; continue}
        '^I'       {Start-SeInternetExplorer @StartParams; continue}
    }
    Write-Verbose -Message "Opened $($Global:SeDriver.Capabilities.browsername) $($Global:SeDriver.Capabilities.ToDictionary().browserVersion)"
    if ($SleepSeconds) {Start-Sleep -Seconds $SleepSeconds}
}`

function SeType {
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$Keys,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [OpenQA.Selenium.IWebElement]$Element,
        [switch]$ClearFirst,
        $SleepSeconds = 0 ,
        [switch]$Submit,
        [Alias('PT')]
        [switch]$PassThru
    )
    begin{
        foreach ($Key in $Script:SeKeys.Name) {
            $Keys = $Keys -replace "{{$Key}}", [OpenQA.Selenium.Keys]::$Key
        }
    }
    process {
        if($ClearFirst)   {$Element.Clear()}

        $Element.SendKeys( $Keys)

        if($Submit)       {$Element.Submit()}
        if($SleepSeconds) {Start-Sleep -Seconds $SleepSeconds}
        if($PassThru)     {$Element}
    }
}

function Get-SeSelectionOption {
    [Alias('SeSelection')]
    [cmdletbinding(DefaultParameterSetName='default')]
    param (

        [Parameter(Mandatory=$true,  ParameterSetName='byValue', Position=0, ValueFromPipelineByPropertyName=$true)]
        [String]$ByValue,

        [Parameter(Mandatory=$true,  ValueFromPipeline=$true,    Position=1)]
        [OpenQA.Selenium.IWebElement]$Element,

        [Parameter(Mandatory=$true,  ParameterSetName='byText', ValueFromPipelineByPropertyName=$true)]
        [String]$ByFullText,

        [Parameter(Mandatory=$true,  ParameterSetName='bypart', ValueFromPipelineByPropertyName=$true)]
        [String]$ByPartialText,

        [Parameter(Mandatory=$true,  ParameterSetName='byIndex', ValueFromPipelineByPropertyName=$true)]
        [int]$ByIndex,

        [Parameter(Mandatory=$false, ParameterSetName='default')]
        [Parameter(Mandatory=$false, ParameterSetName='byValue')]
        [Parameter(Mandatory=$false, ParameterSetName='byText')]
        [Parameter(Mandatory=$false, ParameterSetName='byIndex')]
        [switch]$Clear,

        [Parameter(Mandatory=$false, ParameterSetName='default')]
        [switch]$ListOptionText,

        [Parameter(Mandatory=$true,  ParameterSetName='multi')]
        [switch]$IsMultiSelect,

        [Parameter(Mandatory=$true,  ParameterSetName='selected')]
        [Parameter(Mandatory=$false, ParameterSetName='byValue')]
        [Parameter(Mandatory=$false, ParameterSetName='byText')]
        [Parameter(Mandatory=$false, ParameterSetName='bypart')]
        [Parameter(Mandatory=$false, ParameterSetName='byIndex')]
        [switch]$GetSelected,

        [Parameter(Mandatory=$true,  ParameterSetName='allSelected')]
        [Parameter(Mandatory=$false, ParameterSetName='byValue')]
        [Parameter(Mandatory=$false, ParameterSetName='byText')]
        [Parameter(Mandatory=$false, ParameterSetName='bypart')]
        [Parameter(Mandatory=$false, ParameterSetName='byIndex')]
        [switch]$GetAllSelected,

        [Parameter(Mandatory=$false, ParameterSetName='byValue')]
        [Parameter(Mandatory=$false, ParameterSetName='byText')]
        [Parameter(Mandatory=$false, ParameterSetName='bypart')]
        [Parameter(Mandatory=$false, ParameterSetName='byIndex')]
        [Alias('PT')]
        [switch]$PassThru
    )
    try {
        #byindex can be 0, but ByText and ByValue can't be empty strings
        if ($ByFullText -or $ByPartialText -or $ByValue -or $PSBoundParameters.ContainsKey('ByIndex')) {
            if ($Clear) {
                if     ($ByText)        {[SeleniumSelection.Option]::DeselectByText( $Element,$ByText)}
                elseif ($ByValue)       {[SeleniumSelection.Option]::DeselectByValue($Element,$ByValue)}
                else                    {[SeleniumSelection.Option]::DeselectByIndex($Element,$ByIndex)}
            }
            else {
                if     ($ByText)        {[SeleniumSelection.Option]::SelectByText( $Element,$ByText,$false)}
                if     ($ByPartialText) {[SeleniumSelection.Option]::SelectByText( $Element,$ByPartialText,$true)}
                elseif ($ByValue)       {[SeleniumSelection.Option]::SelectByValue($Element,$ByValue)}
                else                    {[SeleniumSelection.Option]::SelectByIndex($Element,$ByIndex)}
            }
        }
        elseif ($Clear)                 {[SeleniumSelection.Option]::DeselectAll($Element) }
        if ($IsMultiSelect)      {return [SeleniumSelection.Option]::IsMultiSelect($Element)
        }
        if ($PassThru -and ($GetAllSelected -or $GetAllSelected)) {
            Write-Warning -Message "-Passthru option ignored because other values are returned"
        }
        if ($GetSelected)        {return [SeleniumSelection.Option]::GetSelectedOption($Element).text
        }
        if ($GetAllSelected)     {return [SeleniumSelection.Option]::GetAllSelectedOptions($Element).text
        }
        if ($PSCmdlet.ParameterSetName -eq 'default') {
            [SeleniumSelection.Option]::GetOptions($Element) | Select-Object -ExpandProperty Text
        }
        elseif ($PassThru) {$Element}
    }
    catch {
        throw "An error occured checking the selection box, the message was:`r`n $($_.exception.message)"
    }
}

function SeShouldHave {
    [cmdletbinding(DefaultParameterSetName='DefaultPS')]
    param(
        [Parameter(ParameterSetName='DefaultPS', Mandatory=$true , Position=0, ValueFromPipeline=$true)]
        [Parameter(ParameterSetName='Element'  , Mandatory=$true , Position=0, ValueFromPipeline=$true)]
        [string[]]$Selection,

        [Parameter(ParameterSetName='DefaultPS', Mandatory=$false)]
        [Parameter(ParameterSetName='Element'  , Mandatory=$false)]
        [ValidateSet('CssSelector', 'Name', 'Id', 'ClassName', 'LinkText', 'PartialLinkText', 'TagName', 'XPath')]
        [ByTransformAttribute()]
        [string]$By = 'XPath',

        [Parameter(ParameterSetName='Element'  , Mandatory=$true , Position=1)]
        [string]$With,

        [Parameter(ParameterSetName='Alert'    , Mandatory=$true )]
        [switch]$Alert,
        [Parameter(ParameterSetName='NoAlert'  , Mandatory=$true )]
        [switch]$NoAlert,
        [Parameter(ParameterSetName='Title'    , Mandatory=$true )]
        [switch]$Title,
        [Parameter(ParameterSetName='URL'      , Mandatory=$true )]
        [Alias('URI')]
        [switch]$URL,

        [Parameter(ParameterSetName='Element'  , Mandatory=$false, Position=3)]
        [Parameter(ParameterSetName='Alert'    , Mandatory=$false, Position=3)]
        [Parameter(ParameterSetName='Title'    , Mandatory=$false, Position=3)]
        [Parameter(ParameterSetName='URL'      , Mandatory=$false, Position=3)]
        [ValidateSet('like', 'notlike', 'match', 'notmatch', 'contains', 'eq', 'ne', 'gt', 'lt')]
        [OperatorTransformAttribute()]
        [String]$Operator = 'like',

        [Parameter(ParameterSetName='Element'  , Mandatory=$false, Position=4)]
        [Parameter(ParameterSetName='Alert'    , Mandatory=$false, Position=4)]
        [Parameter(ParameterSetName='Title'    , Mandatory=$true , Position=4)]
        [Parameter(ParameterSetName='URL'      , Mandatory=$true , Position=4)]
        [Alias('contains', 'like', 'notlike', 'match', 'notmatch', 'eq', 'ne', 'gt', 'lt')]
        [AllowEmptyString()]
        $Value,

        [Parameter(ParameterSetName='DefaultPS')]
        [Parameter(ParameterSetName='Element'  )]
        [Parameter(ParameterSetName='Alert'    )]
        [Alias('PT')]
        [switch]$PassThru,

        [Int]$Timeout = 0
    )
    begin {
        $endTime  = [datetime]::now.AddSeconds($Timeout)
        $lineText = $MyInvocation.Line.TrimEnd("$([System.Environment]::NewLine)")
        $lineNo   = $MyInvocation.ScriptLineNumber
        $file     = $MyInvocation.ScriptName
        Function expandErr {
            param ($message)
            $ex       = New-Object exception $message
            $id       = 'PesterAssertionFailed';
            $cat      = [Management.Automation.ErrorCategory]::InvalidResult ;
            New-Object Management.Automation.ErrorRecord $ex, $id, $cat,
                @{Message = $message; File = $file; Line=$lineNo; Linetext=$lineText}
        }
        function applyTest{
            param(
                $Testitems,
                $Operator,
                $Value
            )
            Switch ($Operator) {
                    'Contains' {return ($testitems -contains $Value)}
                    'eq'       {return ($TestItems -eq       $Value)}
                    'ne'       {return ($TestItems -ne       $Value)}
                    'like'     {return ($TestItems -like     $Value)}
                    'notlike'  {return ($TestItems -notlike  $Value)}
                    'match'    {return ($TestItems -match    $Value)}
                    'notmatch' {return ($TestItems -notmatch $Value)}
                    'gt'       {return ($TestItems -gt       $Value)}
                    'le'       {return ($TestItems -lt       $Value)}
            }
        }

        #if operator was not passed, allow it to be taken from an alias for the -value
        if (-not $PSBoundParameters.ContainsKey('operator') -and $lineText -match ' -(eq|ne|contains|match|notmatch|like|notlike|gt|lt) ') {
            $Operator = $matches[1]
        }
        $Success       = $false
        $foundElements = @()
    }
    process {
        #If we have been asked to check URL or title get them from the driver. Otherwise call Get-SEElement.
        if ($URL) {
            do {
                     $Success = applyTest -testitems $Global:SeDriver.Url   -operator $Operator -value $Value
                     Start-Sleep -Milliseconds 500
            }
            until (  $Success -or [datetime]::now -gt $endTime )
            if (-not $Success) {
                throw (expandErr  "PageURL was $($Global:SeDriver.Url). The comparison '-$operator $value' failed.")
            }
        }
        elseif ($Title) {
            do {
                     $Success = applyTest -testitems $Global:SeDriver.Title -operator $Operator -value $Value
                     Start-Sleep -Milliseconds 500
            }
            until (  $Success -or [datetime]::now -gt $endTime )
            if (-not $Success) {
                throw (expandErr  "Page title was $($Global:SeDriver.Title). The comparison '-$operator $value' failed.")
            }
        }
        elseif($Alert -or $NoAlert) {
            do {
                try  {
                    $a =$Global:SeDriver.SwitchTo().alert()
                    $Success = $true
                }
                catch {
                    Start-Sleep -Milliseconds 500
                }
                finally {
                    if($NoAlert -and $a) {throw (expandErr  "Expected no alert but an alert of '$($a.Text)' was displayed") }
                }
            }
            until ($Success -or [datetime]::now -gt $endTime )

            if($Alert -and -not $Success ) {
                throw (expandErr  "Expected an alert but but none was displayed")
            }
            elseif($value -and -not (applyTest -testitems $a.text -operator $Operator -value $value)) {
                throw (expandErr  "Alert text was $($a.text). The comparison '-$operator $value' failed.")
            }
            elseif($PassThru) {return $a}
        }
        else   {
            foreach ($s in $Selection) {
                $GSEParams =  @{By=$By; Selection=$s}
                if($Timeout) {$GSEParams['Timeout'] = $Timeout}
                try          {$e = Get-SeElement @GSEParams }
                catch        {throw (expandErr $_.Exception.Message)}

                #throw if we didn't get the element; if were only asked to check it was there, return gracefully
                if (-not $e) {throw (expandErr "Didn't find '$s' by $by")}
                else         {
                    Write-Verbose "Matched element(s) for $s"
                    $foundElements += $e
                }
            }
        }
    }
    end     {
        if    ($PSCmdlet.ParameterSetName -eq "DefaultPS" -and $PassThru) {return $e}
        elseif($PSCmdlet.ParameterSetName -eq "DefaultPS")                {return }
        else {
            foreach ($e in $foundElements) {
                switch ($with) {
                    'Text'      {$testItem = $e.Text}
                    'Displayed' {$testItem = $e.Displayed}
                    'Enabled'   {$testItem = $e.Enabled}
                    'TagName'   {$testItem = $e.TagName}
                    'X'         {$testItem = $e.Location.X}
                    'Y'         {$testItem = $e.Location.Y}
                    'Width'     {$testItem = $e.Size.Width}
                    'Height'    {$testItem = $e.Size.Height}
                    'Choice'    {$testItem = (Get-SeSelectionOption -Element $e -ListOptionText)}
                    default     {$testItem = $e.GetAttribute($with)}
                }
                if (-not $testItem -and ($Value -ne '' -and $foundElements.count -eq 1)) {
                    throw (expandErr "Didn't find '$with' on element")
                }
                if (applyTest -testitems $testItem -operator $Operator -value $Value) {
                    $Success = $true
                    if ($PassThru) {$e}
                }
            }
            if (-not $Success) {
                if ($foundElements.count -gt 1) {
                    throw (expandErr  "$Selection match $($foundElements.Count) elements, none has a value for $with which passed the comparison '-$operator $value'.")
                }
                else {
                    throw (expandErr  "$with had a value of $testitem which did not pass the the comparison '-$operator $value'.")
                }
            }
        }
    }
}