Fix webdriver.chrome.driver system property Error

Background – webdriver.chrome.driver system property

I was following along Selenium’s quick-start guide but kept getting an error, “The path to the driver executable must be set by the webdriver.chrome.driver system property“. Most of the answers I found were for Java and simply set the value in code. I tried a similar fix in C# to no avail. Finally, I found a solution that works that sets the values as command-line parameters when I launch the Node.

Files

All files were placed in C:\Selenium. An entry was added to my Windows PATH variable by:

  1. Right-click “This PC” and select “Properties”
  2. In the left navigation menu, select “Advanced system settings”
  3. Select the “Advanced” tab
  4. Select “Environment Variables…”
  5. Under “System variables” select “Path” and then select “Edit…”
  6. Select “New”
  7. Enter C:\Selenium\
  8. Select “OK”

Start the Hub

The following script will start the Grid Hub (on the default port of 4444).

cd C:\Selenium
java -jar selenium-server-standalone-3.0.0.jar -role hub

Start the Node

The following script will start a Grid Node (on the default port of 5555). It will also register the locations of the Chrome and Firefox WebDrivers (respectively).

cd C:\Selenium
java -Dwebdriver.chrome.driver=C:\Selenium\chromedriver.exe -Dwebdriver.gecko.driver=C:\Selenium\geckodriver.exe -jar selenium-server-standalone-3.0.0.jar -role node -hub http://localhost:4444/grid/register

Automated API Testing Using Powershell

Background

About a year ago I was worked on a project where we had to write a RESTful API from the ground up in Node. After the first few controllers, manual testing become quite tedious and labor intensive. One of the biggest rules in SQA is “automate what you can”. So, once we had a few endpoints solidified to match the RESTful pattern, I got to work. It took about 8 hours to come up with this automated testing solution and it saved countless hours of manual QA and tickets bouncing back-and-forth between QA and DEV (because they didn’t have unit tests). I put the solution on a VM that anyone working on the project could access, modify, and run. Let’s get started with API Testing Using Powershell.

Why Powershell?

I wanted to use the HttpClient from .Net 4.5 that I used in a lot of previous projects. The devs I was working with were MEAN and LAMP stack proficient, so I had to be sure to “Keep It Stupid Simple” and be sure to comment.

The Setup

As previously mentioned, I use the HttpClient (System.Net.Http.dll). I also import the JSON.Net (Newtonsoft.Json.dll) library to parse the JSON response from the RESTful API. In the project you’ll also see a .CSV file (check it out on GitHub). The CSV file is what makes this a data-driven testing solution that is easily updated and read by both machine and man.

The Code

You can view/fork the code from my GitHub project. You’ll need Powershell to run it, but I highly recommend using Powershell ISE. I am not going to copy+paste the code like in my other posts but rather I will explain it at a high-level.

What’s Going On

  1. Get the Present Working Directory (pwd) and save it for the next few steps
  2. Import the .dll files (see “The Setup” above)
  3. Set the BaseUrl, so that only the endpoints need to be specified in the .CSV file
    • This allows you to easily switch environments
  4. Create a new instance of the HttpClient
    • This will hold your session, so that you can log-in with one test and then do some call that requires a valid session afterwards
  5. Import the .CSV file
  6. Parse the .CSV file using a foreach loop
    1. Create an error array
    2. Create a description of the current test (row of the .CSV)
    3. Build a request with the given data
      • If the Method is PUT or POST, then add the body content
    4. Send the request and save the response
    5. Save the response’s status code
    6. Save the response’s content
    7. If the expected status code does not match the response’s, then we add that error message to the error array
    8. If the error array has any length, then we print out an error (with red text-color)
      • Else, print out a success message (with green-text)
    9. If there is a comment in the .CSV file’s current row, then print that out (with gray-text)

API Testing Using Powershell in Action

API Testing Using Powershell Screenshot

Load Testing Using PowerShell

Background

