{{announcement.body}}
{{announcement.title}}

Developer Tips: C# Selenium with MSTest Basics

DZone 's Guide to

Developer Tips: C# Selenium with MSTest Basics

Get started with Selenium and MSTest.

· Web Dev Zone ·
Free Resource

Selenium is a great tool for testing your user interface (UI). There are plenty of great tutorials on the web that I encourage you to review. This article is going to cover some basic setup steps and a simple .NET Core 2.1 code sample.

Browser Settings

  1. Assumptions: Chrome/Firefox (64-bit)/IE11/Edge (Win10 or higher).
  2. Most of these settings have to be done for IE11 as the modern browsers do this by default or the alternative usually still works.
    • Always open pop-ups in a new tab.
    • Turn off pop-up blockers.
    • IE11: Enable Protected Mode for all security zones.
    • Disable save password prompts.
    • When prompted to AutoComplete, click "No".
    • Set zoom to 100%.
  3. Restart the browsers.

Windows Settings

  1. Disable the logon screen save while you're sitting back watching your automated tests run the screen saver does not kick on ruining your test.
  2. Restart the computer.

Set the WebDrivers

  1. IE 11.
  2. 64 bit should also work, but some consultants I worked with recommended the 32 bit over 64 bit as of 12/2018.
    • Extract "IEDriverServer.exe" from the zip to c:\Selenium.WebDrivers
  3. Microsoft Edge (EdgeHtml).
    • https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
    • Edge version 18 or great, then run the following in command prompt as an administrator.
      • DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0.
      • Edge version less than 18, then do the following.
    • Under "Downloads" > Microsoft Edge (EdgeHtml) > click the top Release.
      • Save "MicrosoftWebDriver.exe" to c:\Selenium.WebDrivers.
  4. Microsoft Edge (Chromium).
    1. Since this version is in Preview  I did not download and test but here are the steps.
    2. https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
    3. Under "Downloads" > Microsoft Edge (Chromium) > for the top Release click x64.
    4. Extract "msedgedriver.exe " from the zip to c:\Selenium.WebDrivers.
  5. Chrome.
    1. https://sites.google.com/a/chromium.org/chromedriver/
    2. Under "All versions available in Downloads" next to the "Latest stable release" click the ChromeDriver link > Click "chromedriver_win32.zip."
    3. Extract "chromedriver.exe" from the zip to c:\Selenium.WebDrivers.
  6. Firefox.
    1. https://github.com/mozilla/geckodriver/releases
    2. Under the latest release v#.##.# under "Assets" click the geckodriver-*-win64.zip.
    3. Extract "geckodriver.exe" from the zip to c:\Selenium.WebDrivers.

Create the Application

The full source code is in https://github.com/penblade/Tips/tree/master/Tips.Selenium.

  1. Create a new project > MSTest Test Project (.NET Core).
  2. Install the following NuGet packages.
    • WaitHelpers by SeleniumExtras.WaitHelpers (v3.11.0).
      • Used for StalenessOf checks.
    • Support by Selenium Committers (v3.141.0).
    • WebDriver by Selenium Committers (v3.141.0).

src/Test/Utilities/BrowserType.cs

namespace OpenQA.Selenium
{
    public enum BrowserType
    {
        NotSet,
        Chrome,
        Firefox,
        Edge,
        IE11
    }
}


WebDriverFactory

Create a factory to get the correct browser web driver.

using System;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
namespace OpenQA.Selenium
{
    internal static class WebDriverFactory
    {
        public static IWebDriver Create(BrowserType browserType, string seleniumWebDriversPath)
        {
            switch (browserType)
            {
                case BrowserType.Chrome:
                    return new ChromeDriver(seleniumWebDriversPath);
                case BrowserType.Firefox:
                    return new FirefoxDriver(seleniumWebDriversPath);
                case BrowserType.Edge:
                    // Edge 18 or greater is installed via command line.  See docs for more info.
                    return new EdgeDriver();
                case BrowserType.IE11:
                    return new InternetExplorerDriver(seleniumWebDriversPath);
                default:
                    throw new ArgumentOutOfRangeException(nameof(browserType), browserType, null);
            }
        }
    }
}


WebDriverExtensions

I toiled over deciding if I wanted to create class objects vs. extension methods of the IWebDriver.  After going back and forth multiple times, I settled on the extension methods. After some further research, I decided to follow the naming convention of not including the "I" in front of the extensions class.  I decided to keep the code in the OpenQA.Selenium namespace, except for the actual test class, so developers would not have to add another using path.

