Check if element is interactable in C# Selenium/ChromeDriver app? - c#

I have a C# Selenium app that uses the ChromeDriver/WebDriver NuGet package that supports Chrome 88. My Chrome version is 88.0.4324.104.
Some of the web pages I am working with have more than one BUTTON element with the same class and and tagName so when I execute my FindDomElement() call using my desired xpath, I get back multiple matching elements. Only one of the returned elements is interactable and the other ones are not. Right now I try executing a "Click()" call on each element until I find the right one, catching the ElementNotInteractableException exception for the ones that fail.
Is there a way to ask Selenium if an element is interactable?
Please note, all the elements are visible and enabled and I already know how to check the status of those properties via C#. They simply are not helpful in this case.

An extension method might be a better solution here. It would return true if the element was clicked, and return false if the element was not interactable.
public static class WebElementExtensions
{
public static void TryClick(this IWebElement element)
{
try
{
element.Click();
return true;
}
catch (ElementNotInteractableException)
{
return false;
}
}
}
Then it is just a simple test:
if (button.TryClick())
{
// Button was clicked successfully
}
else
{
// Button is not interactable
}

Related

Clear() not actually clearing the input element

I ran into a scenario today where clear() wasn't actually clearing the input element and I'm hoping someone can shed some light on this as I've never ran into this before.
It looks like the method executes successfully without throwing an exception but it just doesn't clear.
Clear Method - this is in a separate framework that is being used by the calling project
public void Clear(By element)
{
try
{
driver.FindElement(element).Clear();
}
catch (Exception ex)
{
DTAF.Helpers.Screenshot.TakeScreenshot(driver);
throw ex;
}
}
Element HTML - you can see it has value that I was trying to clear
<input aria-invalid="false" id="txtCustomerEditFirstName" type="text" data-testid="firstname" class="MuiFilledInput-input MuiInputBase-input css-1ncak0i" value="test123">
I was able to execute just raw selenium in the Immediate window and it looked like it cleared the input but when I clicked the actual element in the UI the value just came back so it didn't actually clear it.
I did find a workaround where I just get the input value attribute and loop through that pressing the Backspace key until theres nothing left. It works just fine but I'd prefer to use the selenium clear method if possible.
EDIT
Element Locator
public static By FirstNameTextbox = By.Id("txtCustomerEditFirstName");
Calling method
public EditUnregisteredCustomerProfileModal ClearAndEnterFirstName(string firstName)
{
try
{
ElementActions.Clear(EditUnregisteredCustomerProfileModalElements.FirstNameTextbox);
ElementActions.Type(EditUnregisteredCustomerProfileModalElements.FirstNameTextbox, firstName);
}
catch (Exception e)
{
DTAFLogger.GetLogger().Error($"Failed to clear and enter unregistered customer first name '{firstName}'. {e.Message}");
Assert.Fail($"Failed to clear and enter unregistered customer first name '{firstName}'. {e.Message}");
}
return this;
}

Selenium Wait Until using Element rather the By