I was tasked with doing a load test for a client. At the start of work on the ticket I found that Telerik Load Testing wasn’t finding my dynamic target. This is because it was looking at query-string parameters but I needed to target values sent via a POST with a multi-part form as its body content. So, I took some code I used for another project where I used the HttpClient from the .Net 4.5 library in PowerShell.

Getting the Required Assembly

The code we will cover uses a .Net 4.5 dynamic link library (.dll). You can get this by installing .Net 4.5 (or higher) SDK. I have also added them to the sample project at the end of the article. Ss a convenience, I like to copy the .dll file(s) I need to the same directory to the PowerShell script (.ps1 file).

Understanding the Basics

A virtual user will make a series of URL requests, often with wait times (called “Think Time”) peppered in to simulate an actual users interaction with the server). We will use the HttpClient for the virtual user’s session as it is thread safe. It needs to be thread safe because we will use PowerShell RunSpaces and Jobs to spin up concurrent users. The calculator for finding the number of concurrent users can be found at webperfomance.com. Note that the number of concurrent users you can actually use is limited by the hardware the script is executing on as we are spinning up multiple instances of PowerShell.

Multi-Threading  PowerShell

The code for multi-threading comes from TheSurleyAdmin.com. The code works like this: Each thread is started up and given the task within the $ScriptBlock. Once the jobs are all done, the results are spit out into a GridView.

# http://thesurlyadmin.com/2013/02/11/multithreading-powershell-scripts/
cls

$Throttle = 5
$ScriptBlock = {
    Param (
        [int]$RunNumber
    )
    $RanNumber = Get-Random -Minimum 1 -Maximum 10
    Start-Sleep -Seconds $RanNumber
    $RunResult = New-Object PSObject -Property @{
        RunNumber = $RunNumber
        Sleep = $RanNumber
    }
    Return $RunResult
}

$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $Throttle)
$RunspacePool.Open()
$Jobs = @()

1..20 | % {
    $Job = ::Create().AddScript($ScriptBlock).AddArgument($_)
    $Job.RunspacePool = $RunspacePool
    $Jobs += New-Object PSObject -Property @{
        RunNum = $_
        Pipe = $Job
        Result = $Job.BeginInvoke()
    }
}

Write-Host "Waiting.." -NoNewline
Do {
    Write-Host "." -NoNewline
    Start-Sleep -Seconds 1
} While ( $Jobs.Result.IsCompleted -contains $false)
Write-Host "All jobs completed!"

$Results = @()
ForEach ($Job in $Jobs)
{   
    $Results += $Job.Pipe.EndInvoke($Job.Result)
}

$Results | Out-GridView

Adding in the HttpClient

Now that we have our base, we’ll need to modify it to suit our needs. The first thing we’ll need to add is the HttpClient. Because we are firing up multiple instances of PowerShell, the import will need to happen in the $ScriptBlock.

$ScriptBlock = {
    ...
    # Get the current working directory
    $pwd = pwd
    # Import the .Net Http Dynamic Link Library
    Add-Type -Path "$pwd\System.Net.Http.dll"
    # Instantiate a new http client, using the imported library
    $client = New-Object -TypeName System.Net.Http.Httpclient
    ...
}

Driving the Test Using a Data-Bound CSV File

Instead of having a list of URLs in the script, I like to use a .csv file as the PowerShell cmdlet is amazingly simple to use. In this CSV I have just a few columns; ID, METHOD, and URL. Method was needed so that when a POST was encountered, I could create the body dynamically. More on that in a moment.

$ScriptBlock = {
    ...
    # Define the path to the CSV file that holds all the URLs to be tested
    $pathToCsv = "$pwd\URLs.csv"
    # Import the CSV (defined above)
    $csv = Import-csv -path $pathToCsv
    ...
}

Handling POSTs

