Can not catch WebDriverException for certain elements - c#

I have the following elements identified in a page object..
public WindowsElement usernameField => _session.FindElementByAccessibilityId("UserName");
public WindowsElement passwordField => _session.FindElementByAccessibilityId("Password");
public WindowsElement menuButton => _session.FindElementByXPath("//Button[contains(#Name, 'Menu')]");
I have a test with the following steps..
WaitForObject(usernameField)
usernameField.SendKeys("...")
WaitForObject(passwordField)
passwordField.SendKeys("...")
ClickButton("Sign In");
WaitForObject(menuButton);
menuButton.Click();
And below, here is my WaitForObject method..
// Wait for an Object to be accessible
public void WaitForObject(WindowsElement element)
{
var wait = new DefaultWait<WindowsDriver<WindowsElement>>(_session)
{
Timeout = TimeSpan.FromSeconds(10),
PollingInterval = TimeSpan.FromSeconds(1)
};
wait.IgnoreExceptionTypes(typeof(WebDriverException));
wait.IgnoreExceptionTypes(typeof(InvalidOperationException));
wait.IgnoreExceptionTypes(typeof(StaleElementReferenceException));
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
wait.IgnoreExceptionTypes(typeof(NotFoundException));
WindowsElement waitElement = null;
wait.Until(driver =>
{
waitElement = element;
return waitElement != null && waitElement.Enabled && waitElement.Displayed;
});
}
The WaitForObject method works great for the usernameField & passwordField checks, but for some reason it fails immediately when passing in the menuButton. I know it was checking properly for usernameField & passwordField because I included some Console.WriteLines() to print out whenever it would detect one of those exceptions. As soon as it gets to the menuButton, nothing is logged it just fails immediately with a WebDriverException
OpenQA.Selenium.WebDriverException : An element could not be located on the page using the given search parameters.
Why would it not act the same for the menuButton? I have tried other approaches using while loops catching general Exceptions, but still it fails immediately when it gets to this element with a WebDriverException.
If I use a Thread.Sleep(10000) before trying to check for the element, it works fine and performs the click..
I am using WinAppDriver / Appium libraries.

Oh wait, sorry, I looked at your code more closely. Basically what's happening is because the method parameter is asking for the type itself, when C# goes to hand in the element to WaitForObject, it tries to evaluate the "WindowsElement menuButton" expression when it's handed to WaitForObject. By changing the WaitForObject method to accept a delegate, you'll defer that evaluation until you're inside the wait.
You'll need to change your WaitForObject to be something like:
public void WaitForObject(Func<WindowsElement> element)
// Unchanged code here
wait.Until(driver =>
{
waitElement = element();
return waitElement != null && waitElement.Enabled && waitElement.Displayed;
});
THEN call it like:
WaitForObject(() => menuButton);
menuButton.Click();

you can try using regex instead of button use '*'
as inspect.exe is not defining the tag i.e. button.
else go With finding the element with Name locator.

Related

How can I use Selenium 'Until' function without throwing an Exception?

I am trying to create a test script using selenium that might interact with an element but if the element is not there it will not. I need to add that the element might take some time to appear. The issue is that if I use FindElement I get an exception. If I use FindElements it take too long. So, I tried to used "until" function which work well to wait until the element to appear... but if it doesn't appear it throws an exception I want to avoid that.
I know I can use a try catch.But, is there any better way to do this?
I curently have this:
IWebElement button;
try{
string x = "search query";
button = this.WaitDriver.Until(d => d.FindElement(By.XPath(x)));
}catch{
button = null;
}
If you want to test for existence without a try-catch then you would have to use .FindElements(). It shouldn't take any longer than .FindElement() using the same wait but the wait will time out if the element doesn't exist... which is a pain. One way to get around this is to do something like the below. It's basically what you had with a few tweaks.
public bool ElementExists(By locator)
{
try
{
new WebDriverWait(driver, TimeSpan.FromSeconds(5)).Until(ExpectedConditions.ElementExists(locator));
return true;
}
catch (Exception e) when (e is NoSuchElementException || e is WebDriverTimeoutException)
{
return false;
}
}
I prefer checking for existence rather returning a null because then you have to check for null also. By putting this in a function, you can call it whenever and your tests stay cleaner. You would use it like
By locator = By.Id("someId");
if (ElementExists(locator))
{
IWebElement e = driver.FindElement(locator);
// do something with e
}

How to add expected condition to function which finds element

