Element not interactable with C# app that uses Chrome WebDriver - c#

PREFACE: After a lengthy Stack Overflow search I found two suggested solutions to solve the "element not interactable" problem I am having when I try to interact with the target node element. Neither of them worked, as described below.
I have a C# app that uses the OpenQA.Selenium package to remote control a YouTube web page. I am trying to click on a button on the page that opens a dialog box, but when I do I get the notorious "element not interactable" message. I found the following two suggestions on Stack Overflow:
Actions actions = new Actions(chromeDriver);
actions.MoveToElement(webElem);
actions.Perform();
And this suggestion that one commenter said is ill-advised because it can click on elements that are not visible or are below modal objects:
IJavaScriptExecutor executor = (IJavaScriptExecutor)chromeDriver;
executor.ExecuteScript("arguments[0].click();", webElem);
I tried the second one anyways to see if it worked. Unfortunately, with the first suggestion that uses the Actions interface, I still got "element not interactable" message but this time on the Perform() statement. The third attempt did not get the error message but it failed to click the button. I know this because clicking the button opens a dialog window when it works, and no dialog window appeared when I tried the third solution.
Below is the code I am using to try and click on the element. The collection it iterates are the elements I select via an XPath statement that finds the button I am want to click. It tries every button that matches the XPath statement and skips those that fail to work. Unfortunately, none of the 3 buttons found by the XPath statement work.
What is strange is that if I take the exact same XPath statement I am using in my C# app and plug it into the Chrome DevTools debugger, referencing the first element in the array of found elements, it works:
$x(strXPath)[0].click()
But so far nothing I have tried from C# app works. Does anyone have an idea on why I am having this problem?
public IWebElement ClickFirstInteractable(ChromeDriver chromeDriver)
{
string errPrefix = "(ClickFirstInteractable) ";
if (this.DOM_WebElemensFound == null || this.DOM_WebElemensFound.Count() < 1)
throw new NullReferenceException(errPrefix + "The DOM_WebElementsFound collection is empty.");
IWebElement webElemClicked = null;
foreach (IWebElement webElem in this.DOM_WebElemensFound)
{
// Try and "click" it.
try
{
// First make sure the element is visible, or we will get
// the "element not interactable" error.
/* FIRST ATTEMPT, didn't work.
*
webElem.scrollIntoView(true);
webElem.Click(); // <<<<<----- Error occurs here
*/
/* SECOND ATTEMPT using Actions, didn't work
* and I go the error message when the Perform() statement executes.
Actions actions = new Actions(chromeDriver);
actions.MoveToElement(webElem);
actions.Perform(); // <<<<<----- Error occurs here
*/
/* THIRD ATTEMPT using script execution, didn't work.
* I did not get the error message, but the button did not get clicked.
*/
IJavaScriptExecutor executor = (IJavaScriptExecutor)chromeDriver;
executor.ExecuteScript("arguments[0].scrollIntoView();", webElem);
executor.ExecuteScript("arguments[0].click();", webElem);
// Click operation accepted. Stop iteration.
webElemClicked = webElem;
break;
}
catch (ElementNotInteractableException exc)
{
// Swallow this exception and go on to the next element found by the XPath expression.
System.Console.WriteLine(exc.Message);
}
}
return webElemClicked;
}

I tried to reproduce your scenario by clicking on a "hidden" button, waiting for the modal to appear, then acting on that modal, etc.
I hope it helps you!
const string Target = #"https://www.youtube.com/";
using var driver = new ChromeDriver();
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(20))
{
PollingInterval = TimeSpan.FromMilliseconds(250),
};
driver.Navigate().GoToUrl(Target);
// i don't consent cookies to
// save time, so just do it
// here manually and then press enter to console
Console.ReadLine();
var menuLocator = By.XPath("//a[#id = 'video-title-link'][1]" +
"/ancestor::div[#id = 'meta']" +
"/following-sibling::div[#id = 'menu']" +
"//button[#class = 'style-scope yt-icon-button']");
var menu = wait.Until(d => d.FindElement(menuLocator));
var actions = new Actions(driver);
actions.MoveToElement(menu).Click().Perform();
var shareLocator = By.XPath("//div[#id = 'contentWrapper']//*[normalize-space(text()) = 'Share']");
var share = wait.Until(d => d.FindElement(shareLocator));
actions.MoveToElement(share).Click().Perform();
var copyLinkLocator = By.XPath("//button[#aria-label = 'Copy']");
var copyLink = wait.Until(d => d.FindElement(copyLinkLocator));
actions.MoveToElement(copyLink).Click().Perform();