For my load test, the POSTs needed to have unique email addresses. For this I use one of my favorite tricks: the Gmail “+” trick and UnixTimeStamp. With Gmail you can add + to make a unique email address that routes back to the original. As an example, “tim+1@gmail.com” is actually “tim@gmail.com”. So after the “+” I just throw in a UnixTimeStamp. I found that this wasn’t unique enough so I also added the thread number to the string. Also, notice the use of MultipartFormDataContent. That is the bit that creates the form to be used as the POST’s body content.

$ScriptBlock = {
    ...
    # For Loop to run through each row of data in the CSV
    foreach($line in $csv) {
        $startTime = Get-Date
        $email = ''
        $statusCode = ''
        # If the request is a POST, then we need to set a unique email address
        if ($line.Method -eq "POST")  {
            $mfdc = New-Object -TypeName System.Net.Http.MultipartFormDataContent
            $pathToFormDataCsv = "$pwd\Form-Data.csv"
            $formData = Import-csv -path $pathToFormDataCsv
            foreach ($row in $formData)
            {
                $key = $row.Key
                $value = New-Object -TypeName System.Net.Http.StringContent $row.Value
                if ($key -eq "email") {
                    $email = "tim+$RunNumber{0:G}@spiredigital.com" -f [int][double]::Parse((Get-Date -UFormat %s))
                    $value = New-Object -TypeName System.Net.Http.StringContent $email
                }
                $mfdc.Add($value, $key)
            }
            $response = $client.PostAsync($line.URL, $mfdc).Result
            # Get the status code
            $statusCode = $response.StatusCode
        }
        # Otherwise, just make the request
        else {
            $request = New-Object -TypeName System.Net.Http.HttpRequestMessage
            $request.RequestUri = $line.URL
            $request.Method = $line.Method
            $response = $client.SendAsync($request).Result
            # Get the status code
            $statusCode = $response.StatusCode
        }
        $endTime = Get-Date
        ...
    }
}

Handling Results

After each request, I log the time it took and add my test data to a PowerShell Object using Add-Member to add properties to the generic object. That object is returned as the result of the job. Then after all jobs are complete, the results are aggregated and spit out in a GridView (same as the original script from TheSurleyAdmin).

$ScriptBlock = {
    ...
    # For Loop to run through each row of data in the CSV
    foreach($line in $csv) {
        ...
        $runTime = New-TimeSpan -Start $startTime -End $endTime
        # Save the test data to a PowerShell object
        $iterationResult = New-Object PSObject
        $iterationResult | Add-Member -NotePropertyName "Thread" -NotePropertyValue $RunNumber
        $iterationResult | Add-Member -NotePropertyName "StatusCode" -NotePropertyValue $statusCode
        $iterationResult | Add-Member -NotePropertyName "URL" -NotePropertyValue $line.URL
        $iterationResult | Add-Member -NotePropertyName "Time" -NotePropertyValue $runTime
        # Add the new object to the test result
        $threadResults += $iterationResult
    }
    # Return the results array for this thread
    Return $threadResults
}

Putting It All Together

Here is the final script:

