I need to perform drag and drop action for a scenario, How can I able to achieve this using Page-Object.
I need to click an element (like button from options) and drop it in a text area. I have search for a solution but I can't able to find. Please help to resolve this issue. Thanks
There are more easy cases for Watir:
drag_and_drop_by - Drag and drop this element by the given offsets.
browser.div(:id => "draggable").drag_and_drop_by 100, -200
drag_and_drop_on - Drag and drop this element on to another element instance.
a = browser.div(:id => "draggable")
b = browser.div(:id => "droppable")
a.drag_and_drop_on b
Source: http://rubydoc.info/gems/watir-webdriver/Watir/Element#drag_and_drop_by-instance_method
Using Selenium WebDriver:
WebElement element = driver.findElement(By.name("source"));
WebElement target = driver.findElement(By.name("target"));
(new Actions(driver)).dragAndDrop(element, target).perform();
Using watir-webdriver (only works in FF(?)):
browser.div(:text=>"from_div").wd.drag_and_drop_on(browser.div(:text=>"to_div").wd)
Using HTML5 Drag and Drop Selenium WebDriver for Ruby
1) drag_and_drop_helper.js(https://gist.github.com/2362544) to your test/helpers directory
2) Create a new method:
def drag_and_drop(source,target)
js_filepath=File.dirname(__FILE__)+"/drag_and_drop_helper.js"
js_file= File.new(js_filepath,"r")
java_script=""
while (line=js_file.gets)
java_script+=line
end
js_file.close
#driver.execute_script(java_script+"$('#{source}').simulateDragDrop({ dropTarget: '#{target}'});")
rescue Exception => e
puts "ERROR :" + e.to_s
end
Hope that helps
PageObject drag and drop for Ruby
The following is a sample of 'drag and drop' using PageObject with Cucumber, Ruby, PageObject Selenium WebDriver framework, using jqueryui.com/droppable/ page:
#
# Feature file with Gherkin scenario
#
Feature: Drag and Drop test
Scenario: Perform drag-and-drop test
Given I am on drag and drop test page
When I switch to frame with demo-frame class
And I drag left element and drop it on right element
#
# Step definitions file
#
Given("I am on drag and drop test page") do
visit(HomePage)
end
When("I switch to frame with demo-frame class") do
on(HomePage) do |page|
# call switch_to.frame only in one step definition,
# as it will give an error when try to call it in the next step definition
page.browser.switch_to.frame page.browser.find_element(:class, 'demo-frame')
end
end
And("I drag left element and drop it on right element") do
on(HomePage) do |page|
from_el = page.get_from_element
to_el = page.get_to_element
# elements has to be assigned directly from Selenium WebDriver,
# if assigned from the PageObject next line will give an error
page.browser.action.click_and_hold(from_el).perform
page.browser.action.move_to(to_el).perform
page.browser.action.release.perform
sleep 10 # just to see the drag and drop result
end
end
#
# Page file
#
class HomePage
include PageObject
page_url "https://jqueryui.com/droppable/"
def get_from_element
self.browser.find_element(:id, "draggable")
end
def get_to_element
self.browser.find_element(:id, "droppable")
end
end
Starting with the comment that Justin made that this is not implemented yet, then you can monkey patch the Page-object gem in the:
module PageObject
module Elements
class Element
def drag_and_drop_on(other)
element.drag_and_drop_on other.element
end
end
end
end
This will at least perform the drag_and_drop_on method in watir-webdriver gem without a deprecation warning.
Related
I have the following script which loops through an element list:
$browser.divs(class:'menu-index-page__menu-category').map do |cat|
cate = cat.h3.text
puts cate
cc = cat.ul(class:'menu-index-page__items')
cc.lis(class:'menu-index-page__item').each do |lit|
lit.fire_event :click
sleep(5)
Once in a while the name of the class list changes to:
menu-index-page__item menu-index-page__item--unavailable
This breaks the script, and I want to be able to skip this whenever it comes up and continue with the original script.
Assuming that you are using Watir v6.5+, the :class locator supports excluding classes. You can find all elements that include class "a", but not "b" by doing:
browser.elements(class: ['a', '!b'])
For your specific example, you can do:
cc.lis(class: ['menu-index-page__item', '!menu-index-page__item--unavailable']).each do |lit|
# your actions on lit
end
When you include the lis using class, you have to be careful because when you locate using menu-index-page__item, it locates everything which includes menu-index-page__item so it locates menu-index-page__item menu-index-page__item--unavailable as well. So try forming xpath in this place, it would work
$browser.divs(class: 'menu-index-page__menu-category').map do |cat|
cate = cat.h3.text
puts cate
cc = cat.ul(class: 'menu-index-page__items')
cc.lis(xpath: "//li[#class='menu-index-page__item']").each do |lit|
lit.fire_event :click
end
end
Or you can exclude the specific list this way
$browser.divs(class:'menu-index-page__menu-category').map do |cat|
cate = cat.h3.text
puts cate
cc = cat.ul(class:'menu-index-page__items')
cc.lis(xpath: "//li[not(#class=menu-index-page__item menu-index-page__item--unavailable')]").each do |lit|
lit.fire_event :click
end
end
And also never do click via fireevent because that's the javascript click, it's not a selenium click, you would be missing many events which would be triggered after the click.
So perform
lit.click
I'm using watir-webdriver for automated testing.
We need tests drag and drop with pressed shift|control key before the drop.
I need realize:
1. click element 'a'
2. start drag
3. press shift|control
4. continue drag
5. drop to element 'b'
Can anyone help me with this?
require 'watir-webdriver'
module Selenium
module WebDriver
class ActionBuilder
def hold(timeout)
#devices.merge!(:self => self) unless #devices[:self]
#actions << [:self, :sleep, timeout]
self
end
end # ActionBuilder
end # WebDriver
end # Selenium
begin
browser = Watir::Browser.new
browser.goto my_page
a = browser.div(id: "a")
b = browser.div(id: "b")
browser.driver.action.click_and_hold(a.wd).move_to(b.wd).hold(3).key_down(:shift).key_up(:shift).release.perform
#browser.driver.action.click_and_hold(a.wd).move_to(b.wd).hold(3).send_keys(:shift).release.perform
ensure
browser.close
end
or if need drug with pressed shiift (hold)
browser.driver.action.key_down(:shift).click_and_hold(a.wd).move_to(b.wd).key_up(:shift).release.perform
Drag and drop support is in the roadmap: https://code.google.com/p/selenium/wiki/RoadMap
Per the 0.6.11 documentation, the Element class has 2 drag and drop methods: drag_and_drop_by and drag_and_drop_on.
The rdoc example for drag_and_drop_on appears to be what you're trying to accomplish:
a = browser.div(:id => "draggable")
b = browser.div(:id => "droppable")
a.drag_and_drop_on b
public class DragAndDropConcept {
public static void main(String[] args) {
WebDriver driver = new FirefoxDriver();
driver.get("http://jqueryui.com/droppable/");
driver.switchTo().frame(0);
Actions obj = new Actions(driver);
obj.clickAndHold(driver.findElement(By.xpath("//*[#id='draggable']")))
.moveToElement(driver.findElement(By.xpath("//*[#id='droppable']")))
.release().build().perform();
}
}
I am using a combination of cucumber and pageobject to test my web application. Sometimes, the script tries to click an element even before the page that contains the element starts loading. (I confirmed this by capturing the screenshots of failing scenarios)
This inconsistency is not wide-spread and it happens repeatedly only for a few elements. Instead of directly accessing those elements, if I do example_element.when_visible.click, the test suite always passes.
As of now, I click a link using link_name (generated by pageobject module on calling link(:name, identifier: {index: 0}, &block)
I would like to not edit the above mentioned snippet, but act as if i called link_name_element.when_visible.click. The reason is, the test suite is pretty large and it would be tedious to change all the occurences and I also believe that the functionality is already present and somehow I don't see it anywhere. Can anybody help me out?!
This seems solution seems quite hacky and may not be considering some edge cases. However, I will share it since there are no other answers yet.
You can add the following monkey patch assuming that you are using watir-webdriver. This would be added after you require page-object.
require 'watir-webdriver'
require 'page-object'
module PageObject
module Platforms
module WatirWebDriver
class PageObject
def find_watir_element(the_call, type, identifier, tag_name=nil)
identifier, frame_identifiers, wait = parse_identifiers(identifier, type, tag_name)
the_call, identifier = move_element_to_css_selector(the_call, identifier)
if wait
element = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{the_call}.when_present"
else
element = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{the_call}"
end
switch_to_default_content(frame_identifiers)
type.new(element, :platform => :watir_webdriver)
end
def process_watir_call(the_call, type, identifier, value=nil, tag_name=nil)
identifier, frame_identifiers, wait = parse_identifiers(identifier, type, tag_name)
the_call, identifier = move_element_to_css_selector(the_call, identifier)
if wait
modified_call = the_call.dup.insert(the_call.rindex('.'), '.when_present')
value = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{modified_call}"
else
value = #browser.instance_eval "#{nested_frames(frame_identifiers)}#{the_call}"
end
switch_to_default_content(frame_identifiers)
value
end
def parse_identifiers(identifier, element, tag_name=nil)
wait = identifier.has_key?(:wait) ? false : true
identifier.delete(:wait)
frame_identifiers = identifier.delete(:frame)
identifier = add_tagname_if_needed identifier, tag_name if tag_name
identifier = element.watir_identifier_for identifier
return identifier, frame_identifiers, wait
end
end
end
end
end
Basically, the intent of this patch is that the Watir when_present method is always called. For example, your page object call will get translated to Watir as browser.link.when_present.click. In theory, it should get called for any method called on a page object element.
Unfortunately, there is a catch. There are some situations where you probably do not want to wait for the element to become present. For example, when doing page.link_element.when_not_visible, you would not want to wait for the element to appear before checking that it does not appear. In these cases, you can force the standard behaviour of not waiting by including :wait => false in the element locator:
page.link_element(:wait => false).when_not_visible
I am trying to automate an online survey on a website but I get this error each time:
Selenium::WebDriver::Error::UnknownError: unknown error: Element is not clickable at
point (561, 864). Other element would receive the click: a id="habla_oplink_a"
class="habla_oplink_a_normal hbl_pal_header_font_size hbl_pal_title_fg "
What I need to understand is how I can scroll to a certain point of the page so that my script can resume filling out the survey on the page.
This is my code that manages to fill out a portion of the survey but fails when it reaches a row which is not in view inside the browser (a row that requires the user to scroll down to):
buttons = browser.elements(:class => "assessment-choice")
buttons.each do |button|
button.click
end
I would also like to be able to change my code so that it only selects a specific option but the HTML on the page is not very friendly.
This is the webpage I am looking at: https://staging2.clearfit.com/assessment/assessment/95867fb272df436352a0bd5fbdd
The HTML of one of the options on the survey:
<a id="answers_79_0" class="assessment-choice" onmouseover="answerOver(this)" onmouseout="answerOut(this)" onclick="setAssessmentAnswer(this, 3, '0', '79', '#answers_49839163')">Strongly<br>Agree</a>
Using execute_script
To scroll to an element, you will need to execute javascript:
browser.execute_script('arguments[0].scrollIntoView();', button)
This can be seen to be working in the following script. Without the line to scroll, a chat tab overlays one of the buttons causing an exception.
require 'watir-webdriver'
browser = Watir::Browser.new :chrome
browser.goto 'https://staging2.clearfit.com/assessment/assessment/95867fb272df436352a0bd5fbdd'
buttons = browser.elements(:class => "assessment-choice")
buttons.each do |button|
browser.execute_script('arguments[0].scrollIntoView();', button)
button.click
end
Using the watir-scroll gem
Note that you can install the watir-scroll gem to make the scrolling line nicer. The gem allows the line to simply be:
browser.scroll.to button
The script would then look like:
require 'watir-webdriver'
require 'watir-scroll'
browser = Watir::Browser.new :chrome
browser.goto 'https://staging2.clearfit.com/assessment/assessment/95867fb272df436352a0bd5fbdd'
buttons = browser.elements(:class => "assessment-choice")
buttons.each do |button|
browser.scroll.to button
button.click
end
Firstly, this should be unnecessary. According to the spec, all element interactions require implicit scrolling to the element. If something does prevent this from happening, though, you can use this Selenium method instead of a javascript implementation:
buttons = browser.elements(:class => "assessment-choice")
buttons.each do |button|
button.wd.location_once_scrolled_into_view
button.click
end
public
def scroll_to(param)
args = case param
when :top, :start
'window.scrollTo(0, 0);'
when :center
'window.scrollTo(window.outerWidth / 2, window.outerHeight / 2);'
when :bottom, :end
'window.scrollTo(0, document.body.scrollHeight);'
when Array
['window.scrollTo(arguments[0], arguments[1]);', Integer(param[0]), Integer(param[1])]
else
raise ArgumentError, "Don't know how to scroll to: #{param}!"
end
#browser.execute_script(*args)
end
public
# This method pulls the object on the page you want to interact with, then it 'jumps to it'.
def jump_to(param)
# Leveraging the scroll_to(param) logic, this grabs the cooridnates,
# and then makes them an array that is able to be located and moved to.
# This is helpful when pages are getting too long and you need to click a button
# or interact with the browser, but the page 'Cannot locate element'.
location = param.wd.location
location = location.to_a
$helper.scroll_to(location)
end
Then you just call jump_to(element) and it "Jumps" to it.
This is how I got around it- not sure if that is a normal way. The problem is it goes to point (0,0); working on a version that moves to it to center screen.
I'm trying to use the DOM inspector to find the ID of the "More" button at the bottom of this page that reveals more results.
I'm trying to do something like this example:
require 'watir-webdriver'
b = Watir::Browser.new
b.goto 'svpply.com/editors_pick'
#count products
puts b.elements(:xpath => '//li[#data-class="Product"]').count
#=> 30
#Now click button
show_all = b.button(:id => "btn_all")
show_all.click
sleep 4
#count products again
puts b.elements(:xpath => '//li[#data-class="Product"]').count
#=>60
However, I'm unclear on how to search for that particular id within the DOM structure. Can someone also explain the difference between an attribute, element, id, and node?
To use the DOM Inspector for the More button:
Open the DOM Inspector (Ctrl+Shift+I)
Click the [More] button that you want to inspect
On the right side of the DOM Inspector bar, click the [HTML] button
This should show the HTML for the page, which will include the details of the [More] control. You'll notice that the element is actually a DIV not a button. As well that the ID is in the form "_more".
--> This should show the HTML for the page, which will show the details of the [More] control. You'll notice that the element is actually a DIV not a button. As well that the ID is in the form "_more".
So to do your example with the Quora page, you would do something like:
require 'watir-webdriver'
class QuoraPage
def initialize(browser)
#browser = browser
end
def goto()
#browser.goto 'http://www.quora.com/Startups/best_questions'
wait_questions_loaded
end
def click_more()
#browser.div(:id, /_more/).click
wait_questions_loaded
end
def questions_count()
#browser.links(:class, 'question_link').count{ |x| x.visible? }
end
def wait_questions_loaded()
begin
questions_start_count = questions_count()
sleep(2)
end while questions_start_count != questions_count()
end
end
page = QuoraPage.new(Watir::Browser.new :chrome)
page.goto
puts page.questions_count
page.click_more
puts page.questions_count
Note that I had to put the sleeps in otherwise webdriver hangs like anonygoose mentioned. I tried different wait_untils, but did not manage to find something that worked (other than sleep which is not very robust).
Regarding your question about nodes, elements, etc. I think you are best to look at http://www.w3schools.com/dom/default.asp.
To press the button on svpply you can use simply
b.button(:text => "Show All").click
Counting all the products that appear on the page could potentially be done with
b.lis(:class => "grab large").count
This is all for the svpply site. I can't get quora to automate at all, it just stalls my watir-webdriver indefinitely.
You'll also want to wait before you have watir count the products. This can be done with:
b.wait_until{b.lis(:class => "grab large").count > 30}