Related

Selenium - OpenQA.Selenium.ElementNotInteractableException: 'element not interactable

I'm trying to input text into a username field. It appears to find an element, however SendKeys() errors stating that the element is not interactable. I'm already waiting until the element exists, so I wouldn't think its related to waiting. Here is my code:
Console.WriteLine("Hello, World!");
ChromeDriver cd = new
ChromeDriver(#"C:\Users\xxx\Downloads\chromedriver_win32\");
cd.Url = #"https://connect.ramtrucks.com/us/en/login";
cd.Navigate();
WebDriverWait wait = new WebDriverWait(cd,TimeSpan.FromSeconds(10));
IWebElement e = wait.Until(ExpectedConditions.ElementExists(By.ClassName("analytics-login-username")));
e.SendKeys("xxx#gmail.com");
Any suggestions would be much appreciated :)
There are 2 thing you need to fix here:
You are using locator that is not unique.
You need to wait for element clickability, not just existence. I couldn't find element clickability case in C#, so element visibility can be used instead.
So, instead of
IWebElement e = wait.Until(ExpectedConditions.ElementExists(By.ClassName("analytics-login-username")));
e.SendKeys("xxx#gmail.com");
Try this:
IWebElement e = wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("input.analytics-login-username"))).SendKeys("xxx#gmail.com");

Parallel testing in Selenium with Telerik Kendo UI and screenshots subscribed to WebDriver events

The issue is related to Kendo UI's (dropdownlist, combobox, searchbox, etc) onfocusout and blur events, which close menu dropdowns when triggered, or if a browser window loses focus. In my case it was the WebDriver's GetScreenshot() method which calls an active focus on a screenshotted browser. With the parallel testing I'm taking screenshots using EventFiringWebDriver events. There are two windows of Edge/Chrome browser running in parallel and they are flashing constantly since GetScreenshot() is triggered. If Kendo UI element is opened in one of the windows, and the moment when flashing happens at the same time, it automatically triggers onfocusout and blur and dropdown closes. 40% of my tests were false negative because of that.
You can see the demo of the elements here: https://demos.telerik.com/kendo-ui/dropdownlist/index
I was able to overcome the issue with a multi-step solution. This works on Edge/Chrome 103+ and Selenium 4.1+
Firstly, I got rid of active focus calling by overring the GetScreenshot() method with a direct command to WebDriver.
public Screenshot GetScreenshot()
{
IHasCommandExecutor executor = Driver as IHasCommandExecutor;
var sessionId = ((WebDriver)Driver).SessionId; //if your driver is wrapped by any other class, you have to cast it to WebDriver first
var command = new HttpCommandInfo(HttpCommandInfo.PostCommand, $"/session/{sessionId}/chromium/send_command_and_get_result");
executor.CommandExecutor.TryAddCommand("Send", command); //try to create and add a command
var response = Send(executor, "Page.captureScreenshot", new JObject { { "format", "png" }, { "fromSurface", true } });
var base64 = ((Dictionary<string, object>)response.Value)["data"];
return new Screenshot(base64.ToString());
}
private Response Send(IHasCommandExecutor executor, string cmd, JObject args)
{
var json = new JObject { { "cmd", cmd }, { "params", args } };
var command = new Command("Send", json.ToString());
return executor.CommandExecutor.Execute(command); //execute the added command
}
Secondly, I found an extension for Chromium browsers, which disables Visibility API. There are a bunch of them, just search "Disable Visibility API" in Chrome Web Store. Apparently, it fakes 'activeness' of a browser window, so onfocusout and blur won't be firing anymore. Now you just have to include the extension when you're instantiating your driver:
var options = new EdgeOptions();
options.AddExtension("your path to extension"); //set your path
new DriverManager().SetUpDriver(new EdgeConfig(), VersionResolveStrategy.MatchingBrowser);
var service = EdgeDriverService.CreateDefaultService();
Driver = new EdgeDriver(edgeOptions);