# Set the number of threads we want to run concurrently
$threads = 5
# The script block is the code to be executed by each thread. Threads do not share data so if a library needs to be imported, then each thread needs to do so.
$ScriptBlock = {
    # Parameter - https://technet.microsoft.com/en-us/magazine/jj554301.aspx
    Param ( [int]$RunNumber )
    # Get the current working directory
    $pwd = pwd
    # Import the .Net Http Dynamic Link Library
    Add-Type -Path "$pwd\System.Net.Http.dll"
    # Instantiate a new http client, using the imported library
    $client = New-Object -TypeName System.Net.Http.Httpclient
    # Define the path to the CSV file that holds all the URLs to be tested
    $pathToCsv = "$pwd\URLs.csv"
    # Import the CSV (defined above)
    $csv = Import-csv -path $pathToCsv
    # Create an array to hold iteration results
    $threadResults = @()
    # For Loop to run through each row of data in the CSV
    foreach($line in $csv) {
        $startTime = Get-Date
        $email = ''
        $statusCode = ''
        # If the request is a POST, then we need to set a unique email address
        if ($line.Method -eq "POST")  {
            $mfdc = New-Object -TypeName System.Net.Http.MultipartFormDataContent
            $pathToFormDataCsv = "$pwd\Form-Data.csv"
            $formData = Import-csv -path $pathToFormDataCsv
            foreach ($row in $formData)
            {
                $key = $row.Key
                $value = New-Object -TypeName System.Net.Http.StringContent $row.Value
                if ($key -eq "email") {
                    $email = "tim+$RunNumber{0:G}@spiredigital.com" -f [int][double]::Parse((Get-Date -UFormat %s))
                    $value = New-Object -TypeName System.Net.Http.StringContent $email
                }
                $mfdc.Add($value, $key)
            }
            $response = $client.PostAsync($line.URL, $mfdc).Result
            # Get the status code
            $statusCode = $response.StatusCode
        }
        # Otherwise, just make the request
        else {
            $request = New-Object -TypeName System.Net.Http.HttpRequestMessage
            $request.RequestUri = $line.URL
            $request.Method = $line.Method
            $response = $client.SendAsync($request).Result
            # Get the status code
            $statusCode = $response.StatusCode
        }
        $endTime = Get-Date
        $runTime = New-TimeSpan -Start $startTime -End $endTime
        # Save the test data to a PowerShell object
        $iterationResult = New-Object PSObject
        $iterationResult | Add-Member -NotePropertyName "Thread" -NotePropertyValue $RunNumber
        $iterationResult | Add-Member -NotePropertyName "StatusCode" -NotePropertyValue $statusCode
        $iterationResult | Add-Member -NotePropertyName "URL" -NotePropertyValue $line.URL
        $iterationResult | Add-Member -NotePropertyName "Time" -NotePropertyValue $runTime
        # Add the new object to the test result
        $threadResults += $iterationResult
    }
    # Return the results array for this thread
    Return $threadResults
}
# Create a pool of powershell instances
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $threads)
$RunspacePool.Open()
# Create an Jobs array and invoke each job as they are added to the array
$Jobs = @()
1..$threads | % {
    $Job = ::Create().AddScript($ScriptBlock).AddArgument($_)
    $Job.RunspacePool = $RunspacePool
    $Jobs += New-Object PSObject -Property @{
        RunNum = $_
        Pipe = $Job
        Result = $Job.BeginInvoke()
    }
}
# Clear screen
cls
# Write a status message to the console, until all jobs finish
Write-Host "Working.." -NoNewline
Do {
    Write-Host "." -NoNewline
    Start-Sleep -Seconds 1
} While ( $Jobs.Result.IsCompleted -contains $false)
Write-Host "All jobs completed!"
# Package all threads results into one object
$Results = @()
ForEach ($Job in $Jobs) {   
    $Results += $Job.Pipe.EndInvoke($Job.Result)
}
# Display the results in a table
$Results | Out-GridView

gridview-example

Download the Sample

Note that I have removed the URLs as they are subject to a confidentiality agreement with the client.

Load Test (compressed via .zip).

PowerShell Primer

About PowerShell

Windows PowerShell® is a task-based command-line shell and scripting language designed especially for system administration. Built on the .NET Framework, Windows PowerShell helps IT professionals and power users control and automate the administration of the Windows operating system and applications that run on Windows.
http://technet.microsoft.com/en-us/library/bb978526.aspx

Download PowerShell

Windows Management Framework 4.0 includes updates to Windows PowerShell, Windows PowerShell ISE, Windows PowerShell Web Services (Management OData IIS Extension), Windows Remote Management (WinRM), Windows Management Instrumentation (WMI), the Server Manager WMI provider, and a new feature for 4.0, Windows PowerShell Desired State Configuration (DSC).
http://www.microsoft.com/en-us/download/details.aspx?id=40855

Starting PowerShell