Afternoon,
I could use a little advise. I have page object setup for example
IWebElement SiteInUse => DriverContext.Driver.FindElement(By.ClassName("site-txt"));
I have a method set up that will
1. Wait till the element is visible.
2. Check the Elements text is correct.
I am trying to above doing something like
WaitHelpers.WaitTillVisiible(By.ClassName("site-txt"));
as if the id changes I will need to edit it in two places. I am trying to create a extension method for IWebElement.
I have tried
ublic static bool WaitUntilElementIsVisible(this IWebElement element)
{
WebDriverWait wait = new WebDriverWait(DriverContext.Driver, TimeSpan.FromSeconds(30));
return wait.Until(ElementIsVisible(element));
}
public static Func<IWebDriver, bool> ElementIsVisible(IWebElement element)
{
return (driver) =>
{
try
{
return element.Displayed;
}
catch (Exception)
{
// If element is null, stale or if it cannot be located
return false;
}
This works but only if the element is visible but will not continue to look for the 30 seconds.
What am I doing wrong?
I will attempt to answer given the original problem, which was needing to test for visibility, but this required you to update a locator in two places.
The answer is insanely simple: define a instance field for the locator:
public class SomePageModel
{
By siteInUseLocator = By.ClassName("site-txt");
IWebElement SiteInUse => DriverContext.Driver.FindElement(siteInUseLocator);
...
}
Then later on you can reuse this field to test for visibility:
WaitHelpers.WaitTillVisiible(siteInUseLocator);

Selenium c# - waiting for text not to be visible but doesn't seem to work

I am new to Selenium and C# and creating an automated test whereby I create something and delete it straightaway (all in the same test).
The last step in my test is to verify the name of the item I've deleted is no longer visible - this is where I seem to be getting stuck.
I am in a modal dialog and the item is visible in the background, so once I confirm the deletion in the modal dialog, the next step is to verify the item name is no longer visible, but the code does see the name and therefore throws an exception because it is true instead of my expected result of false.
See below the code I'm using:
public bool DeletedCategoryNoLongerVisible(string CategoryDisplayName) {
try {
WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(20));
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath("//*[#id='10_anchor']")));
Driver.FindElement(By.XPath($ "//*[#class='jstree-anchor'][text()='{CategoryDisplayName}']"));
return false;
} catch (Exception) {
return true;
}
}
You can wait for the targeted element to be missing or for its displayed text to be empty with InvisibilityOfElementWithText:
new WebDriverWait(Driver, TimeSpan.FromSeconds(20))
.Until(ExpectedConditions.InvisibilityOfElementWithText(By.XPath(...), CategoryDisplayName));
And if the element is hidden with another element on top of it, then try to click it until failure:
new WebDriverWait(Driver, TimeSpan.FromSeconds(20))
.Until((driver) => {
try {
driver.FindElement(By.XPath(...)).Click();
return false;
} catch (WebDriverException) {
return true;
}
});

Selenium Webdriver C# wait for existence when using PageFactory

