In any test automation project you're going to run into errors. They might be errors in your programming. They might also be errors raised as your scripts try to interact with the application you're testing. Either way when an error occurs during the execution of your automation code it's considered an 'exception'. That is, your code can't continue to be executed as you intended; *an exception to the rules you've defined in your code has to be handled.*
In this article we look at how C# deals with Exceptions and we look at the different types of exceptions you're likely to encounter with Selenium in C#.
Contents
C# Exception Handling
When C# runs into a problem it raises an exception object. This exception object contains all the data C# and it's CLR (Common Language Runtime) knows about the problem. If you don't deal with the exception that has been thrown then your automation run will halt. That's not good because clearly you might still have lots of tests to finish running. In general you have two options then..
1. Modify/fix your code so that you stop the error condition and exception from happening in the first place. For example if there's an object you can't always identify reliably then you might write code that checks to see if the object is there before you try to interact with it.
2. Use the exception handling capabilities of C# to catch the exception and deal with it. In this case you would let the 'error' happen, wait for C# to throw the 'exception' and then deal with this exception. This is known as exception handling.
In this article we'll look at the core C# exception concepts. Then we'll look into the main Selenium exception types and how to handle them.
The C# CLR Exception Object
If you've been coding in C# then you've probably seen numerous exceptions when running your code. Let's take a simple example so that we can understand how C# deals with exceptions.
Say we try to access a file on the file system that isn't there. Simple bit of code.
class MainProgram
{
static void Main(string[] args)
{
string fileContents;
string path = "C:\\test.txt"; // file that doesn't exist
fileContents = System.IO.File.ReadAllText(path);
Console.WriteLine(fileContents);
}
}
We run this code, specifying a file that doesn't exist. When we do that C# throws an exception. We can view the exception object that is created in the locals window or the Watch window. It's this exception object that contains all the details we need in order to get us out of this mess.
Calculator Demo Application
If you run the above code in Debug mode then you'll see Visual Studio raise the exception. If you view that exception in the Watch Window you'll be able to view the details contained within the Exception object. Details that you'll find useful include..
It's the detail from this exception object that will allow us to catch and handle the exception without it stopping the execution of our automation code. In C# we have a number of commands (try, catch and finally) that can help us rectify the problem at run time. Using these commands is known as “Exception Handling".
Exception Handling in C#
The fundamentals of 'Exception Handling' in C# revolve around the try, catch and finally keywords. The basic “try catch finally" block looks like this..
class MainProgram
{
static void Main(string[] args)
{
string fileContents;
string file = "C:\\test.txt"; // file that doesn't exist
try
{
fileContents = System.IO.File.ReadAllText(file);
Console.WriteLine(fileContents);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Finished file read operation");
}
}
}
If you run this code you'll notice that the execution doesn't grind to a halt because of the file read exception. The exception is caught as the commands in the Try block are processed. As we catch this exception the CLR creates the Exception object and we then assign this object to a variable. In this case we assign it to 'ex'. This 'ex' variable gives us access to all the useful properties of the 'Exception' object.
The thing to be aware of is that there are lots of different types of Exception. At the top level there is the base 'Exception' object. All objects then inherit from this base Exception object. More on this in a bit!
Throwing an Exception
If you want to throw your own Exception (e.g. force an exception in your code) then you can use the throw keyword. For example to manually throw your own exception you could use
class MainProgram
{
static void Main(string[] args)
{
string fileContents;
string path = "C:\\test.txt"; // file that doesn't exist
try
{
if (File.Exists(path))
{
fileContents = System.IO.File.ReadAllText(path);
Console.WriteLine(fileContents);
}
else
{
throw new Exception("My file was not found");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
In this example the throw new statement creates an Exception object from the base 'Exception' class. If we look at the definition for the 'Exception' class we can see that this class can accept a 'string' that is the message.
C# Selenium Exception
Hence when we 'throw new Exception' we can pass in our own message that provides a bit more detail to the user. So when we look at the exception in the Watch window we'll see
These are just a couple of the properties of what we call the “Base Exception".
The Base Exception
.NET can raise lots (and I mean lots) of different types of exceptions. All these different exception types have some similarities though. For this reason there's a base class called Exception that all other exceptions inherit from
Everything we've done so far is based around this 'base' Exception class. We could throw a different type of exception if we wanted by specifying a different exception type. Ultimately though it comes from this base Exception class.
class MainProgram
{
static void Main(string[] args)
{
string fileContents;
string path = "C:\\test.txt"; // file that doesn't exist
try
{
if (File.Exists(path)) {
fileContents = System.IO.File.ReadAllText(path);
Console.WriteLine(fileContents);
}
else
{
throw new FileNotFoundException("My file was not found", path);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
In this instance we're using another inbuilt exception type called the FileNotFoundException.
We're NOT leaving it to the CLR to raise this Exception though. We're writing our own check with the 'if file exists' statement and then throwing the exception, of type 'File Not Found', ourselves with the throw new statement. This is a much cleaner way to deal with things because we're in control of what's happing and the path that the code follows.
Catching the OpenQA Selenium No Such Element Exception
When we look at this exception in the watch window we see that the type for the 'ex' object is System.Exception, and more specifically System.IO.FileNotFoundException. The FileNotFoundException inherits from the main Exception object. We also see our message and the file name. “Message" is common to all Exception objects. “FileName" is specific to the File Not Found Exception.
And again if you peek at this exception class you'll see it can take a 'message' and a 'file name' as a parameters:
Catching the OpenQA Selenium No Such Element Exception
Not surprising seeing as this is an exception designed for use when you can't find a file. What you should be aware of though is that a lot of the common properties of this FileNotFoundException are inherited from the base 'Exception' class (like the 'message' property).
As you become more familiar with C# you'll become more familiar with the common exception classes (e.g. NullReferenceException, MemoryOverflowException, etc) and how to use them.
The point is this though; for each different exception class we'll want to handle things differently in our code. For that reason in the catch statement we can define, more precisely, the type of exception we want to handle.
Catching Different Types of Exceptions
If you're going to all the trouble of catching your exceptions you'll want to deal with them in the right way. If you write your catch block to deal with a 'File Not Found' situation you don't want that code running if you hit a 'Null Reference Exception'. You need to catch different types of exception and deal with them differently.
For this reason, once you've hit an exception the CLR checks to see if your 'type' of exception matches either:
So far we've just specified that any exception raised should be dealt with using our catch block. With the following code ANY type of exception (base Exception class) is caught.
try
{
fileContents = System.IO.File.ReadAllText(file);
Console.WriteLine(fileContents);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
What we want though is to be a bit more specific. We want to write our code to deal specifically with the 'File Not Found' exception. For this we can catch (FileNotFoundException ex).
try
{
fileContents = System.IO.File.ReadAllText(file);
Console.WriteLine(fileContents);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("My file note found: " + ex.Message);
}
In the example above we ONLY catch if it's a 'File Not Found Exception'. If you want to catch all exception types then you can specify the type Exception (the base class that all exception types inherit from). In this way we'll deal with our File Not Found but if it's any other type of exception then ANY exception type is caught with the generic catch (Exception ex).
try
{
fileContents = System.IO.File.ReadAllText(file);
Console.WriteLine(fileContents);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("My file note found: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
In the example above 'any' exception will be caught by this catch(Exception ex) block because the block specifies the Exception base class as the type of exception to match.
Note that the catch statement matches the Exception type and then assigns the exception object to the variable provided. In our case above we match on the type Exception and then assign the object to the variable ex. We can then use the exception object, ex, in the catch block code. If we want to write the exception message to the console we refer to the Message property of the ex object with ex.Message.
Be aware that the 'first' catch block that matches the exception type will be executed. Any other blocks are ignored. It's for this reason that the catch(Exception ex) block should always be last. catch(Exception ex) will always match everything.
Finally Blocks
You can add a finally block to your try/catch constructs too. Two things
to note about the finally block
1. the finally block is run if an exception occurs and if an exception does NOT occur
2. the finally block is run after the exception is handled. Or, if no exception occurs in the try block, the finally block will be run straight after the try block.
In the example below when an exception occurs in the try block, the catch code block is run first. Then the finally block is executed.
class MainProgram
{
static void Main(string[] args)
{
string file_path = "C:\\test.txt"; // file that doesn't exist
try
{
string fileContents = System.IO.File.ReadAllText(file_path);
Console.WriteLine(fileContents);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Attempt to read file completed");
}
}
}
What you end up with then is the following console message:
Which demonstrates that the catch code is run first and the finally code is run second. Typically, especially in test automation projects, we'd use this finally block to clean up and restore the test run to a known state.
Caught and Uncaught Exceptions
If you have exception handling in your call stack that catches an exception then an exception is said to be 'Caught'. If you have NO exception handling in your call stack then the exception is said to be 'Uncaught'. However, there's more to it than just that.
One of the core concepts to understand when dealing with exceptions is where they are caught. And, how they are passed up the call stack. As one piece of code calls another piece of code an exception raised in the lower levels will get passed back up the call stack to the higher levels until the first matching 'catch' statement is found.
So we see this sort of logic applied when it comes to trying to catch exceptions:
1. When we hit an exception we…check to see if the current statement is in a try block and if it is…
2. see if there is a matching catch block
3a. If there is a matching catch block … then run the code in that block
3b. if there is NOT a matching catch block check the calling method for a try statement
What this means is that if you have this bit of code where there's NO try/catch block in the lower level …
class MainProgram
{
static void Main(string[] args)
{
string path = "C:\\test.txt"; // file that doesn't exist
try
{
WriteMyFile(path);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
finally
{
System.Diagnostics.Debug.WriteLine("File read operation completed");
}
}
public static void WriteMyFile(string file_path)
{
string fileContents;
fileContents = System.IO.File.ReadAllText(file_path);
Console.WriteLine(fileContents);
}
}
The WriteMyFile() piece of code at the bottom of the call stack has no exception handling code (point 1 in the digaram below). However the code at the top of the call stack, where Main calls the WriteMyFile() method, does have exception handling code (point 2 in the digrame below). So when an exception is raised at the bottom it gets passed up to the higher level in the call stack.
Exception caught and passed up the call stack
Whilst the exception was directly wrapped in a try block, the call higher up was wrapped in the try block. The was, therefore, caught in the calling code.
Typically we'll structure things so that the types of Exception we catch are more specific the closer to the code where we're likely to hit the exception.
In this code below you'll notice that the WriteMyFile() method contains the Exception handling code that catches the FileNotFoundException. Yet the calling routine still contains the Exception handling code that catches all other types of Exceptions.
class MainProgram
{
static void Main(string[] args)
{
string path = "C:\\test.txt"; // file that doesn't exist
try
{
WriteMyFile(path);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
finally
{
System.Diagnostics.Debug.WriteLine("Higher level File read operation completed");
}
}
public static void WriteMyFile(string file_path)
{
string fileContents;
try
{
fileContents = System.IO.File.ReadAllText(file_path);
Console.WriteLine(fileContents);
}
catch (FileNotFoundException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
finally
{
System.Diagnostics.Debug.WriteLine("Lower level File read operation completed");
}
}
}
If a FileNotFoundException occurs we catch it and deal with exactly where it occured. If another type of Exception occurs, of a type that we weren't expecting, then it's passed back up to the calling code to deal with it.
Exception Caught at Bottom of Call Stack
As you can see in our contrived example the exception gets caught in the WriteMyFile() method. It doesn't get passed up to the calling method.
Note though that the finally blocks of code get executed for BOTH try/catch/finally blocks. That's because even though the exception was NOT caught in the top level method the finally block of code gets executed even when an Exception isn't caught.
Custom Exceptions
Sometimes the inbuilt exceptions just don't cut it. Perhaps you write some code and find yourself writing a routine to read “test data" that you need to for your test cases. When you read and use this data maybe there are specific error scenarios you keep hitting (I don't know… perhaps your test environment database keeps getting refreshed and your data data insertion fails). Maybe you want a custom exception type that is specific to your environment.
In this case we can go about creating our own Exception type. We also throw our own exception, which gerearates an instance of the Exception object. You can write your own Exception class with this format:
class MyTestDataFileNotFountException : Exception
{
public MyTestDataFileNotFountException(string message) : base(String.Format("Invalid Test Data File: {1}", message))
{
// Code to reset and clean up test data goes here
}
}
What we're doing is creating a new class, MyTestDataFileNotFountException, that
inherits from C#'s base class 'Exception'. We'll add our custom code that we want to run when this specific type of exception is raised.
All we need to do now is work out how to throw and catch this type of Exception. That's easy enough with the following
class MainProgram
{
static void Main(string[] args)
{
string file_path = "C:\\test.txt"; // file that doesn't exist
try
{
if (!File.Exists(file_path))
{
throw new MyTestDataFileNotFountException(file_path);
}
else
{
string fileContents = System.IO.File.ReadAllText(file_path);
}
}
catch (MyTestDataFileNotFountException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Higher level File read operation completed");
}
}
}
class MyTestDataFileNotFountException : Exception
{
public MyTestDataFileNotFountException(string message) : base(String.Format("Invalid Test Data File: {0}", message))
{
// Code to reset and clean up test data goes here
}
}
What you can see here is that if we don't find our 'test data' file, we….
throw new MyTestDataFileNotFountException(file_path);
This creates an instance of our new MyTestDataFileNotFountException class. Where this new class inherits from the base class 'Exception'.
Once we have that MyTestDataFileNotFountException object instantiated we catch it with this custom catch block
catch (MyTestDataFileNotFountException ex)
{
Console.WriteLine(ex.Message);
}
The point to note here is that we can throw this MyTestDataFileNotFountException in various places within our code. Whenever it's thrown the 'common' code we add to it's class is always run. So we don't need to duplicate code … which we might end up doing if we have lots of finally blocks littered throughout out code that try to manage this scenario.
The catch/finally blocks are used for specifics in that area of code. The new Exception class is used for common code related to that type of Exception when it's thrown.
It's this 'Custom Exception' approach that's used in the Selenium libraries. Custom exceptions handle specifics relating to our Selenium test automation code. Now you understand all the detail around C# Exceptions you'll understand the specific Selenium Exceptions that we're going to look at next.
Selenium Exception Fundamentals
Let's take a snippet of code from a C# SpecFlow project that implements the Page Object model. We'll work with a demo online site found here…
https://specflowoss.github.io/Calculator-Demo/Calculator.html
This gives us a few common text, button and form fields that we can work with.
Calculator Demo Application
With selenium we might work with something like this…
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.PageObjects;
using System.Diagnostics;
namespace SpecFlowWebCalc.PageObjects
{
internal class CalculatorPageObject
{
private IWebDriver _webDriver;
public const int DefaultWaitInSeconds = 5;
public CalculatorPageObject(IWebDriver webDriver)
{
_webDriver = webDriver;
_webDriver.Url = "https://specflowoss.github.io/Calculator-Demo/Calculator.html";
PageFactory.InitElements(_webDriver, this);
}
public IWebElement FirstNumberElement => _webDriver.FindElement(By.Id("first-number"));
private IWebElement SecondNumberElement => _webDriver.FindElement(By.Id("second-number"));
private IWebElement AddButtonElement => _webDriver.FindElement(By.Id("add-button"));
private IWebElement ResultElement => _webDriver.FindElement(By.Id("result"));
private IWebElement ResetButtonElement => _webDriver.FindElement(By.Id("reset-button"));
}
// code that enters data removed for clarity
}
In the example above we're using the FindElement method implemented by the web driver with CCS selectors. Nothing complicated there. We then use these element objects our automation code (in the example below we have step definitions from SpecFlow) like this..
namespace SpecFlowWebCalc.StepDefinitions
{
[Binding]
public sealed class CalculatorStepDefinitions
{
//Page Object for Calculator
private readonly CalculatorPageObject _calculatorPageObject;
private readonly SimpleBrowserDriver _browserDriver;
public CalculatorStepDefinitions(SimpleBrowserDriver browserDriver)
{
_browserDriver = browserDriver;
_calculatorPageObject = new CalculatorPageObject(_browserDriver.CurrentWebDriver);
}
[Given("the first number is (.*)")]
public void GivenTheFirstNumberIs(int number)
{
_calculatorPageObject.FirstNumberElement.Clear();
_calculatorPageObject.FirstNumberElement.SendKeys(number.ToString());
}
If you force an exception by putting in an invalid Id then C# will raise an exception and any further execution of the test or code grinds to a halt.
C# Selenium Exception
What we can see from this is that the exception “type" is: OpenQA.Selenium.NoSuchElementException
We can drill into the exception (click on 'View Details') and see more information like:
Now we have some detail to work with. Our job is to catch this exception and deal with it. In this scenario we'd want to:
If we don't catch the exception then the test will fail and the CLR will halt the execution of your test run.
At the most basic level then we can wrap the statements where we search for the elements in a Try/Catch catch block like this…
[Given("the first number is (.*)")]
public void GivenTheFirstNumberIs(int number)
{
try
{
_calculatorPageObject.FirstNumberElement.Clear();
_calculatorPageObject.FirstNumberElement.SendKeys(number.ToString());
}
catch (OpenQA.Selenium.NoSuchElementException ex)
{
Debug.WriteLine("Send to debug output.");
}
}
The important part to note here is that we're looking to catch an exception of Type OpenQA.Selenium.NoSuchElementException. If you place a break point on the 'Debug' line and run in Debug mode you'd expect this exception to be caught when the 'FirstNumberElement' isn't found. And, indeed, it is…
Catching the OpenQA Selenium No Such Element Exception
Now because in our example we're using SpecFlow an exception automatically quits the test and runs the After Test and, potentially After Scenario code. However, if you had clean up code that was specific to this GivenTheFirstNumberIs step then this catch block is the place to put it.
If you wanted to take this a step further and pass the exception message to the SpecFlow Scenario Context (so that it can be used in other classes – like the Hooks) then you could do something like this…
[Given("the first number is (.*)")]
public void GivenTheFirstNumberIs(int number)
{
try
{
_calculatorPageObject.FirstNumberElement.Clear();
_calculatorPageObject.FirstNumberElement.SendKeys(number.ToString());
}
catch (OpenQA.Selenium.NoSuchElementException ex)
{
ScenarioContext.Current[("Error")] = ex;
}
}
Then you can use `ScenarioContext.Current["Error"]`
in your other classes. This will give you access to the Exception object caught in your step definition. Anyway, this is supposed to be more about Selenium exceptions rather than specflow. So back to the topic in hand!
Let's correct our object identification error by updating the FindElement call in the Page Object
public IWebElement FirstNumberElement => _webDriver.FindElement(By.Id("first-number"));
Then we can look at forcing some other common types of Exceptions you'll come across in Selenium.
Common Types of Selenium C# Exceptions
For the rest of the examples we're going to use https://the-internet.herokuapp.com/ . We'll find examples of elements in this demo application that'll help use understand the types of exceptions and how to manage them.
https://the-internet.herokuapp.com/dynamic_loading/1
What we've done in our example code is condense everything into the step definition; the page navigation, element identification and element interaction. Clearly this isn't good architecturally but it makes it easier to understand the examples.
Feature: Interact with the internet
@NoSuchElementException
Scenario: Interact with the internet
Given I click the delete button
@ElementClickInterceptedException
Scenario: Element Click Intercepted
Given I try to click the click here text
@NoAlertPresentException
Scenario: No Alert Present
Given I try to switch to an alert box that isn't present
@ElementNotInteractableException @ElementNotVisibleException
Scenario: Can not interact with element
Given I try to hover over the "Downloads" text
@InvalidSelectorException
Scenario: Invalid Selector
Given I try to click the "23.4" text
@NoSuchWindowException
Scenario: No Such Window
Given I try to switch to a window that doesn't exist
@NoSuchFrameException
Scenario: No Such Frame
Given I try to switch to a frame that doesn't exist
@StaleElementReferenceException
Scenario: Stale Element Reference
Given I switch to the left frame
@TimeoutException
Scenario: Timeout waiting for element
Given I dont wait very long
@TimeoutException
Scenario: Timeout waiting for page to load
Given I dont wait very long for the page to load
NoSuchElementException (No Such Element Exception)
This example with 'The Internet' is similar to our calculator application above. We're going to try finding a 'Delete' button on this page when there isn't one.
No Such Element Exception
Our FindElement statement in this case is looking for a delete button with the By.Id selector.
[Given("I click the delete button")]
public void IClickTheDeleteButton()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/dynamic_loading/1";
try
{
// there is no delete button on this page
IWebElement DeleteButtonElement = _browserDriver.CurrentWebDriver.FindElement(By.Id("delete"));
_theinternetPageObject.DeleteButtonElement.Click();
}
catch (OpenQA.Selenium.NoSuchElementException ex)
{
Debug.WriteLine("Deal with No Such Element Exception");
throw ex;
}
catch (Exception ex)
{
Debug.WriteLine("Deal with any other exception.");
throw ex;
}
}
We've provided the fully qualified name for the exception in this example, OpenQA.Selenium.NoSuchElementException. You'll see in subsequent examples that we're able to shorten this to just the exception name, OpenQA.Selenium.NoSuchElementException.
Another thing you'll notice in this example is that we also have the 'catch all' block that catches any other Exception. We've omitted this is the following examples (for clarity), but you should seriously consider using this.
The final thing to notice in this example is that when we catch the Exception we re-throw it. Why do we do this? Well if we just catch it then for our SpecFlow examples the exception doesn't get handed back up the stack. Which means even if you have the exception the test case passes. If you want the test case to fail then you need to pass the exception back up the stack to SpecFlow with the throw statement. This is largely a judgement call… if your catch block recovers the test and nothing fails then you wouldn't want to re-throw the exception.
ElementClickInterceptedException (Element Click Intercepted Exception)
In this example we try to click on an element where a modal box overlays the page.
Click Intercepted
We identify the 'click here' link with XPath and attempt to click on this link with a simple Click() method.
[Given(@"I try to click the click here text")]
public void GivenITryToClickTheText(string p0)
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/entry_ad";
IWebElement ClickHereLinkElement = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//a[contains(text(), \"click here\")]"));
try
{
Thread.Sleep(2000); // wait for the modal to appear
_theinternetPageObject.ClickHereLinkElement.Click();
}
catch (ElementClickInterceptedException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
}
}
As the modal overlay intercepts the click, and we don't actually click the 'Click here' link. Instead the ElementClickInterceptedException gets raised. In this instance we can extend the catch block to clear the overlay and then make the click action when we're ready.
[Given(@"I try to click the click here text")]
public void GivenITryToClickTheText()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/entry_ad";
IWebElement ClickHereLinkElement = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//a[contains(text(), \"click here\")]"));
TimeSpan ts = TimeSpan.FromSeconds(10);
try
{
Thread.Sleep(2000); // wait for the modal to appear
ClickHereLinkElement.Click();
}
catch (ElementClickInterceptedException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
// close the modal box
IWebElement CloseLinkElement = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//p[contains(text(), \"Close\")]"));
CloseLinkElement.Click();
// wait for modal box to close and then click element
var wait = new WebDriverWait(_browserDriver.CurrentWebDriver, ts);
var element = wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementToBeClickable(By.XPath("//a[contains(text(), \"click here\")]")));
element.Clic![[no-alert-present.png]]k();
}
}
In the example above we've enhanced the try/catch block code significantly. See how we've utilised the WebDriverWait and WaitHelpers here, along with the ElementToBeClickable method. With these Selenium extensions we only try the click once the modal has really disappeared and the link really is ready to be clicked.
NoAlertPresentException (No Alert Present Exception)
In this example we try to clear an Alert box which isn't present. This raises the NoAlertPresentException which we can then deal with in the catch block of code.
No Alert Present
Once on this page, and before the Alert box is generated, we'll try to Switch to the Alert. When that fails we'll make sure the Alert is displayed and then Switch to the Alert and Dismiss it.
[Given(@"I try to clear the wrong type of alert box")]
public void GivenITryToClearTheWrongTypeOfAlertBox()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/javascript_alerts";
IWebElement AlertButton = _browserDriver.CurrentWebDriver
.FindElement(By.XPath("//button[contains(text(), \"Click for JS Alert\")]"));
try
{
_browserDriver.CurrentWebDriver.SwitchTo().Alert();
}
catch (NoAlertPresentException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
AlertButton.Click();
Thread.Sleep(1000);
_browserDriver.CurrentWebDriver.SwitchTo().Alert().Dismiss();
}
}
ElementNotInteractableException & ElementNotVisibleException (Element Not Interactable Exception & Element Not Visible Exception)
First thing to say is that the ElementNotVisibleException and ElementNotInteractableException are very similar. Which one turns up depends very much on your application and how the driver interacts with the elements in your application. Ultimately you're going to handle both of these in the same way. Which is why this is a good place to show you how to run the same catch block of code for different exceptions. We'll get on to that in a moment.
Here we going to try and interact with an element in a JQuery UI menu that isn't displayed yet and can't be interacted with until the top level menu option has been selected. So we're looking for the 'Download' option which, until you hover over the 'Enabled' item, won't be visible.
Element Not Interactable
What we do in the following code example is try to interact with the 'Download' element before it's displayed. This fails and we catch the exception. It's in the 'catch' block that we then interact with 'Enabled' element to display the 'Downloads' element. Then in the 'finally' block we'll click the 'Download' menu item.
[Given(@"I try to hover over the ""([^""]*)"" text")]
public void GivenITryToHoverOverTheText(string p0)
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/jqueryui/menu#";
IWebElement EnabledMenuItem = _browserDriver.CurrentWebDriver
.FindElement(By.XPath("//a[contains(text(), \'" + "Enabled" + "\')]"));
try
{
// 1st attempt to interact with the Download menu item (hover)
IWebElement DownloadsMenuItem = _browserDriver.CurrentWebDriver
.FindElement(By.XPath("//a[contains(text(), \'" + p0 + "\')]"));
Actions action = new Actions(_browserDriver.CurrentWebDriver);
action.MoveToElement(DownloadsMenuItem).Perform();
}
catch (Exception ex) when (ex is ElementNotInteractableException || ex is ElementNotVisibleException)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
Actions action = new Actions(_browserDriver.CurrentWebDriver);
action.MoveToElement(EnabledMenuItem).Perform();
}
finally
{
// 2nd attempt to interact with Download menu item (click)
Thread.Sleep(1000);
IWebElement DownloadsMenuItem = _browserDriver.CurrentWebDriver
.FindElement(By.XPath("//a[contains(text(), \'" + p0 + "\')]"));
Actions action = new Actions(_browserDriver.CurrentWebDriver);
action.MoveToElement(DownloadsMenuItem).Perform();
DownloadsMenuItem.Click();
}
}
When you run this code, initially, the 'Download' menu item isn't displayed. We try get the 'DownloadsMenuItem' element (even though it's not displayed) and we try to hover over the element. This is when see the ElementNotInteractableException raised.
We've then enhance our code by adding the catch block. This moves the cursor over the 'Enabled' element thus revealing the 'Download' element. Then we're in a position to interact with the 'Download' element.
A couple of interesting point about this example. First note how C# handles 'variable scope' between the try/catch/finally blocks. If you declare a variable _inside_ the 'try' block it's visible also in the 'catch' block. However, it's NOT visible in the 'finally' block. This is why the 'DownloadMenuItem' variable is declared again in the 'finally' block.
Second see how we've use a single catch block to catch both exeception types.
`catch (Exception ex) when (ex is ElementNotInteractableException || ex is ElementNotVisibleException)`
This is a trick worth having up your sleeve if you're going to manage the catch actions the same way for different exception types. Which is typical for the ElementNotVisibleException and ElementNotInteractableException exceptions which are very similar. This avoids you having to write duplicate code.
InvalidSelectorException (Invalid Selector Exception)
Possibly the simplest exception of them all to deal with. In the code example below we've used an XPath when the FindElement method is being used with the By.CssSelector find elements mechanism. This forces the InvalidSelectorException which we catch and just log the message.
There's no real recovery method from this. So nothing worth implementing in the catch or finally blocks. You'd just need to fix your selectors.
[Given(@"I try to click the ""([^""]*)"" text")]
public void GivenITryToClickTheText(string p0)
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/large";
try
{
// Highlights the importanct of having the Try block wrapping the correct statements
IWebElement SiblingText = _browserDriver.CurrentWebDriver
.FindElement(By.CssSelector("//a[contains(text(), \'" + p0 + "\')]"));
SiblingText.Click();
}
catch (InvalidSelectorException ex)
{
// note that as SpecFlow doesnt' catch the exception the test passes!
Debug.WriteLine("Deal with Exception:" + ex.Message);
}
}
Worth pointing out though, that in the SpecFlow domain if you do catch the Exception then it's considered dealt with by SpecFlow. It's not passed up the stack. The results of this is that SpecFlow will see this as a passing step even though an exception was raised.
Exception Caught but Test Still Passes
The way to deal with this is to throw the exception back up the stack so that SpecFlow can still catch the exception. So you might go with something like this in the catch block:
catch (InvalidSelectorException ex)
{
// note that as SpecFlow doesnt' catch the exception the test passes!
Debug.WriteLine("Deal with Exception:" + ex.Message);
throw ex;
}
In this way we get to catch and deal with the error (not that we're doing a lot in the above example but you get the idea), and then throw the exception back up to SpecFlow to force the test to fail.
Exception Caught and Thrown Again
NoSuchWindowException (No Such Window Exception)
In this example we try to swap to a new window using the Selenium window handles. The new window opens successfully (using the SwitchTo().Window() method) but we fail to use a valid window handle when we attempt the switch.
[Given(@"I try to switch to a window that doesn't exist")]
public void GivenITryToSwitchToAWindowThatDoesntExist()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/windows";
string currentWindowHandle = _browserDriver.CurrentWebDriver.CurrentWindowHandle.ToString();
try
{
IWebElement OpenWindowLink = _browserDriver.CurrentWebDriver
.FindElement(By.XPath("//a[contains(text(), \"Click Here\")]"));
OpenWindowLink.Click();
_browserDriver.CurrentWebDriver.SwitchTo().Window("Doesn't Exist");
}
catch (NoSuchWindowException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
}
finally
{
// Switch back to the original window
_browserDriver.CurrentWebDriver.SwitchTo().Window(currentWindowHandle);
}
}
As Selenium can't find the new window we raise the 'NoSuchWindowException'. Note that a good trick here is to store the 'currentWindowHandle' at the start so that the finally block can be used to swap back regardless of an Exception being thrown or not.
NoSuchFrameException (No Such Frame Exception)
With this example we're using Selenium's ability to SwitchTo() a new frame and just providing an invalid frame Id with Frame(3)
[Given(@"I try to switch to a frame that doesn't exist")]
public void GivenITryToSwitchToAFrameThatDoesntExist()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/iframe";
IWebElement iFrame = _browserDriver.CurrentWebDriver
.FindElement(By.XPath("//iframe[contains(@title, \"Rich Text Area\")]"));
try
{
// invalid frame element/id used
_browserDriver.CurrentWebDriver.SwitchTo().Frame(3);
}
catch (NoSuchFrameException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
_browserDriver.CurrentWebDriver.SwitchTo().Frame(iFrame);
}
Nothing complicated there. Just a question of using the correct 'iFrame' element when we have a 2nd attempt to switch to the frame. The technique worth picking up from this though is capturing the valid iFrame object that you're starting out with . Saving the valid frame reference with …
` IWebElement iFrame = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//iframe[contains(@title, "Rich Text Area")]"));`
Then you can recover from any exceptions later by switching back to this good framework whenever you need it.
More on handling nested frames and the SwitchTo().Frame() method in the next example.
StaleElementReferenceException (Stale Element Reference Exception)
This is an interesting one. We're using nested frames here and simulating a stale element. That is, an element that you've found and stored in a variable. Then you go back and attempt to interact with that element but the element has changed in some way. It's changed such that your reference to the element is no longer valid.
In the example below we drill right down through this hierarchy of nested frames. However, before we get to the bottom layer (where the right and left frames are) we store references to both these frames in a couple of IWebElement variables.
Then we drill right down, switching to the Right Frame. Now the Right frame does NOT contain the left frame. It's as we try to reference the left frame whilst being in the right frame that the StaleElementReferenceException gets raised.
[Given(@"I switch to the left frame")]
public void GivenISwitchToTheLeftFrame()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/nested_frames";
IWebElement TopFrame = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//frame[contains(@name, \"frame-top\")]"));
IWebElement RightFrame;
IWebElement LeftFrame;
try
{
_browserDriver.CurrentWebDriver.SwitchTo().ParentFrame();
// We've selected the main frameset so we can now switch to the child/top frame
_browserDriver.CurrentWebDriver.SwitchTo().Frame(TopFrame);
// find the left and right frames
RightFrame = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//frame[contains(@name, \"frame-right\")]"));
LeftFrame = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//frame[contains(@name, \"frame-left\")]"));
// switch to the right frame
_browserDriver.CurrentWebDriver.SwitchTo().Frame(RightFrame);
// now we're in the right frame we shouldn't be able to switch to the left frame
_browserDriver.CurrentWebDriver.SwitchTo().Frame(LeftFrame);
}
catch (StaleElementReferenceException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
// try and find left frame again
_browserDriver.CurrentWebDriver.SwitchTo().ParentFrame();
LeftFrame = _browserDriver.CurrentWebDriver.FindElement(By.XPath("//frame[contains(@name, \"frame-left\")]"));
_browserDriver.CurrentWebDriver.SwitchTo().Frame(LeftFrame);
}
}
Obviously te the prefered solution is not to try and access the wrong frame in the first place. However, we can recover in the catch block with the `SwitchTo().ParentFrame()`
and then try to access the left frame again.
Avoiding the StaleElementReferenceException, in many cases, comes down to making sure the logic in your scripts is valid. In this way if you do hit this exception it's down to a correct test failure and the exception needs to be handled by the test runner (e.g. SpecFlow) and the test case Failed.
WebDriverTimeoutException (Web Driver Timeout Exception)
Two examples to highlight instances of the WebDriverTimeoutException. First we'll look at a timeout based around searching for an element. The we'll look at a timeout based on waiting for the page to load. In looking at both we'll see how to handle this WebDriverTimeoutException by using WebDriverWait and seeting the pageLoad time out in the driver.
First then if we're waiting for an element to load on a page we can use the WebDriverWait class. Something along these lines:
[Given(@"I dont wait very long")]
public void IDontWaitVeryLong()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/dynamic_loading/1";
//string currentWindowHandle = _browserDriver.CurrentWebDriver.CurrentWindowHandle.ToString();
IWebElement template = _browserDriver.CurrentWebDriver.FindElement(By.XPath("/html/body/div[2]/div/div/div[1]/button"));
try
{
template.Click();
WebDriverWait wait = new WebDriverWait(_browserDriver.CurrentWebDriver, TimeSpan.FromSeconds(60));
IWebElement LoginButton = wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions
.ElementIsVisible(By.XPath("//H4[contains(text(), \"Hello World!\")]")));
}
catch (WebDriverTimeoutException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
throw ex; // re-throw the exception to fail the test case
}
}
What we're doing here is open a page that has a 'start' button. Once we click on that button it dynamicaly loads an element that we wait for with WebDriverWait. Note that we've set this to 2 seconds. This times out, raises the WebDriverTimeoutException which we catch. We then throw this again so that SpecFlow will report the test fails.
However if we update this statement to say 60 seconds….
`WebDriverWait(_browserDriver.CurrentWebDriver, TimeSpan.FromSeconds(60))`
Then we can prove that the elements found, the execption isn't raised and the test case passes.
The other scenario where the WebDriverTimeoutException tends to get raised is in waiting for pages to load. In the example below we force the driver to only wait 2 seconds for the page to load. Yet the page we're loading takes over 30 seconds to load (due to a rogue GET request).
[Given(@"I dont wait very long for the page to load")]
public void GivenIDontWaitVeryLongForThePageToLoad()
{
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/slow";
try
{
_browserDriver.CurrentWebDriver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(60);
_browserDriver.CurrentWebDriver.Url = "https://the-internet.herokuapp.com/slow_external";
}
catch (WebDriverTimeoutException ex)
{
Debug.WriteLine("Deal with Exception:" + ex.Message);
throw ex; // re-throw the exception to fail the test case
}
}
The key statement in all of this is the setting of the PageLoad parameter in the driver. This is done with..
``` _browserDriver.CurrentWebDriver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(6);```
With this the WebDriverTimeoutException exception gets raised and SpecFlow picks this up because we re-throw the exception. It proves though, that the page failing to load throws the same execption.
If you modify this to set the page load time out to 60 seconds..
``` _browserDriver.CurrentWebDriver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(60);```
Then you'll see the test case pass. We've set the timeout to 60 seconds which is well past the 30 seconds the page takes to complete loading.
n short then two methods you can use to deal with the WebDriverTimeoutException exception from a page load and/or an element load. Use the WebDriverWait class to adjust for the element loading times. Use the driver .Manage().Timeouts().PageLoad setting to deal with page load times.
Conclusion
You have to understand the fundamentals of handling exceptions in C# before you learn about the Selenium specifics. Once you've grasped the core concepts for the try/catch/finally blocks (and that includes things like variable scope and re-throwing exceptions) then you'll be better placed to understand how to handle the Selenium specific exceptions.
There are about 10 Selenium Exceptions that it's worth getting to grips with. Everything from not finding elements, window handling and timeouts. Get to grips with all 10 and you'll be well placed to harden and create far more reliable automated tests with Selenium and C#.