The Windows PowerShell Integrated Scripting Environment (ISE) is a host application for Windows PowerShell. In Windows PowerShell ISE, you can run commands and write, test, and debug scripts in a single Windows-based graphic user interface with multiline editing, tab completion, syntax coloring, selective execution, context-sensitive help, and support for right-to-left languages. You can use menu items and keyboard shortcuts to perform many of the same tasks that you would perform in the Windows PowerShell console. For example, when you debug a script in the Windows PowerShell ISE, to set a line breakpoint in a script, right-click the line of code, and then click Toggle Breakpoint.
http://technet.microsoft.com/en-us/library/dd315244.aspx

Do one of the following:

    • Click Start, point to All Programs, point to Windows PowerShell V2, and then click Windows PowerShell ISE.
    • In the Windows PowerShell console Cmd.exe, or in the Run box, type, powershell_ise.exe.

Hello World!

The Write-Host cmdlet enables you to write messages to the Windows PowerShell console.
http://technet.microsoft.com/en-us/library/ee177031.aspx

  1. Open PowerShell ISE
  2. Click File and then click New
  3. Type Write-Host "Hello World!"
  4. Execute the script by pressing [F5]
"Hello World!"

Variables

When you write a script, particularly a system administration script, you rarely get to hard-code in all your values ahead of time; instead, you typically need to retrieve information, store that information in a variable or two, and then display the values of those variable.
http://technet.microsoft.com/en-us/library/ee692790.aspx

PowerShell is influenced by Unix shells and Perl so variables are prefixed with a $sigil.

$message = 'Hello World!'
Write-Host $message
"Hello World!"

Functions

A function is a list of Windows PowerShell statements that has a name that you assign. When you run a function, you type the function name. The statements in the list run as if you had typed them at the command prompt.
http://technet.microsoft.com/en-us/library/hh847829.aspx

Windows PowerShell uses a verb-noun pair for the names of cmdlets and for their derived Microsoft .NET Framework classes. For example, the Get-Command cmdlet provided by Windows PowerShell is used to retrieve all the commands that are registered in Windows PowerShell. The verb part of the name identifies the action that the cmdlet performs. The noun part of the name identifies the entity on which the action is performed.
http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx

$message = "Hello World!"
function Write-Message()
{
    Write-Host $message
}
# Call the function
Write-Message
"Hello World!"

Passing Variables to Functions

Previously, $message was accessible because its scope was Global. You can pass variables and values to functions by appending those values to the function call. Also, your function must expect a value. In this example, we pass a string into the function that we declare as $message.

function Write-Message([string]$message)
{
    Write-Host $message
}
# Call the function
Write-Message "Hello World!"
"Hello World!"

Error Handling

Use Try, Catch, and Finally blocks to respond to or handle terminating errors in scripts. The Trap statement can also be used to handle terminating errors in scripts. For more information, see about_Trap.
http://technet.microsoft.com/en-us/library/hh847793.aspx

function Write-Message([string]$message)
{
    Write-Host $message
}

try
{
    # Call the function
    Write-Message "Hello World!"
}
catch [Exception]
{
    # Write exception to host
    Write-Host ("==> Caught Exception: {0}" -f $_.Exception.Message)
}
finally
{
    # Pause the script so that the result can be viewed before the console closes
    Pause
}
"Hello World!"

NOTE: The exception string is built using composite formatting:
{0} is the argument.
-f is the format operator.
$_ is this (keyword).

Further Reading

Scripting with Windows PowerShell

  • Microsoft TechNet’s complete guide to PowerShell.
  • Contains documentation, guides, and related links.

An A-Z Index of Windows PowerShell 2.0 commands

  • SS64 has compiled an alphabetical list of PS v2 commands.

Related Reading

How to Run PowerShell Commands on Remote Computers

  • Hot-To Geek’s guide to enable PowerShell Remoting

Calling PowerShell Function from Command Line

  • StackOverflow Q&A