I need to click an okay button which might appear after completing a field - it might take 5 seconds to appear. So i need (if) Wait for existence 5 seconds. I'm using PageFactory in a pages framework, I've seen some solutions but cant figure out how to implement them in this context.
[FindsBy(How = How.Name, Using = "OK")]
private IWebElement alertOKBtn;
public void PopulateFields //method to populate the form
{
// Populate fields
dateFromField.SendKeys(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
// Click on this field
descriptionField.Click();
//OK button might appear, might take 5secs - pseudcode
if ( ***alertOKBtn exists, wait for it for 5 secs..*** )
{
alertOkBtn.Click();
}
//continue populating form
}
The PopulateFields method is called from the [Test] as:-
Pages.PTW.PopulateFields();
where Pages.PTW is a get method to PageFactory.InitElements(browser.Driver, page); return page;
Managed to resolve it - in PopulateFields i now do this:-
//wait to see if alert popup appears - give it 8 secs
string waitToSee = browser.wait(alertOKBtn, 8);
if ( waitToSee == "true" )
{
alertOKBtn.Click(); //alert popup did appear
}
Then I've added a method to my browser.class :-
public static string wait(IWebElement elem, int timeout ) //waits for existence of element up to timeout amount
{
try
{
var wait = new WebDriverWait(webDriver, TimeSpan.FromSeconds(timeout));
wait.Until(ExpectedConditions.ElementToBeClickable(elem));
return "true";
}
catch (Exception e ) //didnt appear so exception thrown return false
{
return "false";
}
So it now waits up to 8 seconds and if it doesnt appear it ignores and moves on. Thanks Bendram for the pointers.
Need to add conditional wait. That means, your code should wait till the control appears and then perform the action.
WebDriverWait class which inherits DefaultWait class serves the purpose. The below is the code snippet.
var wait = new WebDriverWait(this.driver, waitTime);
wait.Until(ExpectedConditions.ElementToBeClickable(alertOkBtn));
alertOkBtn.Click();

Is is possible to know for sure if a WebBrowser is navigating or not?

I'm trying to find a way for my program to know when a WebBrowser is navigating and when is not. This is because the program will interact with the loaded document via JavaScript that will be injected in the document. I don't have any other way to know when it starts navigating than handling the Navigating event since is not my program but the user who will navigate by interacting with the document. But then, when DocumentCompleted occurs doesn't necessarily mean that it have finished navigating. I've been googling a lot and found two pseudo-solutions:
Check for WebBrowser's ReadyState property in the DocumentCompleted event. The problem with this is that if not the document but a frame in the document loads, the ReadyState will be Completed even if the main document is not completed.
To prevent this, they advise to see if the Url parameter passed to DocumentCompleted matches the Url of the WebBrowser. This way I would know that DocumentCompleted is not being invoked by some other frame in the document.
The problem with 2 is that, as I said, the only way I have to know when a page is navigating is by handling the Navigating (or Navigated) event. So if, for instance, I'm in Google Maps and click Search, Navigating will be called, but just a frame is navigating; not the whole page (on the specific Google case, I could use the TargetFrameName property of WebBrowserNavigatingEventArgs to check if it's a frame the one that is navigating, but frames doesn't always have names). So after that, DocumentCompleted will be called, but not with the same Url as my WebBrowsers Url property because it was just a frame the one that navigated, so my program would thing that it's still navigating, forever.
Adding up calls to Navigating and subtracting calls to DocumentCompleted wont work either. They are not always the same. I haven't find a solution to this problem for months already; I've been using solutions 1 and 2 and hoping they will work for most cases. My plan was to use a timer in case some web page has errors or something but I don't think Google Maps has any errors. I could still use it but the only uglier solution would be to burn up my PC.
Edit: So far, this is the closest I've got to a solution:
partial class SafeWebBrowser
{
private class SafeNavigationManager : INotifyPropertyChanged
{
private SafeWebBrowser Parent;
private bool _IsSafeNavigating = false;
private int AccumulatedNavigations = 0;
private bool NavigatingCalled = false;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSafeNavigating
{
get { return _IsSafeNavigating; }
private set { SetIsSafeNavigating(value); }
}
public SafeNavigationManager(SafeWebBrowser parent)
{
Parent = parent;
}
private void SetIsSafeNavigating(bool value)
{
if (_IsSafeNavigating != value)
{
_IsSafeNavigating = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsSafeNavigating"));
}
}
private void UpdateIsSafeNavigating()
{
IsSafeNavigating = (AccumulatedNavigations != 0) || (NavigatingCalled == true);
}
private bool IsMainFrameCompleted(WebBrowserDocumentCompletedEventArgs e)
{
return Parent.ReadyState == WebBrowserReadyState.Complete && e.Url == Parent.Url;
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
public void OnNavigating(WebBrowserNavigatingEventArgs e)
{
if (!e.Cancel) NavigatingCalled = true;
UpdateIsSafeNavigating();
}
public void OnNavigated(WebBrowserNavigatedEventArgs e)
{
NavigatingCalled = false;
AccumulatedNavigations++;
UpdateIsSafeNavigating();
}
public void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
{
NavigatingCalled = false;
AccumulatedNavigations--;
if (AccumulatedNavigations < 0) AccumulatedNavigations = 0;
if (IsMainFrameCompleted(e)) AccumulatedNavigations = 0;
UpdateIsSafeNavigating();
}
}
}
SafeWebBrowser inherits WebBrowser. The methods OnNavigating, OnNavigated and OnDocumentCompleted are called on the corresponding WebBrowser overridden methods. The property IsSafeNavigating is the one that would let me know if it's navigating or not.
Waiting till the document has loaded is a difficult problem, but you want to continually check for .ReadyState and .Busy (dont forget that). I will give you some general information you will need, then I will answer your specific question at the end.
BTW, NC = NavigateComplete and DC = DocumentComplete.
Also, if the page you are waiting for has frames, you need to get a ref to them and check their .busy and .readystate as well, and if the frames are nested, the nested frames .readystate and .busy as well, so you need to write a function that recursively retreives those references.
Now, regardless of how many frames it has, first fired NC event is always the top document, and last fired DC event is always that of the top (parent) document as well.
So you should check to see if its the first call and the pDisp Is WebBrowser1.object (literally thats what you type in your if statement) then you know its the NC for top level document, then you wait for this same object to appear in a DC event, so save the pDisp to a global variable, and wait until a DC is run and that DC's pDisp is equal to the Global pDisp you've saved during the first NC event (as in, the pDisp that you saved globally in the first NC event that fired). So once you know that pDisp was returned in a DC, you know the entire document is finished loading.
This will improve your currect method, however, to make it more fool proof, you need to do the frames checking as well, since even if you did all of the above, it's over 90% good but not 100% fool proof, need to do more for this.
In order to do successful NC/DC counting in a meaningful way (it is possible, trust me) you need to save the pDisp of each NC in an array or a collection, if and only if it doesn't already exist in that array/collection. The key to making this work is checking for the duplicate NC pDisp, and not adding it if it exists. Because what happens is, NC fires with a particular URL, then a server-side redirect or URL change occurs and when this happens, the NC is fired again, BUT it happens with the same pDisp object that was used for the old URL. So the same pDisp object is sent to the second NC event now occuring for the second time with a new URL but still all being done with the exact same pDisp object.
Now, because you have a count of all unique NC pDisp objects, you can (one by one) remove them as each DC event occurs, by doing the typical If pDisp Is pDispArray(i) Then (this is in VB) comparison wrapped in a For Loop, and for each one taken off, your array count will get closer to 0. This is the accurate way to do it, however, this alone is not enough, as another NC/DC pair can occur after your count reaches 0. Also, you got to remember to do the exact same For Loop pDisp checking in the NavigateError event as you do in the DC event, because when a navigation error occurs, a NavigateError event is fired INSTEAD of the DC event.
I know this was a lot to take, but it took me years of having to deal with this dreaded control to figure these things out, I've got other code & methods if you need, but some of the stuff I mentioned here in relation to WB Navigation being truely ready, haven't been published online before, so I really hope you find them useful and let me know how you go. Also, if you want/need clarification on some of this let me know, unfortunately, the above isn't everything if you want to be 100% sure that the webpage is done loading, cheers.
PS: Also, forgot to mention, relying on URL's to do any sort of counting is inaccurate and a very bad idea because several frames can have the same URL - as an example, the www.microsoft.com website does this, there are like 3 frames or so calling MS's main site that you see in the address bar. Don't use URL's for any counting method.
First I've converted the document to XML and then used my magic method:
nodeXML = HtmlToXml.ConvertToXmlDocument((IHTMLDocument2)htmlDoc.DomDocument);
if (ExitWait(false))
return false;
conversion:
public static XmlNode ConvertToXmlDocument(IHTMLDocument2 doc2)
{
XmlDocument xmlDoc = new XmlDocument();
IHTMLDOMNode htmlNodeHTML = null;
XmlNode xmlNodeHTML = null;
try
{
htmlNodeHTML = (IHTMLDOMNode)((IHTMLDocument3)doc2).documentElement;
xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", ""/*((IHTMLDocument2)htmlDoc.DomDocument).charset*/, ""));
xmlNodeHTML = xmlDoc.CreateElement("html"); // create root node
xmlDoc.AppendChild(xmlNodeHTML);
CopyNodes(xmlDoc, xmlNodeHTML, htmlNodeHTML);
}
catch (Exception err)
{
Utils.WriteLog(err, "Html2Xml.ConvertToXmlDocument");
}
magic method:
private bool ExitWait(bool bDelay)
{
if (m_bStopped)
return true;
if (bDelay)
{
DateTime now = DateTime.Now;
DateTime later = DateTime.Now;
TimeSpan difT = (later - now);
while (difT.TotalMilliseconds < MainDef.IE_PARSER_DELAY)
{
Application.DoEvents();
System.Threading.Thread.Sleep(10);
later = DateTime.Now;
difT = later - now;
if (m_bStopped)
return true;
}
}
return m_bStopped;
}
where m_bStopped is false by default, IE_PARSER_DELAY is a timeout value.
I hope this helps.

Categories

Resources