Is there a way to determine if an element is clickable in Chrome Browser using Selenium and Watir? - ruby

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.

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.

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.

Related

Access browser print popup for testing purposes

I'm using capybara to write end to end test.
I have a print feature inside my app and would like to access its DOM in order to assert different things on my printed document.
How could I access the popup DOM using Capybara?
+How could I programmatically close the print popup when I'm done asserting? It seems that Capybara is stuck after executing the print method.
ps: I'm using an headless Chrome.
Note: This answer only applies to using the Selenium driver with non-deadless chrome, and once you're inside the shadowDOM you can only use CSS selectors which means you can't use any of the Capybara methods that involve Capybaras built-in selectors ('fill_in', 'select', etc). This may also break with any release of Chrome or chromedriver.
Because of the way chromedriver/chrome works, it isn't returning from whatever action you did to open the print window until the print window is closed. It is technically possible to work around this by triggering the print window asynchronously. But then you run into the issue that most of the print window is inside shadow DOM elements which the WebDriver spec doesn't currently provide an API for. You can however work around that using evaluate_script.
Therefore to open and then close a print window would be something like this
print_window = page.window_opened_by do
page.execute_script('window.setTimeout(function(){ window.print();}, 50)')
end
page.within_window(print_window) do
print_app = page.find('print-preview-app')
print_app_shadow = print_app.evaluate_script('this.shadowRoot')
print_header = print_app_shadow.find('print-preview-header')
print_header_shadow = print_header.evaluate_script('this.shadowRoot')
print_header_shadow.find('.cancel-button').click
end
Obviously all those calls could be chained together, the intermediate elements are only spelled out to make it clearer what is being done.

Scrolling inside a form in Watir

