3

As I understand it, this question is really only relevant when performing web test automation using Chrome browser due to the manner in which a click event is implemented with Selenium Webdriver and Chromedriver. To preface, I use and am aware of finding an element through the use of the Watir element function "present?", which as I understand is essentially a combination of "visible?" and "exists?". I could also, at need find an element with Webdriver element functions to identify if an element is present with a rescue for an exception if it is not. What I am trying to determine is the following:

At times, due to a lack of responsiveness to a page, there will be page elements that will be found and pass all validation tests for it's existence, but cannot actively actually be interacted with due to the aforementioned lack of page responsiveness. Using Chrome browser (with Chromedriver) attempts to interact with these elements will result in the error:

irb(main):003:0> @browser.button(:id, "button_login").present? => true irb(main):004:0> @browser.button(:id, "button_login").click Selenium::WebDriver::Error::UnknownError: unknown error: Element ... is not clickable at point (915, nt would receive the click:

...
(Session info: chrome=66.0.3359.181) (Driver info: chromedriver=2.38.552522 (437e6fbedfa8762dec75e2c5b3ddb86763dc9dcb),platform=Windows NT 6.3.9600 x86_64) from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/response.rb:69:in 'assert_ok' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/response.rb:32:in 'initialize' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/common.rb:83:in 'new' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/common.rb:83:in 'create_response' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/default.rb:107:in 'request' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/common.rb:61:in 'call' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/bridge.rb:170:in 'execute' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/oss/bridge.rb:579:in 'execute' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/oss/bridge.rb:328:in 'click_element' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/common/element.rb:74:in 'click' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/watir-6.4.1/lib/watir/elements/element.rb:131:in 'block in click' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/watir-6.4.1/lib/watir/elements/element.rb:656:in 'element_call' from C:/Ruby23/lib/ruby/gems/2.3.0/gems/watir-6.4.1/lib/watir/elements/element.rb:122:in 'click' from (irb):4 from C:/Ruby23/bin/irb.cmd:19:in ''

I know I can rescue at this point, but that entails that I actually click the element. Essentially I want to write a special function "clickable?" that will return a boolean output without actually clicking the element and possibly navigating away from the page. I would prefer not to attempt this with a type of --ctrl+click, if new window return true, close window, set focus on first window, rescue return false-- workflow.

JeffC
  • 22,180
  • 5
  • 32
  • 55
user3224193
  • 31
  • 1
  • 2

2 Answers2

3

Watir 6.15.0+

Element#obscured? has been added to check for this scenario. You can now do:

browser.element(id: 'target').wait_while(&:obscured?).click

Watir pre-6.15.0

For older versions, you will need to take a different approach.

I would try waiting for the overlapping element to go away. If the overlapping is something like an overlay that will eventually disappear, it's relatively straightforward - eg:

browser.element(id: 'overlapping_element').wait_while(&:present?)

If the overlapping element gets moved rather than disappears or you don't know the overlapping element, you could try approximating the overlapping element check. When Chrome clicks an element, it gets the element's center location and then clicks at that point. If there the top-level element at that point is not your element, the exception is thrown. The following wait will do this check until there is no overlapping element:

target = browser.button
target_children = target.elements.to_a
browser.wait_until do
  location = target.location
  size = target.size
  center = [location.x + size.width/2, location.y + size.height/2]
  element_at_point = browser.execute_script("return document.elementFromPoint(#{center[0]}, #{center[1]});")
  [target, target_children].flatten.include?(element_at_point)
end
target.click

Note that I haven't had to do this before, so I don't know if there are edge cases. Seemed to work with Chrome and Firefox.

Justin Ko
  • 46,526
  • 5
  • 91
  • 101
  • Buts it's available in Java selenium binding, why it's not available for Ruby Selenium Binding? – Rajagopalan Jun 01 '18 at 15:08
  • 1
    @Rajagopalan, sorry, I don't know much about the Java binding. A quick search suggests that [elementToBeClickable](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html#elementToBeClickable) just checks that the element is visible and enabled. I wouldn't expect that to be sufficient in this case and is also suggested in [another answer](https://stackoverflow.com/a/46315984/1200545). – Justin Ko Jun 01 '18 at 15:23
  • I can tell you that in the Java bindings this would fail in the same way. You would have to write a method that does #2. It would be pretty hard to do #1 for all elements. – JeffC Jun 01 '18 at 15:40
  • @JeffC, checking to see if the element is overlapped was simpler than expected (see updated answer). Well at least finding a solution that worked in a simple case. Do you know if there would be edge cases around this approach? – Justin Ko Jun 01 '18 at 15:46
  • Right... finding if two specific elements overlap is straightforward with location and dimensions. I was talking more about a generic method to see if any elements overlap the element I'm about to click. – JeffC Jun 01 '18 at 16:32
  • I'm unfamiliar with ruby/watir... what does `target.elements.to_a` return? – JeffC Jun 01 '18 at 16:32
  • I think your former #2 is the easiest to implement... attempt a click and eat those exceptions until it succeeds or times out. – JeffC Jun 01 '18 at 16:38
  • @JeffC, `target.elements.to_a` would return an Array containing all child elements of the target element. If the target element has children, the element at the point being clicked may be one of the children. – Justin Ko Jun 01 '18 at 16:38
  • @JeffC, yes, rescuing the exception would perhaps be the safest. But the asker was looking for an alternative. – Justin Ko Jun 01 '18 at 16:41
  • In my experience, the elements that are blocking are usually dialog or some other popup/banner thing that is unlikely to be a child of the element you are trying to click. I'm not sure what you would do at this point... maybe create a list of known popups/banners/spinners and check against those? It doesn't seem like the best method. – JeffC Jun 01 '18 at 17:24
  • 1
    @JeffC, the `document.elementFromPoint` is supposed to get the element on top. If the element on top is not the target element or one of its children, then the target element will not be clickable. – Justin Ko Jun 01 '18 at 17:41
  • I'm not positive but I think you can change the z-index of any element so one of the child elements could have a higher z-index and block the target (parent) element still. https://developer.mozilla.org/en-US/docs/Web/CSS/z-index. I'm not sure how common this is so maybe this would be an edge case. – JeffC Jun 01 '18 at 18:30
  • Actually... isn't it a fact that if there is ANY element on top of the target element that the target element will not be clickable? If it's on top, by definition it would block the click, right? Or am I missing something? – JeffC Jun 01 '18 at 18:31
  • @JeffC, I'm not sure. I added the children for situations like ``. If you did `browser.button.click`, Chrome treats the span as being on top, but still treats the click as valid. – Justin Ko Jun 01 '18 at 19:00
  • Okay, I understand. Such a awesome explanation. Upvoted the answer. – Rajagopalan Jun 02 '18 at 05:23
  • For the record, I requested at the last w3c TPAC that an endpoint for this be added to the next version of the spec. So at some point we'll be able to query whether or not something is "interactable" before attempting to interact with it. Watir still might not rescue this exception by default, but there will be more options for end users to deal with it once the spec is updated and implemented. – titusfortner Jun 21 '18 at 18:21
  • @JeffC JustinKo has given the link here for another answer that proves my point https://stackoverflow.com/questions/38327049/check-if-element-is-clickable-in-selenium-java/46315984#46315984 – Rajagopalan Aug 02 '18 at 17:03
  • @Rajagopalan That doesn't prove your point. As I said before, you are oversimplifying the situation. There is no one size fits all solution to this problem that will work in all cases so Selenium doesn't attempt anything other than a simple check. If you can solve this complex issue in a general way, please go submit a pull request on the Selenium project with your fix. – JeffC Aug 02 '18 at 18:38
1

I can suggest you to wait this button to show on the web page. I experienced the same problem (I was using XPath in my tests). To fix it:

Firstly I defined 2 helper methods because I had to reuse them a lot. One for searching of a exact element on the page(this method is usually takes a while to return a result so you don't need to sleep the browser) and one for clicking a button with given "id".

module Helpers
  module Common
    def wait_for_element_id(value)
      find(:xpath, "(//*[@id='#{value}'])[1]")
    end

    def click_button_with_id(value)
      first(:xpath, "//button[@id='#{value}']").click
    end
  end
end

After that in your test you can use the helper methods like:

it 'clicks on the login button and magic is executed' do
  logout(user)
  wait_for_element_id('button_login')
  click_button_with_id('button_login')

  expect(magic).to be_executed
end

I am also not sure but you can also experience the same problem because of the browser window size (button is not shown because the size is too low) or because of the "headless" mode of your tests.

radoAngelov
  • 684
  • 5
  • 12