Selenium is sort of a pseudo-industry standard for UI browser testing. There are others tools available (like cypress.io), but Selenium is a really well known / popular. And that’s why it’s ported or made available into so many other languages. It’s made available in Powershell using the Selenium module by Adam Driscoll (he was the creator of the Powershell Tools for Visual Studio).
The documentation in the github readme.md is short, but its all you really need to get started. But, you quickly start to run into the same problems the rest of the Selenium community runs into (Selenium – How to wait until page is completely loaded [duplicate], Selenium wait for Ajax content to load – universal approach).
So, here’s a quick function to help wait for a particular element on a page to load.
<# | |
.SYNOPSIS | |
Wait until the browser loads the expected element was some Text data. | |
#> | |
function Wait-UntilElementLoaded { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true)] | |
[OpenQA.Selenium.IWebDriver] $Driver, | |
[Parameter(Mandatory = $true, ParameterSetName = "Id")] | |
[string] $Id, | |
[Parameter(Mandatory = $true, ParameterSetName = "ClassName")] | |
[string] $ClassName, | |
[Parameter(Mandatory = $true, ParameterSetName = "Name")] | |
[string] $Name, | |
[Parameter(Mandatory = $true, ParameterSetName = "TagName")] | |
[string] $TagName, | |
[Parameter(Mandatory = $true, ParameterSetName = "LinkText")] | |
[string] $LinkText, | |
[Parameter(Mandatory = $true, ParameterSetName = "XPath")] | |
[string] $XPath, | |
[int] $TimeOutSeconds = 10 | |
) | |
Write-Verbose ("Wait-UntilElementLoaded: Searching on {0} '{1}'" -f $PSCmdlet.ParameterSetName, (iex "`$$($PSCmdlet.ParameterSetName)")) | |
switch($PSCmdlet.ParameterSetName) { | |
"Id" { $element = Find-SeElement -Driver $Driver -Id $Id } | |
"ClassName" { $element = Find-SeElement -Driver $Driver -ClassName $ClassName } | |
"Name" { $element = Find-SeElement -Driver $Driver -Name $Name } | |
"TagName" { $element = Find-SeElement -Driver $Driver -TagName $TagName } | |
"LinkText" { $element = Find-SeElement -Driver $Driver -LinkText $LinkText } | |
"XPath" { $element = Find-SeElement -Driver $Driver -XPath $XPath } | |
} | |
$retry = 0 | |
Write-Verbose "Wait-UntilElementLoaded: $($retry): [string]::IsNullOrWhiteSpace(`$element.Text) = $([string]::IsNullOrWhiteSpace($element.Text))" | |
Write-Verbose "Wait-UntilElementLoaded: $($retry): `$element.Text = $($element.Text)" | |
while([string]::IsNullOrWhiteSpace($element.Text) -and $retry -lt $TimeOutSeconds) { | |
Start-Sleep 1 | |
switch($PSCmdlet.ParameterSetName) { | |
"Id" { $element = Find-SeElement -Driver $Driver -Id $Id } | |
"ClassName" { $element = Find-SeElement -Driver $Driver -ClassName $ClassName } | |
"Name" { $element = Find-SeElement -Driver $Driver -Name $Name } | |
"TagName" { $element = Find-SeElement -Driver $Driver -TagName $TagName } | |
"LinkText" { $element = Find-SeElement -Driver $Driver -LinkText $LinkText } | |
"XPath" { $element = Find-SeElement -Driver $Driver -XPath $XPath } | |
} | |
$retry++ | |
Write-Verbose "Wait-UntilElementLoaded: $($retry): [string]::IsNullOrWhiteSpace(`$element.Text) = $([string]::IsNullOrWhiteSpace($element.Text))" | |
Write-Verbose "Wait-UntilElementLoaded: $($retry): `$element.Text = $($element.Text)" | |
} | |
if([string]::IsNullOrWhiteSpace($element.Text) -and $retry -eq $TimeOutSeconds) { | |
$messageFormat = "Element {0} = '{1}' could not be loaded before the timeout ({2} seconds) ran out. Please ensure the element can load in time." | |
$message = $messageFormat -f $PSCmdlet.ParameterSetName, (iex "`$$($PSCmdlet.ParameterSetName)"), $TimeOutSeconds | |
throw $message | |
} | |
} | |
And, here’s a sample Pester test using the function.
# http://stackoverflow.com/questions/1183183/path-of-currently-executing-powershell-script | |
$root = Split-Path $MyInvocation.MyCommand.Path -Parent; | |
if(-not $global:RunningInvokePester) { | |
Write-Host "Reloading Modules" | |
$Environment = "dev" | |
Import-Module SeleniumExtensions | |
Import-Module Selenium | |
Import-Module Pester | |
} | |
Describe -Tag "UI","Public" -Name "Home" { | |
BeforeAll { | |
if($Environment -ne "prod") { | |
$script:url = "https://somesite.{env}.subgroup.domain.com" -f $Environment | |
} else { | |
$script:url = "https://somesite.subgroup.domain.com" | |
} | |
#$script:driver = Start-SeChrome | |
$script:driver = Start-SeChrome -Arguments "headless", "incognito" | |
} | |
It "Search - Returns Results" { | |
Enter-SeUrl -Driver $script:driver -Url $script:url | |
Wait-UntilElementLoaded -Driver $script:driver -ClassName "navbar-brand" | |
$searchBar = Find-SeElement -Driver $script:driver -Id "search-terms" | |
Send-SeKeys -Element $searchBar -Keys "lookups" | |
$searchBtn = Find-SeElement -Driver $script:driver -Id "search-button" | |
Invoke-SeClick -Element $searchBtn | |
Wait-UntilElementLoaded -Driver $script:driver -XPath "//div[@id='service-small-info-1']/div[@class='highlights']/ul" | |
$firstResult = Find-SeElement -Driver $script:driver -XPath "//div[@id='service-small-info-1']/div[@class='highlights']" | |
$firstResult.Text | Should Match "/classifications \(Get\)" | |
} | |
AfterAll { | |
Stop-SeDriver -Driver $script:driver | |
} | |
} |