3

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?

halfer
  • 19,824
  • 17
  • 99
  • 186
  • Have you tried 'waiting' for the element? Have a look at this question: https://stackoverflow.com/questions/6992993/selenium-c-sharp-webdriver-wait-until-element-is-present – bornfromanegg Nov 28 '17 at 13:53
  • Yep. I've got a method called WaitForVisbleID(). Doesn't help. I've updated the question. –  Nov 28 '17 at 14:00
  • 1
    If you are going to click the button, wait for it to be clickable instead of visible. Put a breakpoint on the line of code that is having the issue and run the script in debug mode. Once it breaks, then investigate the page. You can run code in the immediate window to test it and make sure that it works or debug why it might not. Other suggestion... don't pass around locators as strings. Instead pass them as a `By` class so that you don't have to have a method for by id, by css selector, by xpath, etc. You write one method and it works for all locators. – JeffC Nov 28 '17 at 14:55
  • Is the element by chance in an iframe? – Buster Nov 28 '17 at 18:19
  • Unfortunately, no. That would explain it, I think. However, I do see a couple of entries when I look "upwards" in the code. No sure if that's related function-wise? –  Nov 29 '17 at 12:18
  • jeffC: Unfortunately, the diagnostics tool keeps failing "unexcpetedly" no matter what I do, so real debugging is not really possible. I THINK the problem is Intellisense, but all I can see there is that it keeps claiming not to find a unit test .cs file which is 100% definitely where it says's it's not. This seems to be a Visual Studio problem/bug. –  Nov 29 '17 at 12:53
  • However, waiting for clickable times out just like waiting for visible. –  Nov 29 '17 at 13:04
  • I suspect that this has to do with the initial checkbox that you are selecting, you need to force a change event on that element, to easily debug and see if this solution works, instead of manually clicking, put a breakpoint immediatly after your testcase clicks the checkbox, and run $("#Clicked_checkbox_Locator").change(); in the browser console window (JQuery). Then resume your testcase and see if the button is now visible. – Moe Ghafari Nov 29 '17 at 14:18
  • Moe Ghafari: I'm not well known with jQuery, and I suspect that I'm not supposed to type exactly "$("#Clicked_checkbox_Locator").change();" in the browser? The checkbox doesn't have an ID - it's a checkbox placed automatically in the first cell of a table with rows. The table is populated dynamically. I've added the html code to the post (Update 4). –  Nov 30 '17 at 08:28
  • I just tried clicking the button before it was enabled (by the selection) of the checkbox. This results in the same ElementNotVisible error. So the button seems to be "not visible" to Selenium regardless of the checkbox that enables/disables it. –  Nov 30 '17 at 09:59
  • @JeffC Using a By class sounds interesting, but I'm not sure exactly how you mean? I'd be fun to get more info on that. I'm a sucker for generalizing and simplifying my code. – Frank H. Dec 01 '17 at 12:30
  • 1
    I've added an answer with some debugging tips that I use on a regular basis and some code examples. – JeffC Dec 01 '17 at 14:29

2 Answers2

1

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));
JeffC
  • 22,180
  • 5
  • 32
  • 55
  • Thanks for lots of good info. I'm wondering about the Chrome Dev tools, though: How do I search for elements like you mention in the examples? Just type it into the console? That returns nothing. Search? That gives me "No matches found", even though the window directly above shows the ID I searched for. –  Dec 05 '17 at 10:45
  • 1
    What I mean is if you want to search for your button, you would use a CSS selector for the ID like `$$("#product-catalog-page_order-selected-button")` and that should return at least one element assuming it's on the page the browser is navigated to. – JeffC Dec 05 '17 at 14:09
  • But how to do that in the most practical way? Just type ex. $$("#product-catalog-page_order-selected-button") into the console? –  Dec 05 '17 at 19:24
  • Yes... `$$()` executes CSS selectors and `$x()` does XPaths. – JeffC Dec 05 '17 at 19:29
  • Ah, thanks. For some reason, it didn't seem to work at first. Now it does, however. –  Dec 06 '17 at 10:50
0

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.