I have code which finds element with delay, but sometimes element is already but not clickable and not available in DOM, so what i should to add to my code to check those arguments
public IWebElement FindElement(IWebDriver driver, By howBy, int timeoutInSeconds = 10)
{
TimeSpan elementTimeOut = TimeSpan.FromSeconds(20);
IWebElement elementfound = null;
try
{
WebDriverWait wait = new WebDriverWait(driver, elementTimeOut);
elementfound = wait.Until<IWebElement>(d =>
{
try
{
elementfound = driver.FindElement(howBy);
}
catch (NoSuchElementException e)
{
Debug.WriteLine("Please fail NoSuchElementException");
throw;
}
return elementfound;
});
}
catch (WebDriverTimeoutException e)
{
Debug.WriteLine("Please fail WebDriverTimeoutException");
throw;
}
return elementfound;
}
Firstly, It'll check if it's 'visible' by using the standard ExpectedConditions.visibilityOfElementLocated, it'll then simply check if the element.isEnabled() is true or not.
This can be condensed slightly, this basically means (simplified, in C#):
Wait until the element is returned from the DOM
Wait until the element's .Displayed property is true (which is essentially what visibilityOfElementLocated is checking for).
Wait until the element's .Enabled property is true (which is essentially what the elementToBeClickable is checking for).
I would implement this like so (adding onto the current set of ExpectedConditions, but there are multiple ways of doing it:
// <param name="locator">The locator used to find the element.</param>
// <returns>The <see cref="IWebElement"/> once it is located, visible and clickable.</returns>
public static Func<IWebDriver, IWebElement> ElementIsClickable(By locator)
{
return driver =>
{
var element = driver.FindElement(locator);
return (element != null && element.Displayed && element.Enabled) ? element : null;
};
}
Above method can be used something like:
var wait = new WebDriverWait(driver, TimeSpan.FromMinutes(1));
var clickableElement = wait.Until(ExpectedConditions.ElementIsClickable(By.Id("id")));
However, you can pass howBy as the param of ElementIsClickable() method.
elementToBeClickable is used to wait for an element to be clickable .
Please use the following code inside your method.
elementfound = wait.until(ExpectedConditions.elementToBeClickable(howBy);

Return a dynamic unknown type

As part of using page object pattern, I have a method that should return the a class when clicking a button.
The thing is that I have several buttons in a list which each one of them returns a different page, therefore a different class.
I have tried using the following but getting an error.
public dynamic ClickTheMenuButtonWorkplace<T>(string ElementID, T ClassToReturn)
{
WebDriverWait wait = new WebDriverWait(_webdriver, TimeSpan.FromSeconds(10));
var MenuButton = wait.Until(x => x.FindElement(By.Id(ElementID)));
MenuButton.Click();
return ClassToReturn;
}
The T actually represents the class is being returned , according to the button clicked.
An example of using this method is :
Lakochot IDDetails = new SargelElyon(_webdriver).ClickTheMenuInabWorkplace("nav_conts", new Lakochot(_webdriver));
IDDetails.foo();
The LoginPgae currently have only a constructor that initializing the webdriver and has a foo method that printing something.
I'm getting an error says:
Missing compiler required member Microsoft.CSharp.RuntimeBinder.Binder.Convert
Maybe there is a better way doing that anyway..?
In your specific case why not return T?
The T actually represents the class is being returned, according to the button clicked.
makes it look like this is exactly what you want.
public T ClickTheMenuButtonWorkplace<T>(string ElementID, T ClassToReturn)
{
WebDriverWait wait = new WebDriverWait(_webdriver, TimeSpan.FromSeconds(10));
var MenuButton = wait.Until(x => x.FindElement(By.Id(ElementID)));
MenuButton.Click();
return ClassToReturn;
}
...
var IDDetails = new SargelElyon(_webdriver)
.ClickTheMenuButtonWorkplace("nav_conts", new Lakochot(_webdriver));
// ^ is a Lakochot
IDDetails.foo();
If you want to use dynamic, which you likely do not need here, see Tom Coldenhoff's answer.

Unable to await method in ASP.NET (4.6) MVC5 project

I'm currently seeing a problem whereby my await method is just hanging, and causing the response to just hang, not doing anything until I kill the request. This is evident in both Chrome debug tools and Fiddler.
I have the following API action defined:
[Route("state/{stateCode}")]
[LogApiCallFilter]
public async Task<IList<MapPlaceDTO>> GetWithinState(string stateCode)
{
//
// Additional code truncated for SO
// Via debugging I know that the 'state' variable below is correct
//
IList<Place> places = await _placeManager.GetPlacesInState(state);
// Instantiate the list of places.
IList<MapPlaceDTO> mapPlaces = new List<MapPlaceDTO>();
// Iterate through the places and add to the map place list
foreach (Place place in places)
{
mapPlaces.Add(MapPlaceDTO.FromPlace(place));
}
return mapPlaces;
}
When I step through that code in debug mode for a unit test for the GetWithinState action, the IList<Place> places = await _placeManager.GetPlacesInState(state); method runs without exception, however I am not able to hover over the places variable to inspect it, nothing happens. Nor can I add it to the watch list, I get the following message:
error CS0103: The name 'places' does not exist in the current context
Interestingly however, if I run the exact same code within a "PlaceManager" unit test, outside of the Web API project, the test runs fine, and I can inspect the places variable.
[Fact(DisplayName = "Can_Get_All_Places_Within_State")]
[Trait("Category", "Place Manager")]
public async Task Can_Get_All_Places_Within_State()
{
State state = new State()
{
ShortName = "VIC",
Name = "Victora",
CountryCode = "AU"
};
IList<Place> places = await _placeManager.GetPlacesInState(state);
Assert.NotNull(places);
Assert.True(places.Count > 0);
}
This is the code that runs within the PlaceManager.GetPlacesInState method:
public async Task<IList<Place>> GetPlacesInState(State state)
{
if (state == null)
{
throw new ArgumentNullException("state", "The 'state' parameter cannot be null.");
}
// Build the cache key
string cacheKey = String.Format("places_state_{0}", state.Id);
// Get the places from the cache (if they exist)
IList<Place> places = CacheManager.GetItem<IList<Place>>(cacheKey);
// Get the places from the database.
if (places == null)
{
// Get the places from the database
places = await _repository.Find(i => i.State.ToLower() == state.ShortName.ToLower() && i.Country.ToLower() == state.CountryCode.ToLower());
// If there are places, then add to the cache for next time
if (places != null && places.Count > 0)
{
CacheManager.AddItem(cacheKey, places);
}
}
// return the places
return (places != null ? places : new List<Place>());
}
Does anyone have any idea why this may be occurring within the API method, but is working fine in unit tests?
As mentioned by Alexei Levenkov above, I was causing a deadlock using the getDataAsync().Result code.
While mapping my Place object to a MapPlaceDTO object, the Place object has a get property to load the place type, which would call an async function in the following way:
public PlaceType PlaceType
{
get
{
return getPlaceType().Result;
}
}
Once I removed that property and just called the GetPlaceType method directly using the await keyword, everything started working correctly. Thanks Alexei!

Selenium WebDriver FindElements do not return enough values

I have setup a class with properties get value from WebDriver.FindElements().
public IList<IWebElement> ListObjectElements
{
get
{
var container = WebDriver.FindElement(By.Id("objects"));
return container.FindElements(By.XPath("//*[contains(#id, 'id_')]"));
}
}
I also implemented test case to test Add New function.
All steps ran success. When I tried to get the return of new list after Add New, it missed 1 item.
I set a break point to watch value. The property ListObjectElements has 10 items, but the return newList only has 9.
var newList = clientpage.ListObjectElements;
return newList;
If I add a Thread.Sleep(), the return newList has 10 items same as the property ListObjectElements.
How could I get the exactly results without using Thread.Sleep()?
Thanks in advance for you help.
It sounds like the site you're automating adds the elements representing the objects dynamically to the DOM, and that your code is then losing the race condition that you execute FindElements before the elements are actually added to the DOM. You'll need to implement some sort of wait in your code. You might be able to leverage the WebDriverWait construct, the .NET implementation of which is available in the WebDriver.Support assembly. The construct could be used in a manner similar to the following:
// Assumes 'driver' is a valid IWebDriver instance.
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until<bool>((d) =>
{
return clientPage.ListObjectElements.Count > 9;
});
Putting this in a more concrete context in your test case would look something like this, depending on your architecture and desires:
// Assume that the element you click on to add a new element
// is stored in the variable 'element', and your IWebDriver
// variable is 'driver'.
int originalCount = clientPage.ListObjectElements.Count;
element.Click();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
wait.Until<bool>((d) =>
{
return clientPage.ListObjectElements.Count > originalCount;
});
return clientPage.ListObjectElements;

Categories

Resources