How can i access the console logs on chrome and assert if a line exists?

Quite new to selenium, have been looking to find an answer to this question, but so far, all the times I have tried I was not able to get my desired result.
I followed other answers to access the chrome console logs but i get an exception:
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
var driver = new ChromeDriver(options);
driver.Manage().Window.Maximize();
driver.Url = "https://test.test";
var homePage = new HomePage(driver); //POM
homePage.SignIn().Click();
homePage.Email("email");
homePage.Password("pw");
homePage.LogIn();
var logs = driver.Manage().Logs.GetLog(LogType.Browser);
foreach (var log in logs)
{
Console.WriteLine(log.ToString());
}
the exception is thrown on : var logs = driver.Manage().Logs.GetLog(LogType.Browser);
System.NullReferenceException: 'Object reference not set to an instance of an object.'
I haven't been able to understand why it is thrown.
After that, i would like to assert the console logs to see if a specific entry is present. Is it possible?
So, this is a dirty workaround. If you get any good answer, please don't use mine.
Modify the default console.log method to store data in a newly introduced global variable:
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
//Multiline string used for readability. Write it in single line
js.ExecuteScript("
window.oldConsoleLog = window.console.log;
window.logCalls = [];
window.console.log = function(){
oldConsoleLog.apply(window.console, arguments);
window.logCalls.push(arguments);
}
");
Now you'll be able to get all calls using the next code:
var calls = js.ExecuteScript("return window.logCalls");
If you need a cleanup:
js.ExecuteScript("delete window.logCalls;window.console.log = window.oldConsoleLog;")

Selenium Web Driver issue in Threading. C#

Here's the code.
browser = new FirefoxDriver();
browser.Navigate().GoToUrl("https://www.vicroads.vic.gov.au/registration/buy-sell-or-transfer-a-vehicle/buy-a-vehicle/check-vehicle-registration/vehicle-registration-enquiry");
Thread.Sleep(5000);
browser.FindElement(By.Name("ph_pagebody_0$phthreecolumnmaincontent_1$panel$VehicleSearch$RegistrationNumberCar$RegistrationNumber_CtrlHolderDivShown")).SendKeys("asdf");
It works ok but if I run in thread it shows element not visible.... why it's throwing in a thread?
Element could be non-visible cuz page didnt reload at check moment or website using dynamic names, classes etc.
You can try something like this:
IWebDriver browser = new FirefoxDriver();
browser.Navigate().GoToUrl("https://www.vicroads.vic.gov.au/registration/buy-sell-or-transfer-a-vehicle/buy-a-vehicle/check-vehicle-registration/vehicle-registration-enquiry");
while ( true ) {
try {
browser.FindElement(By.Name("ph_pagebody_0$phthreecolumnmaincontent_1$panel$VehicleSearch$RegistrationNumberCar$RegistrationNumber_CtrlHolderDivShown")).SendKeys("asdf");
break;
}
catch { Thread.Sleep(1000);}
}
Going by the xpath you tried out, it seems the name attribute is dynamic. To locate the text box for Registration number You can try either of the following options :
CssSelector :
browser.FindElement(By.CssSelector("input[class=text text xlong v_registrationNumber v_required][id^=ph_pagebody_)]")).SendKeys("asdf");
XPath :
browser.FindElement(By.XPath("//input[#class='text text xlong v_registrationNumber v_required'][starts-with(#id, 'ph_pagebody_')]")).SendKeys("asdf");

Selenium c# automated test

I have made automated test with selenium c# and have a probelm. My test writes some info in form and then submits, if after submiting div that contains some info has info "Formoje yra klaidu", it must write to file email from form, but the problem is that this div is not visible when email isn't wrong and my test just stops on place where Iwebelement finds element by xpath because the element isn't visible. Here's some of the code
for (int i = 0; i < array.Length; i++)
{
IWebElement PasirinktiParkinga = driver.FindElement(By.CssSelector("#zone_16 > td:nth-child(5) > a:nth-child(1)"));
PasirinktiParkinga.Click();
IWebElement Vardas = driver.FindElement(By.Id("firstname1"));
Vardas.Clear();
Vardas.SendKeys("Vardas");
IWebElement Pavarde = driver.FindElement(By.Id("lastname1"));
Pavarde.Clear();
Pavarde.SendKeys("Pavarde");
IWebElement AutoNumeris = driver.FindElement(By.Id("vehicle_number1"));
AutoNumeris.Clear();
AutoNumeris.SendKeys("ASD123");
IWebElement Pastas = driver.FindElement(By.Id("email1"));
Pastas.Clear();
Pastas.SendKeys(array[i]);
IWebElement Taisykles = driver.FindElement(By.CssSelector("div.checks:nth-child(5) > div:nth-child(1) > label:nth-child(2)"));
Taisykles.Click();
IWebElement uzsakyti = driver.FindElement(By.CssSelector(".submit-zone > input:nth-child(1)"));
uzsakyti.Click();
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
IWebElement MessageRed = driver.FindElement(By.XPath("//*[#id='step_2']/div[3]")); //This line is were i wan't to find this div but i must write it so that if there isn't there - just do the for cicle
if (MessageRed.Text.Contains("Formoje yra klaidų."))
{
failure += array[i] + "\n";
System.IO.File.WriteAllText(#"C:\Users\jarek\Desktop\Failureemail\failure.txt", failure);
}
IWebElement unipark = driver.FindElement(By.CssSelector(".logo > a:nth-child(1)"));
unipark.Click();
i++;
}
How to make that if this element isn't there, code don't stop.
Can any body help me ???
Well, first of all don't use any Thread.Sleeps at all. Use Implicit and Explicit waits instead. Secondly, try to not use xpath (very difficult to maintain, understand it). And if you need to verify elements existance you can do it in next way e.g.
var elements = driver.FindElements(By.XPath("//*[#id='step_2']/div[3]"));
if(elements.Count() > 0)
// do everything you want
else
//continue doing smth
or you can try catch ElementNotFound exception... it's all depends.
You should check to see if the element exists, in this case check and see if the size of the element is greater than 0. This is how I could do it in Java:
if (driver.FindElement(By.XPath("//*[#id='step_2']/div[3]")).size() > 0)
{
//perform your action now
}
else
{
//perform action if the element is not present
}
I did it like this and it worked
if (driver.FindElements(By.XPath("//*[#id='step_2']/div[3]")).Count != 0)
Be carefull with FindElements, the test can be very long to execute if you have huge pages.
When I must use the FindElements to search an element, I use a FindElement that can help me to scope where I must find the researched element with FindElements. In my case, my executing time is reduced of 2 seconds everytime I use directly FindElements
Use an Implicit Wait. This allows you to enter a value in seconds that webdriver will wait for an element if it isn't found initially. This example is set for 2 seconds.
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2)
You could also use a try{} catch{}.
Also if you want to clean up your code you could write functions for finding elements and then just pass that id name into the function. It will make things a lot clearer and easier to read.
Here is my method for finding an element by ID
static void ClickElement_ByID(string elementName)
{
try
{
IWebElement test = driver.FindElement(By.Id(""+elementName+""));
Console.WriteLine("Found: "+elementName);
test.Click();
}
catch (Exception e)
{
Console.WriteLine(e);
}

Categories

Resources