I am running a test on a web app using Ruby-Watir-Rspec. It is very simple since I'm a beginner.
I open a form and enter the required information, but the "Create" button is not in the visible area, so I get the message:
Failure/Error: #browser.button(class: xxx).click
Watir::Exception::UnknownObjectException:
element located, but timed out after 30 seconds, waiting for #<Watir::Button: located: true; {:class=>xxx, :tag_name=>"button"}> to be present
Caused by:
# Selenium::WebDriver::Error::ElementNotVisibleError:
# element not interactable
If I scroll while the script is running, it clicks on the button and the test is successful.
I tried scroll.to, wait_until_present, scroll to coordinates, scroll_into_view, none of them worked.
The only way to make it work was to put " #browser.send_keys :tab" several times until it reaches the button at the bottom of the form.
I believe the problem is the button being inside the form which does not take the entire page (behind the form is the map so that part of the page doesn't have the scrolling option)...so is there some way to scroll inside the form? Or do you know some other approach to finding this button? Any hint is appreciated.
Btw, the page is maximized.
Here is the code snippet, just simple:
it 'should create the place' do
#browser.button(class: xxx).click
end
My guess is that this is a custom scrollable element that hides the content with the overflow: hidden style. Elements in the overflow are not considered visible/present. When you manually scroll, you're bringing the element out of the overflow so that it's present.
I've seen a couple of these in the past. Each one needed a different approach for scrolling. Without the exact HTML/CSS, it's hard to say how to scroll the element.
However, if you're not trying to test the scrolling, you could manually fire the click event. This will bypass the visibility requirements:
#browser.button(class: xxx).click!
Try using the Watir Scroll gem: https://github.com/p0deje/watir-scroll and scrolling the element to the center of the viewport: button.scroll.to :center.
You can also submit the form directly #browser.form.submit

Can I detect if an element (button) is "clickable" in my rspecs?

Context: In my rspec (using Ruby and Capybara)
I click on a link to test an action in my app: adding a branch to my app.
A modal window opens, where I select the branch, and then I click a "submit" button to add the branch to my app. After clicking "submit" the modal window is closed.
The rspecs continues by clicking "Save" in the main screen, to save the state of the application (and effectively saving adding the branch).
Problem: The rspec is failing because (seemingly) it is trying to click the "Save" button on the main screen while the modal window that is used to select the branch is still present. The test doesn't complain that it can't find the "Save" button component, but that it can't be clicked.
The error in the log is:
[...]Save</button> is not clickable at point (692, 23). Other element would receive the click[...]
A gotcha: this rspec passes correctly on some environments, like when it is run against my local server, but it fails when it is executed by our automation server. Thus, this test has been tagged as "flaky".
Potential solutions: Things we have tried so far:
Play around our "clicks configuration", making sure we are on "ready state" and that the modal window is gone. We failed with this, since we kept hitting the same error.
Implement a "wait". We added a loop to sleep for a bit while the modal window seemed to exist
XYZ.add_new_branch_name(#branch_name)
while Utilities.element_visible?(:xpath, myElement)
sleep(0.5)
end
XYZ.save
The while condition checks if the "submit" button of the modal window exists. The element_visible function uses
find(method,element).visible?
but I'm not sure if find should already take into account that the button may exist and be visible but not be clickable.
Since this still fails, in spite of all our effort to make sure that the modal is gone before we attempt to click on the "save" button, I want tot ask:
Is there a proper way to detect if an element behind a modal window is clickable or not using rspecs?
find only cares about "visibility", not "clickability" (and different drivers may have slightly different interpretations of "visibility"). The reason for the flakiness you're seeing is most likely speed of the machine running the tests which affects the timing of the modal animating away. The best way to solve this issue is to disable animations in the test mode (how you do that is dependent on exactly what library and/or CSS you're using for the animations). The other way is to do as you're doing - checking that the modal has disappeared before clicking the 'Save' button, however you should just be using the Capybara provided methods (which include waiting/retrying behavior) rather than writing your own loop for that.
expect(page).not_to have_css('css selector of the modal') # RSpec version
assert_no_css('css selector of the modal') # minitest version
After looking at the mouse position from your error, one other potential issue you may be having is with screen size and scrolling. If the page requires to be scrolled to get to the 'Save' button and (692, 23) would put the button behind a fixed header (you should be able to verify that by taking a screenshot before the button click attempt) then it may not be possible for whatever driver you're using to click the button. In that case you'd need to use execute_script to scroll the page to a different location so the button is not covered on the page and/or increase the "browser" size so scrolling isn't necessary in the test.
I had a similar problem and solved it by writing my own click_on_with_wait helper function:
def click_on_with_wait(text, wait_time: Capybara.default_max_wait_time)
success = false
(wait_time * 10).round.times do
click_on text
success = true
break
rescue Selenium::WebDriver::Error::WebDriverError
sleep(0.1)
end
# Try clicking one last time, so that the error will get raised if it still doesn't work
click_on text unless success
end
This will try to click on the element. If it's still hidden by the modal, the function will wait 100ms and then try again, until the given wait_time is reached.
Using Rails, I put it in system_spec_helpers.rb so that I can simply replace click_on 'Submit Form' with click_on_with_wait 'Submit Form'.

selenium scroll element into (center of) view

When an element is out of view with selenium and one tries to interact with it, selenium will usually scroll the element into view first implicitly. This is great except that what is annoying is that it usually puts in the element just enough into view. What I mean is that if the element is below the window, it will scroll down enough just till when the element is just bordering the edge of the window.
Usually this is fine, but when working on a web site with borders around it, this will lead to numerous of these kinds of errors
Selenium::WebDriver::Error::UnknownError:
unknown error: Element is not clickable at point (438, 747). Other element would receive the click: <body>...</body>
Because usually the border of the web page is over it, but will try to click the element anyway. Is there anyway handle this? perhaps to automatically move elements to the center of the screen when out of view? I am thinking along the lines monkey-patching via ruby.
This should work in order to scroll element into center of view:
WebElement element = driver.findElement(By.xxx("xxxx"));
String scrollElementIntoMiddle = "var viewPortHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);"
+ "var elementTop = arguments[0].getBoundingClientRect().top;"
+ "window.scrollBy(0, elementTop-(viewPortHeight/2));";
((JavascriptExecutor) driver).executeScript(scrollElementIntoMiddle, element);
Yes, it is possible to automatically scroll the browser such that any element we interact with gets centered in the window. I have a working example below, written and tested in ruby using selenium-webdriver-2.41.0 and Firefox 28.
Full disclosure: You might have to edit parts of your code slightly to get this to work properly. Explanations follow.
Selenium::WebDriver::Mouse.class_eval do
# Since automatic centering of elements can be time-expensive, we disable
# this behavior by default and allow it to be enabled as-needed.
self.class_variable_set(:##keep_elements_centered, false)
def self.keep_elements_centered=(enable)
self.class_variable_set(:##keep_elements_centered, enable)
end
def self.keep_elements_centered
self.class_variable_get(:##keep_elements_centered)
end
# Uses javascript to attempt to scroll the desired element as close to the
# center of the window as possible. Does nothing if the element is already
# more-or-less centered.
def scroll_to_center(element)
element_scrolled_center_x = element.location_once_scrolled_into_view.x + element.size.width / 2
element_scrolled_center_y = element.location_once_scrolled_into_view.y + element.size.height / 2
window_pos = #bridge.getWindowPosition
window_size = #bridge.getWindowSize
window_center_x = window_pos[:x] + window_size[:width] / 2
window_center_y = window_pos[:y] + window_size[:height] / 2
scroll_x = element_scrolled_center_x - window_center_x
scroll_y = element_scrolled_center_y - window_center_y
return if scroll_x.abs < window_size[:width] / 4 && scroll_y.abs < window_size[:height] / 4
#bridge.executeScript("window.scrollBy(#{scroll_x}, #{scroll_y})", "");
sleep(0.5)
end
# Create a new reference to the existing function so we can re-use it.
alias_method :base_move_to, :move_to
# After Selenium does its own mouse motion and scrolling, do ours.
def move_to(element, right_by = nil, down_by = nil)
base_move_to(element, right_by, down_by)
scroll_to_center(element) if self.class.keep_elements_centered
end
end
Recommended usage:
Enable automatic centering at the start of any code segments where elements are commonly off-screen, then disable it afterward.
NOTE: This code does not seem to work with chained actions. Example:
driver.action.move_to(element).click.perform
The scrolling fix doesn't seem to update the click position. In the above example, it would click on the element's pre-scroll position, generating a mis-click.
Why move_to?
I chose move_to because most mouse-based actions make use of it, and Selenium's existing "scroll into view" behavior occurs during this step. This particular patch shouldn't work for any mouse interactions that don't call move_to at some level, nor do I expect it to work with any keyboard interactions, but a similar approach should work, in theory, if you wrap the right functions.
Why sleep?
I'm not actually sure why a sleep command is needed after scrolling via executeScript. With my particular setup, I am able to remove the sleep command and it still works. Similar examples from other developers across the 'net include sleep commands with delays ranging from 0.1 to 3 seconds. As a wild guess, I would say this is being done for cross-compatibility reasons.
What if I don't want to monkey-patch?
The ideal solution would be, as you suggested, to change Selenium's "scroll into view" behavior, but I believe this behavior is controlled by code outside of the selenium-webdriver gem. I traced the code all the way to the Bridge before the trail went cold.
For the monkey-patch averse, the scroll_to_center method works fine as a standalone method with a few substitutions, where driver is your Selenium::WebDriver::Driver instance:
driver.manage.window.position instead of
#bridge.getWindowPosition
driver.manage.window.size instead of
#bridge.getWindowSize
driver.execute_script instead of
#bridge.executeScript
The following code will scroll until the element is in view,
WebElement element = driver.findElement(By.id("id_of_element"));
((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", element);
Thread.sleep(500);
//do anything you want with the element
You could use an explicit scroll action via javascript here. In that case you would find the element (this part works already if I understand your question correctly), then scroll the window to a specified position, and then interact with the element.
In java that would be:
WebElement element = driver.findElement(By.id("tabs")).findElement(By.className("youarehere"));
Point p = element.getLocation();
((JavascriptExecutor) driver).executeScript("window.scroll(" + p.getX() + "," + (p.getY() + 200) + ");");

how to scroll with selenium

I have here a challenge that I spent some time addressing.
Selenium tells me that it cannot click a link that is not visible, so that means I need to scroll my canvas? I am using ubuntu 10, firefox 3, selenium 0.1, ruby 1.9.2, and selenium-webdriver 2.5.0
My code is
driver = Selenium::WebDriver.for :firefox;
driver.get login_url
wait = Selenium::WebDriver::Wait.new(:timeout => 2)
wait.until {
driver.find_element(:name => 'j_password')
}
driver.find_element(:name => 'j_username').send_keys(username)
driver.focus(:name => 'j_username')`
and it says that focus() is not defined. How should I modify my code to put the input element on screen?
Um, this is a hypothetical example, I really need to scroll so that some other element is on the screen, but for simplicity I would like to be able to scroll s.t. any element is on the screen, even
location_once_scrolled_into_view to scroll using ruby.
As per your question your element is not visible, so selenium web driver is unable to click on it.
Simple solution to this is:
Store the xpath of the element which is visible and nearby to your element.
Scroll till that visible element, to make your element visible.
Ensure your element is now visible, click on it
eg code:
element = dirver.find_element(:xpath, "xpath of nearby visible element")
element.location_once_scrolled_into_view
my_element = driver.find_element(:xpath, "xpath of your element")
my_element.click
I had an element at the bottom of the page with no other element close enough to select first. Finally got around it by tabbing from the last field in the form on the page.
element.send_keys(:tab)
When the documentation says "not visible" it's not refering the view port, but to the status of the element on the page. If something is hidden with CSS it cannot be clicked.
If you want to scroll on the firefox window using selenium webdriver, one of the way is to use javaScript in the java code, The javeScript code to scroll down is as follows:
JavascriptExecutor js = (JavascriptExecutor)driver;
js.executeScript("window.scrollTo(0,Math.max(document.documentElement.scrollHeight," +
"document.body.scrollHeight,document.documentElement.clientHeight));");
I'm not aware of ruby but the above code can be used as a java Script which will scroll down the whole page.You can even define the area you want to scroll in the view window by simply hard- coding the window.scrollTo(200,350);

Resources