user8465989
user8465989

Reputation:

C# and Selenium: Claims button element is not visible when it is

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:

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?

Upvotes: 3

Views: 2366

Answers (2)

JeffC
JeffC

Reputation: 25596

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));

Upvotes: 1

user8465989
user8465989

Reputation:

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.

Upvotes: 0

Related Questions