I recently needed to set up a workflow for testing the front end of a client’s web app. We wanted to simulate various users and interact with the system as they would, so Selenium WebDriver was an obvious choice. I was amazed at how easily I could get up and running without ever having used it before, so I thought a quick intro post would be fun.
For this tutorial I’ll be using the ChromeDriver utility. You’ll need to ensure you have it to follow along. Additionally, Selenium will need to find the utility. It will default to using your PATH variable but in this case I will directly reference the path to the utility.
As a starting point, let’s load up Visual Studio and create a new Console Application. From there, load up Package Manager Console and enter:
| Install-Package Selenium.Support -Version 2.53.1 |
You can also use the GUI tool to search for Selenium.Support version 2.53.1 if you prefer. Either way, this installs Selenium.WebDriver for controlling ChromeDriver and Selenium.Support for a set of useful extensions.
Now to get to the meat. Rather than building yet another, “Hello World,” and using Selenium to verify its functionality, I thought would be fun to automate a common task. I’ve been dreaming of getting a Subaru WRX for a while now, and I thought I’d run through checking listings on one of the sites I check occasionally. Without further ado:
| open OpenQA.Selenium.Chrome | |
| open OpenQA.Selenium.Support.UI | |
| open System | |
| let pathToChromeDriverUtility = @"C:\WebDrivers\" | |
| let myLocalZip = "29063" | |
| let (|Int|_|) str = | |
| match Int32.TryParse(str) with | |
| | (true,int) -> Some(int) | |
| | _ -> None | |
| let isInt text = | |
| match text with | |
| | Int i -> true | |
| | _ -> false | |
| // If the path to the ChromeDriver executable is not | |
| // part of the path variable, it's easiest to | |
| // simply provide the constructor with the directory | |
| // path. | |
| let driver = new ChromeDriver(pathToChromeDriverUtility) | |
| [<EntryPoint>] | |
| let main argv = | |
| // Load up the cargurus home page. | |
| driver.Navigate().GoToUrl("https://www.cargurus.com") | |
| // Grab the select element for the make. | |
| let makeSelect = driver.FindElementById("carPickerUsed_makerSelect") | |
| |> SelectElement | |
| // Because I like them. | |
| makeSelect.SelectByText("Subaru") | |
| // Grab the select element for the model. | |
| let modelSelect = driver.FindElementById("carPickerUsed_modelSelect") | |
| |> SelectElement | |
| // Because VROOM VROOM. | |
| modelSelect.SelectByText("WRX") | |
| // Grab the input for the Zip Code where I'll search. | |
| let zipInput = driver.FindElementById("dealFinderZipUsedId") | |
| // Enter my local zip. | |
| zipInput.SendKeys(myLocalZip) | |
| // Grab the search button. | |
| let searchButton = driver.FindElementById("dealFinderForm_0") | |
| // Click it FTW! | |
| searchButton.Click() | |
| (* | |
| This is a quick one off, so we won't be parsing the entire result set. | |
| For now, let's just see what the closest available options are. | |
| We could easily extend this to be far more intelligent, but this | |
| shows the basics well enough. | |
| *) | |
| // Grab the select element for sorting. | |
| let sortBySelect = driver.FindElementById("dealFinder-sortHeader-select") | |
| |> SelectElement | |
| // Order the result set by proximity. | |
| sortBySelect.SelectByText("Closest first") | |
| // Get the distance spans for all listings. | |
| let allDistances = driver.FindElementsByCssSelector("div#listingsDiv span.cg-dealFinder-result-stats-milesAway, div#featuredResultsDiv span.cg-dealFinder-result-stats-milesAway") | |
| let distanceText = allDistances |> Seq.map (fun span -> span.Text.Replace("mi", "").Trim()) | |
| distanceText |> Seq.filter (isInt) | |
| |> Seq.map (Convert.ToInt32) | |
| |> Seq.sort | |
| |> Seq.iter (printfn "%i miles away") | |
| driver.Close() | |
| Console.ReadLine() |> ignore | |
| 0 // return an integer exit code |
If you prefer C#:
| using OpenQA.Selenium; | |
| using OpenQA.Selenium.Chrome; | |
| using OpenQA.Selenium.Support.UI; | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| namespace SeleniumCSharp | |
| { | |
| class Program | |
| { | |
| private static string pathToChromeDriverUtility = @"C:\WebDrivers\"; | |
| private static string myLocalZip = "29063"; | |
| private static bool IsInt(string s) | |
| { | |
| int ignore; | |
| return Int32.TryParse(s, out ignore); | |
| } | |
| static void Main(string[] args) | |
| { | |
| // If the path to the ChromeDriver executable is not | |
| // part of the path variable, it's easiest to | |
| // simply provide the constructor with the directory | |
| // path. | |
| var driver = new ChromeDriver(pathToChromeDriverUtility); | |
| // Load up the cargurus home page. | |
| driver.Navigate().GoToUrl("https://www.cargurus.com"); | |
| // Grab the select element for the make. | |
| var makeElement = driver.FindElementById("carPickerUsed_makerSelect"); | |
| var makeSelect = new SelectElement(makeElement); | |
| // Because I like them. | |
| makeSelect.SelectByText("Subaru"); | |
| // Grab the select element for the model. | |
| var modelElement = driver.FindElementById("carPickerUsed_modelSelect"); | |
| var modelSelect = new SelectElement(modelElement); | |
| // Because VROOM VROOM. | |
| modelSelect.SelectByText("WRX"); | |
| // Grab the input for the Zip Code where I'll search. | |
| var zipInput = driver.FindElementById("dealFinderZipUsedId"); | |
| // Enter my local zip. | |
| zipInput.SendKeys(myLocalZip); | |
| // Grab the search button. | |
| var searchButton = driver.FindElementById("dealFinderForm_0"); | |
| // Click it FTW! | |
| searchButton.Click(); | |
| /* | |
| This is a quick one off, so we won't be parsing the entire result set. | |
| For now, let's just see what the closest available options are. | |
| We could easily extend this to be far more intelligent, but this | |
| shows the basics well enough. | |
| */ | |
| // Grab the select element for sorting. | |
| var sortByElement = driver.FindElementById("dealFinder-sortHeader-select"); | |
| var sortBySelect = new SelectElement(sortByElement); | |
| // Order the result set by proximity. | |
| sortBySelect.SelectByText("Closest first"); | |
| // Get the distance spans for all listings. | |
| var allDistances = driver.FindElementsByCssSelector("div#listingsDiv span.cg-dealFinder-result-stats-milesAway, div#featuredResultsDiv span.cg-dealFinder-result-stats-milesAway"); | |
| var distanceText = allDistances.Select(span => span.Text.Replace("mi", "").Trim()); | |
| var orderedDistances = distanceText.Where(IsInt) | |
| .OrderBy(text => Convert.ToInt32(text)); | |
| foreach (var dist in orderedDistances) | |
| Console.WriteLine($"{dist} miles away"); | |
| driver.Close(); | |
| Console.ReadLine(); | |
| } | |
| } | |
| } |
The sky’s the limit from here. You can do much more complex and involved workflows and combining it with unit testing is a breeze. Please, let me know if you’d like more posts on this subject.
Lastly, a disclaimer. I’m not trying to advertise for cargurus, nor am I advocating scraping their or any site. This is just meant to be a fun display of the power of Selenium WebDriver. It can help you nail down your UI testing, or it could just start your favorite websites when you boot your PC. Just remember, don’t be evil.