using System;
using System.Diagnostics;
using OpenQA.Selenium.Support.UI;
using ExpectedConditions = SeleniumExtras.WaitHelpers.ExpectedConditions;
namespace OpenQA.Selenium
{
    internal static class WebDriverExtensions
    {
        // Consider storing the DefaultWaitTime in the web.config.
        private const int DefaultWaitTime = 10;
        // Create a default wait time span so we can reuse the most common time span.
        private static readonly TimeSpan DefaultWaitTimeSpan = TimeSpan.FromSeconds(DefaultWaitTime);
        public static IWait<IWebDriver> Wait(this IWebDriver driver) => Wait(driver, DefaultWaitTimeSpan);
        public static IWait<IWebDriver> Wait(this IWebDriver driver, int waitTime) => Wait(driver, TimeSpan.FromSeconds(waitTime));
        public static IWait<IWebDriver> Wait(this IWebDriver driver, TimeSpan waitTimeSpan) => new WebDriverWait(driver, waitTimeSpan);
        public static IWebElement WaitUntilFindElement(this IWebDriver driver, By locator)
        {
            driver.Wait().Until(condition => ExpectedConditions.ElementIsVisible(locator));
            return driver.FindElement(locator);
        }
        public static IWebElement WaitUntilFindElement(this IWebDriver driver, By locator, Func<IWebDriver, IWebElement> condition)
        {
            driver.Wait().Until(condition);
            return driver.FindElement(locator);
        }
        public static IWebElement WaitUntilInitialPageLoad(this IWebDriver driver, string titleOnNewPage)
        {
            driver.Wait().Until(ExpectedConditions.TitleIs(titleOnNewPage));
            return driver.WaitUntilFindElementForPageLoadCheck();
        }
        public static IWebElement WaitUntilPageLoad(this IWebDriver driver, string titleOnNewPage, IWebElement elementOnOldPage)
        {
            // Inspiration:
            // http://www.obeythetestinggoat.com/how-to-get-selenium-to-wait-for-page-load-after-a-click.html
            // https://stackoverflow.com/questions/49866334/c-sharp-selenium-expectedconditions-is-obsolete
            driver.Wait().Until(ExpectedConditions.StalenessOf(elementOnOldPage));
            driver.Wait().Until(ExpectedConditions.TitleIs(titleOnNewPage));
            return driver.WaitUntilFindElementForPageLoadCheck();
        }
        private static IWebElement WaitUntilFindElementForPageLoadCheck(this IWebDriver driver) => driver.WaitUntilFindElement(By.XPath("html"));
        public static void ScrollIntoView(this IWebDriver driver, IWebElement element)
        {
            // Assumes IWebDriver can be cast as IJavaScriptExecuter.
            ScrollIntoView((IJavaScriptExecutor) driver, element);
        }
        private static void ScrollIntoView(IJavaScriptExecutor driver, IWebElement element)
        {
            // The MoveToElement does not scroll the element to the top of the page.
            //new Actions(driver).MoveToElement(session).Perform();
            driver.ExecuteScript("arguments[0].scrollIntoView(true);", element);
        }
        public static void Quit(this IWebDriver driver, BrowserType browserType)
        {
            driver.Quit();
            if (browserType != BrowserType.IE11) return;
            EndProcessTree("IEDriverServer.exe");
            EndProcessTree("iexplore.exe");
        }
        private static void EndProcessTree(string imageName)
        {
            // Inspiration
            // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill
            // https://stackoverflow.com/questions/5901679/kill-process-tree-programmatically-in-c-sharp
            // https://stackoverflow.com/questions/36729512/internet-explorer-11-does-not-close-after-selenium-test
            // /f - force process to terminate
            // /fi <Filter> - /fi \"pid gt 0 \" - select all processes
            // /im <ImageName> - select only processes with this image name
            Process.Start(new ProcessStartInfo
            {
                FileName = "taskkill",
                Arguments = $"/f /fi \"pid gt 0\" /im {imageName}",
                CreateNoWindow = true,
                UseShellExecute = false
            })?.WaitForExit();
        }
    }
}


WebDriverTest

Now that the extensions have been set up, let's add our test. We'll open a web page, click a link, wait until page load, and then scroll to an element.

For the test, I want to loop through the list of browsers as part of the same step to verify that I have set up the environment correctly.  Once you start looking into adding tests through the pipeline, you'll want to look into using a test runner and specify the browser to test at run time via a configuration file.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
namespace Tips.Selenium.Test
{
    [TestClass]
    public class WebDriverTest
    {
        private const string SeleniumWebDriversPath = @"C:\Selenium.WebDrivers";
        private const string TestUrl = @"https://dogfoodcon.com/";
        [TestMethod]
        [DataRow(BrowserType.Chrome, SeleniumWebDriversPath)]
        [DataRow(BrowserType.Firefox, SeleniumWebDriversPath)]
        [DataRow(BrowserType.Edge, SeleniumWebDriversPath)]
        [DataRow(BrowserType.IE11, SeleniumWebDriversPath)]
        public void VerifyRemoteWebDriversAreSetup(BrowserType browserType, string seleniumWebDriversPath)
        {
            using (var driver = WebDriverFactory.Create(browserType, seleniumWebDriversPath))
            {
                driver.Navigate().GoToUrl(TestUrl);
                // DogFoodCon
                var pageLoadCheck = driver.WaitUntilInitialPageLoad("DogFoodCon");
                var sessionsLink = driver.WaitUntilFindElement(By.XPath("//a[@title='Sessions']"));
                sessionsLink.Click();
                // Sessions - DogFoodCon
                pageLoadCheck = driver.WaitUntilPageLoad("Sessions – DogFoodCon", pageLoadCheck);
                var session = driver.WaitUntilFindElement(By.XPath("//a[text()='Jeff McKenzie']"));
                // Scroll to the element, but don't verify it is visible to the user.
                // I did this step just so you can see the session appear on the screen.
                driver.ScrollIntoView(session);
                driver.Quit(browserType);
            }
        }
    }
}


Resources

  1. The full source code is in https://github.com/penblade/Tips/tree/master/Tips.Selenium
  2. Introduction To Selenium Webdriver With C# In Visual Studio 2015
  3. Migrating A Selenium Project From .NET Framework To .NET Core
  4. Most Complete Selenium WebDriver C# Cheat Sheet
  5. Here are some additional Notes on a couple of issues I dealt with on setup with potential solutions.
    • My Win10 Chrome kept displaying a Windows Defender message.
    • You're supposed to add your Selenium driver path to the environment variable %PATH%, but I hit the max character limit.

Conclusion

I've provided the basics required to set up Selenium, including the environment for Win10 and browsers.  The sample code demonstrates how to implement common calls to handle waits, find elements, and wait until page load as extension methods off the IWebDriver to enhance and streamline the process. The next step would be to learn about the Page Object models. 

Topics:
web dev

Published at DZone with permission of Jonathan Danylko , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}