I've got a webpage with a button which Selenium keeps insisting is not visible, although it most definitely is.
The page source:
<button id="product-catalog-page_order-selected-button" class="btn btn-grey
mq-apla-button opens-overlay" data-bind="click: runOrderWizard, enable:
hasSelection"><span localize-me="">Order Selected</span></button>
<span localize-me="">Order Selected</span>
Below this, there is a dynamic table with a list of entries. The entries have a checkbox in the first cell. Before checking this box, the above button is disabled. However, immediately after clicking it the button is enabled.
I've tried accessing (clicking) this button with both the ID and the XPath. I've tried the XPath for both the <button and the <span elements. Every time I try, I get this error:
Result StackTrace:
at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response
errorResponse)
at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String
driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Remote.RemoteWebElement.Click()
at Common.Navigation.Elements.Button.ClickById(String id) in d:\Source
\Workspaces\QA\NewMySite\Common\Elements\PageElements.cs:line 51
at MyAutomation.Pages.NewOrderPage.OrderSelected() in d:\Source
\Workspaces\QA\NewMySite\MyAutomation\Pages\NewOrderPage.cs:line 36
at Tests.RegressionTests.Ordering.Ordering.User_Order_New_Hardware() in
d:\Source\Workspaces\QA\NewMySite\Tests\RegressionTests\Ordering
\Ordering.cs:line 29
Result Message:
Test method
Tests.RegressionTests.Ordering.Ordering.User_Order_New_Hardware threw
exception:
OpenQA.Selenium.ElementNotVisibleException: **element not visible**
(Session info: chrome=62.0.3202.94)
(Driver info: chromedriver=2.33.506120
(e3e53437346286c0bc2d2dc9aa4915ba81d9023f),platform=Windows NT 10.0.15063
x86_64)
I've also stopped the Selenium test immediately after the checkbox is clicked and before the button is clicked (there's a 3 sec. hard wait). Then I've manually tried to click the button, and it works fine.
There are no duplicate IDs, either; Only one element has this ID on the page.
Any ideas what I'm doing wrong here, or how to get around this if it's Selenium being dumb?
Here's the relevant program/test code:
Button.ClickById("product-catalog-page_order-selected-button"));
public class Button {
public static void ClickById (string id) {
FindById(id).Click();
}
private static IWebElement FindById (string id) {
return GCDriver.Instance.FindElement(By.Id(id));
}
}
I've also tried waiting for the element to be visible, using this code. No luck. The wait just times out.
Wait.WaitForVisibleId("product-catalog-page_order-selected-button");
public class Wait {
public static void WaitForVisibleId (string id) {
GCDriver.WaitForVisibleId(id);
}
}
public class GCDriver {
public static void WaitForVisibleId (string id) {
var wait = new WebDriverWait(GCDriver.Instance,
TimeSpan.FromSeconds(30));
wait.Until(driver => driver.FindElement(By.Id(id)).Displayed);
}
}
A while back, I had another button on this very site that I couldn't access with Selenium. That was a file upload button. I don't remember the Exception, but I solved that one with this code:
public static void ActionsUploadButtonId (string id, string filepath) {
Actions actions = new Actions(GCDriver.Instance);
actions.MoveToElement(GCDriver.Instance.FindElement(By.Id(id)));
actions.Click();
actions.Build().Perform();
}
This doesn't work here, though. It seems to do something, though: When I feed the ID of the problematic button ("product-catalog-page_order-selected-button") to that method, it selects the VERY FIRST clickable element on the page, which is the Front Page link, with its own unique ID, located several hundred lines of HTML code above the element with the actual ID.
Update
To troubleshoot, I followed a tip I found in another thread on Stack Overflow: To use Submit() instead of Clic(). However, that resulted in another (probably logical) exception:
OpenQA.Selenium.NoSuchElementException: no such element: Element was not in a form, so could not submit.
Update 2
I also tried getting the button by the class. There are two button with the exact same classes, but maybe it then would get the first one? Anyway, that results in the exact same ElementNotVisible exception.
Update 3
Both waiting for visible and waiting for clickable times out.
wait.Until(driver => driver.FindElement(By.Id(id)).Displayed);
wait.Until(ExpectedConditions.ElementToBeClickable(By.Id(id)));
Update 4
The html code for the checkbox (which enables the button when selected):
<td class="table-column-checkbox">
<input type="checkbox" data-bind="checked: $parent.selected, checkedValue: href" value="/Api/CatalogProducts/ProductOfferings/PO-6E32-CE4C-C169">
</td>
Update 5
The button is invisible to Selenium whether or not it's enabled or not:
Checkbox not selected
Button disabled
Result: OpenQA.Selenium.ElementNotVisibleException: element not visible
Checkbox selected
Button enabled
Result: OpenQA.Selenium.ElementNotVisibleException: element not visible
Update 6
Looking at the source code, there are no elements on the page set to "invisible", so that should eliminate any invisible parent element messing up.
The only thing of not I can find is that there are two blocks of HTML code with a button with the same ID that are commented out on the page. These appear before the button in question, but I wouldn't expect Selenium to bother about commented-out HTML code?
I've never found Selenium to be wrong about things like this. I would always assume that Selenium is correct and your locator is off or something is going on in the page that you aren't aware of.
The first way I find locators is using the dev console in Chrome. I personally like the Chrome dev tools the best but it's a personal preference (mostly). Use $$(locator) to test CSS selectors and $x(locator) to test XPath. Those will return a collection of elements found by those locators. You can instantly tell if you are finding more than one and if the one you want is not the first one, etc. I generally do this just by browsing to the page but if you are still having issues, put a breakpoint and stop on the offending line and then do this in the browser.
If that still doesn't work, I would add some debug commands to investigate the page before the offending line, e.g.
Console.WriteLine(Driver.FindElements(locator).Count);
and make sure the locator is finding the right elements, the expected number of elements are found, etc.
You can also put a breakpoint on the line that is causing the problem, run the script in debug mode, and then use the Immediate window to test code.
Other comments
I mentioned in a comment that you should pass By classes around instead of strings and you asked for an example.
public class Button
{
public static void Click(By locator)
{
Find(locator).Click();
}
private static IWebElement Find(By locator)
{
return GCDriver.Instance.FindElement(locator);
}
}
and you would call it like
Button.Click(By.Id("id"));
Your WaitForVisible() method is more complicated than it needs to be. You should read up on ExpectedConditions. There are a lot of already available things to wait for that will take care of 90+% of the waits you will need. I would also pass in a TimeSpan so that you can have control over the wait time instead of always having to wait a hardcoded 30s.
public class GCDriver
{
public static void WaitForVisible(By locator, TimeSpan timeout)
{
new WebDriverWait(GCDriver.Instance, timeout).Until(ExpectedConditions.ElementIsVisible(locator));
}
}
You could even go one step further and pass in a Func that will allow you to wait for anything, not just visible.
public class GCDriver
{
public static void Wait(Func<IWebDriver, IWebElement> func, TimeSpan timeout)
{
new WebDriverWait(GCDriver.Instance, timeout).Until(func);
}
}
and call it like
GCDriver.Wait(ExpectedConditions.ElementIsVisible(locator), TimeSpan.FromSeconds(10));
After hours of troubleshooting I was able to found the cause. Or, at least, part of it.
When I used FindElements instead of FindElement, it counted TWO elements with the same ID. These were not visible when I took a snapshot of the page source at the time of the button click.
However, the reason for this is that the page containing the button has 3 tabs. The button should be the same for all tabs, since it's an "Order Selected" button and the tabs contain lists of things to order. But since unique IDs have only recently been implemented, this page wasn't created with that in mind.
So, the DOM contains at least 2 identical IDs which are not visible in the page source. The strange thing though, is that FindElements should have returned 3 elements and not 2. Or possibly the DOM is even more messy.
Anyway: When I told Selenium to click the SECOND element, like this, it
GCDriver.Instance.FindElements(By.Id(id))[1].Click();
it worked.
Related
I am having a really hard time with the following issue. I am trying to navigate through some web pages that have various inputs (text boxes/dropdowns/buttons) followed by a continue button at the bottom to move onto the next screen. My tests frequently fall over because they can't always locate the first element in order to interact with it.
As far as I'm aware the page doesn't do any fancy AJAX post loading or anything, so once the page has loaded then Webdriver should be able to locate every element.
My code:
FRAMEWORK
public static void WaitOnPageForXPathClickable(string selector)
{
new WebDriverWait(Driver.Instance, TimeSpan.FromSeconds(20)).Until(ExpectedConditions.ElementToBeClickable((By.XPath(selector))));
}
TEST CASE
Utilities.WaitOnPageForXPathClickable("the xpath of the continue button and checks that it is clickable");
Driver.Instance.FindElement(By.XPath("the xpath of the first button that I want to click")).Click();
Do I need to try and include a function to ensure that the page is fully loaded before test execution? I am confused about this because I have read that Selenium already waits for the page to load by default. I would have thought that waiting for one element (the continue button) to be clickable should mean that all other elements are ready by then too? I really want to avoid having to wait for every single element before clicking it.
Can you please share html, have you tried with Visible instead of Clickable
public static void WaitOnPageForXPathClickable(string selector)
{
new WebDriverWait(Driver.Instance, TimeSpan.FromSeconds(20)).Until(ExpectedConditions.ElementIsVisible((By.XPath(selector))));
}
You can wait for the page to be fully loaded with something like:
public void WaitForPageToBeFullyLoaded(int timeSpan)
{
new WebDriverWait(Driver, TimeSpan.FromSeconds(timeSpan)).Until(
d => ((IJavaScriptExecutor) d).ExecuteScript("return document.readyState").Equals("complete"));
}
You can check for displayed or Enabled element too.
You have made things much more complicated by waiting for the Continue Button Utilities.WaitOnPageForXPathClickable("the xpath of the continue button and checks that it is clickable"); where as in the next step trying to click() on the First Button Driver.Instance.FindElement(By.XPath("the xpath of the first button that I want to click")).Click();
Test Scenario:
Ideally your Test Scenario should have been :
TEST CASE:
Utilities.WaitOnPageForXPathClickable("the xpath of the first button that I want to click");
Driver.Instance.FindElement(By.XPath("the xpath of the first button that I want to click")).Click();
FRAMEWORK:
public static void WaitOnPageForXPathClickable(string selector)
{
IWebElement element = new WebDriverWait(Driver.Instance, TimeSpan.FromSeconds(20)).Until(ExpectedConditions.ElementToBeClickable((By.XPath(//label[#class='replaced-input-label replaced-input-label--radio']))));
elementClick();
}
I've got a page with both text, text fields, normal form buttons and a file upload button (which opens a file upload dialog).
All the other elements can be accessed, except the file upload button. I've tried with both .Click() and JavaScriptExecutor, but as far as I can see (visually) the file dialog never opens. There are no error messages, though.
The page source:
<a class="bttngrey file-input-container bttn-small" data-bind="enable: !uploading() " style="margin-top: 10px; ... data-original-title="Add attachment">
<i class="fa fa-cloud-upload">...</i>
<input type="file" data-bind="upload: addAttachments, enable: !uploading()"> == $9
C#/Selenium code to click the button:
NB: I'm using a Button class and a JavaScriptActions class to handle the call to the ChromeDriver instance, instead of calling it directly. I hope the code snippets make sense.
Button.FindByXPath("/html/body/div[1]/div[2]/overlay--master/div/div/overlay-lightbox/div/div[3]/content-placeholder/a").Click();
JavaScriptActions.ButtonClickXPath("/html/body/div[1]/div[2]/overlay--master/div/div/overlay-lightbox/div/div[3]/content-placeholder/a");
public class Button {
public static IWebElement FindByXPath (string bttnxpath) {
return GCDriver.Instance.FindElement(By.XPath(bttnxpath));
}
}
public class JavascriptActions {
public static void ButtonClickXPath (string xpath) {
GCDriver.JSClickXPath(xpath);
}
}
public class GCDriver {
....
....
....
public static void JSClickXPath (string xpath) {
IWebElement icon = Instance.FindElement(By.XPath(xpath));
Actions ob = new Actions(Instance);
ob.Click(icon);
IAction action = ob.Build();
action.Perform();
}
....
....
....
}
None of the methods for clicking the button seems to work. Even though they work for other "normal" buttons on the page. The reason I'm trying JavaScriptExecutor is that I've experience a case earlier where a button like this (that opens a file dialog) wasn't clickable by the normal Selenium method, but it was with JavaScriptExecutor.
The strange thing here is that both methods just finishes without any errors. However, when I'm watching the page, I see no clicking and opening of the dialog. The test just finishes immediately. Adding an explicit wait before and after clicking the button doesn't help either.
Any ideas? I'll elaborate more if necessary; It's not always clear how much code needs to be shown for the question/problem to be clear.
Please give below code try, using similar code in my project:
IWebElement element = new WebDriverWait(_browserWindow, TimeSpan.FromSeconds(20)).Until(ExpectedConditions.ElementToBeClickable(By.CssSelector("[class='fa fa-cloud-upload']")));
//Send complete file path to attach it
element.SendKeys("File Path");
Also, as per posted HTML code, upload button contain composite class name. So refer this for composite class element locator:
Compound class names are not supported. Consider searching for one class name and filtering the results
Try this: (using a driver class instance)
Actions actions = new Actions(Driver.Instance);
actions.MoveToElement(Driver.Instance.FindElement(By.XPath(xpath)));
actions.Click();
actions.Build().Perform();
Just create a method in the driver class and call it from the relevant place in your code.
You can possible use SendKeys to send the file path, but the way I usually do it is to use the AutoIt library to handle the actions on the window, since it's not a browser/web element.
I am using the following back-end c# code to see if I need to update the text inside a span.
c#:
if (status1.InnerHtml != temp1)
{
status1.InnerHtml = temp1;
status1.Update();
}
html:
<span runat="server" id="status1">Status 1</span>
This works fine in chrome and firefox, but it has issues in ie.
Visually, this is what happens (only in ie):
first time:
second time:
Possible Source of Error
I noticed that status1.InnerHtml always returns Status 1; as in it never changes. This leads me to believe that this is why it is creating a second element.
This means that I need to find a way to get the the current value of the span, using something besides InnerHtml (runat="server" was supposed to solve this issue).
Looking at the code in ie, on initial load, it is displayed properly. However, the second time I execute the code,
it turns
<ext.net.direct.update id="status1"/>
<span id="status1">
Text - Transfer completed
</ext.net.direct.update/>
(Note: the closing span tag was removed)
into
<span id="el_status1_container">
<span id="status1">
Text - Transfer completed
<span id="status1">
Text - Transfer completed
</ext.net.direct.update/>
(Note: <ext.net.direct.update id="status1"/> gets removed from the code, an element with a duplicate ID is inserted)
Any pointers would be greatly appreciated! Thank you
The solution I came up with was to make a button that when clicked loads the statuses. Using javascript, I clicked the button when it is generated with this code found from this post.
function initialload() {
if (document.getElementById('Label1')) {
document.getElementById("Label1").click();
} else {
setTimeout(initialload, 15);
}
This works because it turns out ie was ignoring the initial span (weird, but hey, it's ie) and using the second one to update it.
This is a workaround, so if anyone has a better answer, please share!
I have a set of common methods which I run for each test. Like I need to add a few items in my cart and the whole test runs for each item. But for the first item it runs fine and when the process is repeated for the second item it fails while verifying a text and I get a StaleElementReferenceException.
How do I look up the item again or solve this ? Thanks.
Code where it fails:
public bool VerifyItemPresentInCart()
{
//Get the cartsize and verify if one item present
IWebElement cartSize = driver.FindElement(By.CssSelector("div[class='cart-size']>div"));
string actualMsg = cartSize.Text;
string expectedMsg = "1";
VerifyIfTextPresentMethod(expectedMsg,actualMsg);
return true;
}
Error at
IWebElement cartSize = driver.FindElement(By.CssSelector("div[class='cart-size']>div"));
Update: html code
<a class="header-button show-cart has-cart-items" data-view-name="cart-badge" data-view-cid="view5" data-model-cid="c6" data-tappable="true">
Cart
<div class="cart-size">
<div>3</div>
</div>
New code:
IWebElement cardDetails = driver.FindElement(By.CssSelector("div[class='form-field clear-fix']>label[for='cardNumber']>div"));
I would try joining your lines of code:
IWebElement cartSize = driver.FindElement(By.CssSelector("div[class='cart-size']>div"));
string actualMsg = cartSize.Text;
So that they are:
string actualMsg = driver.FindElement(By.CssSelector("div[class='cart-size']>div")).Text;
This means that the text will be retrieved on the element as it is selected. I am wondering if between getting a handle on the element via its parent and retrieving the text you are losing focus on that element. Alternatively, remove the >div from your css and see if it retrieves the text anyway.
This not working suggests the situation you are facing is the point under the title The Element is not Attached to the DOM. The fact that your target text is within only divs suggests that this area is being styled by javascript and thus may only be active at certain times. If this element is not active, but is accessible, you can still receive the StaleElementReferenceException, as indicated at that page. Your next step would be to see if you can click a parenting div to activate this target div prior to accessing its text (eg. make sure the element is attached to the DOM then call the code you have provided).
I have a form with a browser control. (this control uses IE9 because I set values on registry editor)
This web browser navigates to a specific URL and fills all fields on HTML page and submit them, then result page is displayed.
My problem is that i just want to know when this reslut page is fully loaded or completed so that i can fetch some information.
I use WebBroswer_DocumentCompleted event which works fine for the first page but not for the result page as it triggers before result
page is loaded.
I tried other solution which is to check the div tag inside the result page (this tag only appears when result page is loaded completely) and it works but not always.
My code:
private void WebBroswer_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
HtmlElementCollection elc3 = this.BotBrowser.Document.GetElementsByTagName("div");
foreach (HtmlElement el in elc3)
{
if (el.GetAttribute("id").Equals("Summary_Views")) //this determine i am at the result page
{
// fetch the result
}
}}
That div id is "Summary_Views".
I can provide you the link of that website on demand which is just for BLAST tools and database website for research purpose.
Frames and IFrames will cause this event to fire multiple times. Check out this answer:
HTML - How do I know when all frames are loaded?
Or this answer:
How to use WebBrowser control DocumentCompleted event in C#?
Or ms's kb article:
http://support.microsoft.com/kb/180366
Do you know if there are frames? If so then please say so, so people can help with that. If not then say so, so people can offer alternatives.
My guess is that the content is being generated by JavaScript. If it is then the document is complete before the JavaScript executes and you need to somehow wait until the JavaScript is done. The solution depends upon the web page. So you might need to process multiple document completes for diagnostic purposes and attempt to determine if there is a way to know which one you need.
At last i have solved my problem. I put a timer control from toolbox and set its time interval to 200ms and its Autoreset property to false. I set a tick event which has a code to check every 200ms whether this Div has been loaded or not, after that, Autoreset property is set to true.This solution is working perfectly :)