Web UI Automation and Test using PowerShell

Lately, I had to do lots of repetitive tasks at work in order to fix defects in an application. As I grew tired of filling numerous forms with the mandatory data needed for them to validate, I thought that I could maybe use PowerShell to automate the form-filling process.

I very quickly stumbled upon an MSDN article titled Web UI Automation with Windows PowerShell. It was definitely the way to go, but I was not really satisfied with everything I found over there.

Getting a Brower object

This part is similar to the one used in the MSDN article.

$global:ie = New-Object -com "InternetExplorer.Application"
$global:ie.Navigate("about:blank")
$global:ie.visible = $true

Please note that I will use $global:ie and $global:doc all the time, as global variables. I could have used $script: scope variables, but global are better for this task. Here, we are building a bunch of reusable function; declaring these variables as $global: ensures that the variables will be available in scripts that include this utility script. See here for more information on PowerShell variable scopes.

So this, sets $global:ie to a COM Internet Explorer object, navigates to “about:blank” and makes the window visible. Now we need some helper functions to navigate, fill forms and click elements easily.

Helper Functions to Navigate, Click, etc…

First of all, we need a method that yields the script while the current page is loading. So, what it should do is simple: wait for the current page to be loaded then set the $global:doc variable to the loaded document.

Function WaitForPage([int] $delayTime = 100)
{
  $loaded = $false
 
  while ($loaded -eq $false) {
    [System.Threading.Thread]::Sleep($delayTime) 
 
    #If the browser is not busy, the page is loaded
    if (-not $global:ie.Busy)
    {
      $loaded = $true
    }
  }
 
  $global:doc = $global:ie.Document
}

I used an optional parameter here for the delay between checks of the browser’s status. It’s pretty straight forward, while the browser ($global:ie) is busy, wait. Once it’s not busy anymore, assign the document to the $global:doc variable. Let’s now define a function to navigate to a given url.

Function NavigateTo([string] $url, [int] $delayTime = 100)
{
  Write-Verbose "Navigating to $url";
 
  $global:ie.Navigate($url)
 
  WaitForPage $delayTime
}

Note that I used Write-Verbose commands around in functions to output some useful information in my script, making it easier to spot mistakes while running it.

Now, as this was made to fill web forms, let’s define a function that will fill an input text field with a given value.

Function SetElementValueByName($name, $value, [int] $position = 0) {
  if ($global:doc -eq $null) {
    Write-Error "Document is null";
    break
  }
  $elements = @($global:doc.getElementsByName($name))
  if ($elements.Count -ne 0) {
    $elements[$position].Value = $value
  }
  else {
    Write-Warning "Couldn't find any element with name ""$name""";
  }
}

This is heavily used in my scripts. An HTML form always has lots of input elements that have unique names that need to be filled. So, if you need to fill the username input text with Philippe value, just call this function:

SetElementValueByName “username” “Philippe”

Note that there is also a option parameter that is used as the element’s position in the array returned by $global:doc.getElementByName. By default, the used position is 0 because most of the forms will only have one element with a given name. However, it can be that in (badly designed?) forms, two elements have the same name. In this case, you can specify which one you want to fill.

Now, I won’t explain all the functions, but here are the ones I wrote:

Function ClickElementByTagName($tagName, [int] $position = 0)
{
  if ($global:doc -eq $null) {
    Write-Error "Document is null"
    break
  }
  $elements = @($global:doc.getElementsByTagName($tagName))
  if ($elements.Count -ne 0) {
    $elements[$position].Click()
    WaitForPage
  }
  else {
    Write-Error "Couldn't find element ""$tagName"" at position ""$position""";
    break
  }
}
 
Function ClickElementById($id)
{
  $element = $global:doc.getElementById($id)
  if ($element -ne $null) {
    $element.Click()
    WaitForPage
  }
  else {
    Write-Error "Couldn't find element with id ""$id"""
    break
  }
}
 
Function ClickElementByName($name, [int] $position = 0)
{
  if ($global:doc -eq $null) {
    Write-Error "Document is null"
    break
  }
  $elements = @($global:doc.getElementsByName($name))
  if ($elements.Count -ne 0) {
    $elements[$position].Click()
    WaitForPage
  }
  else {
    Write-Error "Couldn't find element with name ""$name"" at position ""$position"""
    break
  }
}

These are used to click on objects of the DOM in order to submit the form. These functions are not error proof, but as far as I used this stuff, I didn’t have issues.

A Little Example

Let’s write something very simple to test these functions. I will do a advanced search on google using these functions:

NavigateTo "http://www.google.com/advanced_search"
SetElementValueByName "as_oq" "Unisys Fenix PLDA"
SetElementValueByName "num" "30"
SetElementValueByName "lr" "lang_en"
ClickElementByName "btnG"

This gives you a little example of the kind of things you can do. It’s rather simple, but very powerful if you have repetitive tasks to on some web sites.

Download the full script here.

Comments

10 Responses to “Web UI Automation and Test using PowerShell”

  1. Jack Ukleja on July 15th, 2009 04:00

    Have you tried using Selenium RC? A very easy to use plug-in for Firefox that lets you record and playback and make assertions. “No code required”.

  2. Philippe on July 16th, 2009 23:32

    Nope, didn’t know about that tool. Looks very intresting. Thanks for pointing it out!

    However, what I wrote is more for a personnal use. The application I’m currently working on need a few forms of inputs before reaching the part that I’m working on, so this comes in handy.

  3. Carl on July 29th, 2009 12:59

    Great stuff, thanks very much for posting this..

  4. chan on January 21st, 2010 09:58

    hi do you have any idea, how to detect popup window using powershell?

  5. tom on March 2nd, 2010 22:19

    I also use that stuff for webtesting…
    As i didn’t find a way to automate a fileupload natively i did this with waitn in posh.(http://watin.sourceforge.net/)
    Pretty sure you ca n handle PopUps as well

  6. Davo on March 19th, 2010 06:18

    Philippe..you ROCK! This is an excellent post, I will find this very useful. Thanks very much.

  7. Kiima on November 2nd, 2011 16:41

    Great article. I added a simple link click function to mine. It took me a while to find/figure it out, so I thought I’d share it.

    Function ClickLinkByInnerText($innertext)
    {
    if ($script:doc -eq $null) {
    Write-Error “Document is null”
    break
    }
    $link = $script:doc.getElementsByTagName(‘A’) | where-object {$_.innerText -eq $innertext}
    if ($link -ne $null) {
    $link.Click()
    WaitForPage
    }
    else {
    Write-Error “Couldn’t find link with innertext “”$innertext”"”
    break
    }
    }

  8. Mark on November 11th, 2011 23:33

    By some reason NavigateTo function throws an error. Any ideas?

    Exception calling “Navigate” with “1″ argument(s): “The RPC server is unavailable. (Except
    ion from HRESULT: 0x800706BA)”
    At line:3 char:20
    + $global:ie.Navigate <<<< ("http://www.google.com/advanced_search&quot ;)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ComMethodCOMException

  9. Morgan Dell'Aquila on July 24th, 2012 17:00

    Hello – we are looking to hire a Web Automation Engineer. Do you know of candidates that are looking?

  10. Prasad on January 10th, 2014 06:45

    Hi Morgan, I know some Powershell engg expert in Web automation. How can i contact you