WATIR: Is there a reason why browser scrolling won't work? - ruby

I'm trying to use browser.scroll.to :bottom but nothing happens.
I know that it works because I've tried it on public facing sites such as the BBC and Wikipedia, but for some reason, these scroll commands don't work on our in-house browser based app.
Does anyone know of any reasons or settings that could be preventing this from happening?
Things such as browser.refresh and browser.window.maximize work fine but the scrolling literally refuses to budge.
Here's what my code looks like:
require 'watir'
require_relative 'regression_config_bob01.rb'
require 'date'
require 'faker'
require 'slack-notifier'
require 'watir-scroll'
user_name = "blah"
password = "blah"
test_env = "the Site"
browser = Watir::Browser.new
browser.goto(test_env)
# Login
browser.text_field(:name => 'P101_USERNAME').set user_name
browser.text_field(:tabindex=> '2').set password
browser.link(:text => "Log in").click
sleep 20
browser.wd.action.scroll_by(0, 1000).perform # Suggested Scroll line
print "done"
sleep 30
temporary_exp.rb:62:in <main>': undefined method scroll_by' for #<Selenium::WebDriver::W3CActionBuilder:0x000000014ac54bd0 #bridge=#<Selenium::WebDriver::Remote::W3C::Bridge:0x000000014ac1e440 #capabilities=#<Selenium::WebDriver::Remote::W3C::Capabilities:0x000000014ac1e940 #capabilities={:proxy=>nil, :browser_name=>"chrome", :browser_version=>"106.0.5249.119", :platform_name=>"mac os x", :accept_insecure_certs=>false, :page_load_strategy=>"normal", :implicit_timeout=>0, :page_load_timeout=>300000, :script_timeout=>30000, :remote_session_id=>nil, :accessibility_checks=>nil, :profile=>nil, :rotatable=>nil, :device=>nil, "chrome"=>{"chromedriverVersion"=>"105.0.5195.52 (412c95e518836d8a7d97250d62b29c2ae6a26a85-refs/branch-heads/5195#{#853})", "userDataDir"=>"/var/folders/hb/h_0h2t79183fkkppsr6r19nc0000gn/T/.com.google.Chrome.BV85VD"}, "goog:chromeOptions"=>{"debuggerAddress"=>"localhost:50818"}, "networkConnectionEnabled"=>false, "setWindowRect"=>true, "strictFileInteractability"=>false, "unhandledPromptBehavior"=>"dismiss and notify", "webauthn:extension:credBlob"=>true, "webauthn:extension:largeBlob"=>true, "webauthn:virtualAuthenticators"=>true}>, #session_id="8c59128dd44054681f62819d2fed25cf", #http=#<Watir::HttpClient:0x000000014ab6e298 #open_timeout=60, #read_timeout=nil, #server_url=#<URI::HTTP http://127.0.0.1:9515/>, #proxy=nil, #http=#<Net::HTTP 127.0.0.1:9515 open=true>>, #file_detector=nil, #escaper=#URI::RFC2396_Parser:0x000000014b0cbbc8, #manage=#<Selenium::WebDriver::W3CManager:0x000000014ab640b8 #bridge=#<Selenium::WebDriver::Remote::W3C::Bridge:0x000000014ac1e440 ...>, #window=#<Selenium::WebDriver::Window:0x000000014aa17ca0 #bridge=#<Selenium::WebDriver::Remote::W3C::Bridge:0x000000014ac1e440 ...>>>>, #devices=[#<Selenium::WebDriver::Interactions::PointerInput:0x000000014ac54f40 #name="mouse", #actions=[], #kind=:mouse>, #<Selenium::WebDriver::Interactions::KeyInput:0x000000014ac54d38 #name="keyboard", #actions=[]>], #async=false> (NoMethodError)

The current scroll implementation is using javascript to move the page around. For some reason your app is not responding to the JS commands in the normal way.
Selenium has recently released native scrolling via the drivers that Watir has not yet been updated. Take a look at the Selenium documentation on it: https://www.selenium.dev/documentation/webdriver/actions_api/wheel/?language=ruby
To use this in watir, you just need to add the line to the top: driver = browser.wd
So to scroll down by 2000 pixels, you can do:
driver = browser.wd
driver.action
.scroll_by(0, 2000)
.perform

like Titus said, Watir is a wrapper for Selenium and lets you write shorter and prettier code, like
browser.scroll.to :bottom
browser.scroll.to :top
browser.element(id: "asd").scroll.to
but when (for any reason) it doesn't work you can use Selenium syntax directly by sending commands to browser.wd instead browser like this:
browser.wd.action.scroll_by(0, 1000).perform #this scrolls bottom
browser.wd.action.scroll_by(0, -1000).perform #this scrolls top
browser.wd.action.scroll_to(browser.wd.find_element(id: "asd")).perform
but instead of writing browser.wd every time you need to talk to driver directly, you can type driver = browser.wd so your code is more clear:
driver = browser.wd
driver.action.scroll_by(0, 1000).perform #this scrolls bottom
driver.action.scroll_by(0, -1000).perform #this scrolls top
some_element = driver.find_element(id: "asd")
driver.action.scroll_to(some_element).perform
There are countless examples where using Watir is more efficient than talking to driver directly, you just happened to encounter an edge case.
Also try avoiding sleep when possible, instead, wait for something to show, like:
browser.element(id: 'asd').wait_until(&:present?)
browser.element(id: 'asd').wait_until({ |el| el.text == "Success" })
some_element = browser.element(id: 'asd')
browser.wait_until(30){some_element.present?}
Instead of .element you can use correct subclass like .div, .button, .span etc

Related

Ruby interface Shoes and auto testing tool Watir

I installed and used Watir to do auto testing for my web pages successfully. After this, I tried to create a Shoes interface for my testing code. I want to click on a button and then run my Watir testing code.
My testing code works if I run it in terminal. However, it stops at the step "require 'watir-webdriver'" if I involve Shoes. So, I can see the alerted 1 and 2, but never 3 and nothing after. My code is here:
Shoes.app do
def xxxx(from, to)
alert "1"
puts "my message abcdefg"
alert "2"
require 'watir-webdriver'
alert "3"
browser = Watir::Browser.new
browser.goto 'http://my.page.url.......'
alert "4"
# login
browser.link(:text => 'Login').click
browser.text_field(:id => 'username').set 'xxxx'
browser.text_field(:id => 'password').set 'yyyy'
browser.button(:text => 'Login').click
# some other staff... nothing wrong here
browser.close()
end
# build the interface
#s = stack :width=>200, do
username = edit_line
password = edit_line
button "Login" do
xxxx(1, 2) # just call the function
end
end
#left=(#s.parent.width-#s.style[:width])/2
#s.move(#left,0)
end
Am I using Shoes wrong? I don't get any error at all, but it just stopped. What other interface would you suggest? I need an interface to let the user load a txt file and then perform the testing based on the information in the file.
Thanks a lot.
Well, shoes won't load watir-webdriver and shoes4 has this issue 'Unable to activate selenium-webdriver-2.42.0, because rubyzip-0.9.9 conflicts with rubyzip (~> 1.0)'...
No solution so far...

Capybara with PhantomJS / Poltergeist: Selecting value from drop-down doesn't trigger Ajax call

For non-commercial purposes I am trying to build a scraper. I have worked with PhantomJS before, and it usually works like a charm. However, I have stumbled upon the following problem:
What I want to do:
Go to http://de.soccerway.com/national/germany/bundesliga/2010-2011/regular-season/r11840/
Click on the button 'per Spieltag'
Then select an option from the drop-down menu that holds the matchdays
Wait for Ajax call to be complete and then get the information I need
Unfortunately, I can select the option I want to select, but nothing happens afterwards. I created a mini script to demonstrate this behavior:
require 'capybara/poltergeist'
require 'capybara/dsl'
include Capybara::DSL
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, :js_errors => false)
end
Capybara.default_driver = :poltergeist
Capybara.javascript_driver = :poltergeist
Capybara.default_wait_time = 20
Capybara.ignore_hidden_elements = true
Capybara.current_session.driver.resize(1200, 1000)
visit('http://de.soccerway.com/national/germany/bundesliga/2010-2011/regular-season/r11840/')
# click on button 'per Spieltag'
find(:xpath, ".//a[text() = 'per Spieltag']").click
# drop down menu with matchdays
matchday_selector = find(:xpath, ".//select[#name='page']")
# save screenshot 'before'
save_screenshot('before.png', full: true)
# for demonstration purposes, select option with value '5'
matchday_selector.find(:xpath, ".//option[#value='5']").select_option
# wait some time for ajax call to be complete and save screenshot 'after'
sleep 5
save_screenshot('after.png', full: true)
If you compare the before and after screenshots, you will see that the number of the drop-down menu has changed, but the content of the table didn't. Also, the dropdown menus are not displayed at the correct position, but instead are shown in the top left corner of the screenshot.
If I use Selenium as my default driver, then it works, but I'd rather user PhantomJS because it is much faster in my experience.
How can I get PhantomJS to trigger the Ajax call?
I use PhantomJS 1.9.1, Capybara 2.1.0 and Poltergeist 1.3.0

Clicking group of links in watir

I am new to ruby, and I am trying to work with watir. I think I got the basics, but I am having trouble clicking all links whose id matches a regex. I tried this;
require "watir-webdriver"
browser = Watir::Browser.new :ff
browser.goto "http://mysite.com"
browser.links(:id, /asd[0-7]/).each do |adv|
adv.click
sleep 1
end
But it doesn't seem to be clicking the links. I am doing something wrong here? Links are opening in new windows, so looping through them is no problem. But I couldn't make the loop work.
This kind of investigation is better in IRB. Anyway, you should validate that you have links to click.
require "watir-webdriver"
browser = Watir::Browser.new :ff
browser.goto "https://rvm.io/"
links = browser.links(:href => /gemsets/)
links.count
I changed mine up to use a site I can access and has links.

How do you get window titles, ids, and names in selenium-webdriver?

Im trying to implement the following methods from selenium-webdriver (ruby)
get_all_window_ids
get_all_window_titles
get_all_window_names
I ran Selenium IDE and exported my script to Ruby Test::Unit. Saved it as .rb
Opened my script for editing using Aptana Studio 3
Initial code snippet as follows:
require "rubygems"
require "selenium-webdriver"
require "test/unit"
class SwitchToPopup3 < Test::Unit::TestCase
def setup
#driver = Selenium::WebDriver.for :firefox
#base_url = (URL of my test website)
#driver.manage.timeouts.implicit_wait = 30
#verification_errors = []
end
def teardown
#driver.quit
assert_equal [], #verification_errors
end
def test_switch_to_popup3
.
.
puts #driver.get_all_window_ids()
puts #driver.get_all_window_titles()
puts #driver.get_all_window_names()
.
.
end
The error I keep getting is
NoMethodError: undefined method `get_all_window_ids' for # <Selenium::WebDriver::Driver:0x101e4b040 browser=:chrome>
/Users/rsucgang/Documents/Aptana Studio 3 Workspace/Testing/SwitchToPopup2.rb:37:in `test_switch_to_popup3'
I've studied the documentation for the ruby bindings for selenium-webdriver
http://selenium.googlecode.com/svn/trunk/docs/api/rb/Selenium/Client/GeneratedDriver.html#get_all_window_titles-instance_method
Ultimately my goal here is to run my automation script:
Click on a link that opens a new window with target=_blank and no windowID available (does not implement JS)
Identify the names of all opened windows in the browser
switch to a new pop-up window using the switchToWindow(name) method
continue running my script on that pop-up window
I've googled and researched this on the internet and I have not gotten any much information.
Thanks and please let me know if you need more information.
OSL Mac OSX 10.7.3
Ruby: ruby 1.8.7 (2010-01-10 patchlevel 249) [universal-darwin11.0]
Browser: Firefox 9.0.1 (Mac)
Chrome: Chrome 17.0.963.79 (Mac)
Selenium-Server: Ruby gem 2.20.0
Justin, you have a good approach. But there is a gotcha in assuming that the window handles will return in the correct order. This isn't always the case across all browsers. I outline a slightly different approach in my free weekly Selenium tip newsletter (elemental-selenium.com).
It goes like this:
#driver.get 'http://the-internet.herokuapp.com/windows'
main_window = #driver.window_handle
#driver.find_element(css: '.example a').click
windows = #driver.window_handles
windows.each do |window|
if main_window != window
#new_window = window
end
end
#driver.switch_to.window(main_window)
#driver.title.should_not =~ /New Window/
#driver.switch_to.window(#new_window)
#driver.title.should =~ /New Window/
You can see the full tip here.
The problem is that the get_all_window_ids is for Selenium::Client rather than Selenium::Webdriver. I believe that Selenium::Client is the old version Selenium and the API is not the same as Selenium::Webdriver (see here). Since you are using Selenium::Webdriver, this explains why you get an 'undefined method' error.
For Selenium::Webdriver, the only way I know how to switch between windows is using:
#driver.switch_to.window("<window_handle>")
You can get all the known window_handles by:
#driver.window_handles
#=> Returns all window handles as an array of strings
If you want to switch to the popup you just opened you can do the following. Note that this assumes .window_handles are in the order that the windows were opened, which I believe is true:
#driver.switch_to.window #driver.window_handles.last
To summarize, assuming you only care about accessing the popup (and not about accessing by name) you can do:
#Click control that opens popup
#driver.find_element(:id, 'button that opens popup').click
#Switch to popup
#driver.switch_to.window #driver.window_handles.last
#Do actions in new popup
#driver.find_element(:id, 'id of element in popup').click
Note that if after working with the popup, you will want to return to the original window, then I suggest you do the following. By passing a block to the switch_to.window, the block will be executed in the popup and when the block ends #driver will automatically point back to the original window.
#Click control that opens popup
#driver.find_element(:id, 'button that opens popup').click
#Switch to popup
#driver.switch_to.window( #driver.window_handles.last ){
#Do actions in new popup
#driver.find_element(:id, 'id of element in popup').click
}
#Continue with original window
#driver.find_element(:id, 'button in original window').click

In Ruby, with selenium, difficulty clicking Google Search

I get this far, opening the firefox browser, navigating to google, and finding the google search element
irb(main):001:0> require 'selenium-webdriver'
=> true
irb(main):002:0> driver = Selenium::WebDriver.for:firefox
=> #<Selenium::WebDriver::Firefox::Marionette::Driver:0x..fb3c81796cc82b708 browser=:firefox>
irb(main):003:0> driver.navigate().to("http://www.google.com")
=> nil
irb(main):188:0> driver.find_element(:name, "q").send_keys "fff"
=> nil
irb(main):112:0> driver.find_element(:name, "btnK");
=> #<Selenium::WebDriver::Element:0x5fb450f4379c50ce id="d767311c-27a2-3544-8f11-e4edc9736588">
irb(main):113:0> driver.find_element(:name, "btnK").attribute('value');
=> "Google Search"
But I can't manage to click it!
irb(main):114:0> driver.find_element(:name, "btnK").click
Traceback (most recent call last):
16: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/w3c/bridge.rb:552:in `execute'
15: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/bridge.rb:166:in `execute'
14: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/http/common.rb:62:in `call'
13: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/http/default.rb:104:in `request'
12: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/http/common.rb:84:in `create_response'
11: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/http/common.rb:84:in `new'
10: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/response.rb:32:in `initialize'
9: from /usr/local/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/response.rb:69:in `assert_ok'
8: from clickElement#chrome://marionette/content/listener.js:1209:5
7: from navigate#chrome://marionette/content/listener.js:409:13
6: from navigate/<#chrome://marionette/content/listener.js:410:13
5: from clickElement/<#chrome://marionette/content/listener.js:1210:14
4: from interaction.clickElement#chrome://marionette/content/interaction.js:130:11
3: from webdriverClickElement#chrome://marionette/content/interaction.js:159:11
2: from ElementNotInteractableError#chrome://marionette/content/error.js:286:5
1: from WebDriverError#chrome://marionette/content/error.js:178:5
Selenium::WebDriver::Error::ElementNotInteractableError (Element <input name="btnK" type="submit"> could not be scrolled into view)
irb(main):115:0>
It says that the element "could not be scrolled into view" !
I can do driver.find_element(:tag_name, "body").send_keys :page_down; which would page down. Which is mentioned Looking at ruby selenium documentation for send_keys https://www.rubydoc.info/gems/selenium-webdriver/Selenium%2FWebDriver%2FElement%3Asend_keys . I see a list of key codes https://www.rubydoc.info/gems/selenium-webdriver/Selenium/WebDriver/Keys#KEYS-constant . And also listed https://github.com/SeleniumHQ/selenium/blob/master/rb/lib/selenium/webdriver/common/keys.rb . Or I can page down manually. But scrolling (at least in the normal sense of the term) doesn't seem to be the issue, I still get that error. Maybe it's in a frame I can switch to and I don't know which.
The button is of course visible. It's just a regular google search page.
I can find the button fine but I can't see how to click it, as .click isn't working for me.
added
in reply to a comment asking me if the google suggestions box is obfuscating the button. I can see in the browser that the button isn't obfuscated 'cos sometimes I have done escape manually, or clicked the background window(to get rid of that. I am using IRB so running each statement manually so I have time to do that). But I also did escape with the code, and escape works to get rid of the suggestion box, but still same error when trying to click the button
While discussing this problem in the comments, I wrote some C# code to demonstrate how we wait for clickable in C#
new WebDriverWait(Driver, TimeSpan.FromSeconds(5)).Until(ExpectedConditions.ElementToBeClickable(By.Name("btnK"))).Click();
In the process of writing the C# code I posted, I think I see what the issue is. When you start typing in the search box, a dropdown appears that contains the suggested searches. This covers the "Google Search" button that you are trying to click... BUT another "Google Search" button appears at the bottom of the dropdown itself. You should print a count of the (:name, "btnK") element(s)... I'm assuming it will print 2 (if a proper wait is added). From there, you just need to click the one that is on top.
Another option would be to send a \r\n at the end of your search string and avoid this whole issue... or you could just navigate to the final search URL and save even more headaches and time.
Added note from barlop
To clarify, this has nothing to do with the suggestions popup showing(and having to get rid of the popup).. and nothing to do with having a wait (Since I was working with IRB, I was naturally waiting). And I'd already clicked outside or hit escape to get rid of the popup. The issue was, and Jeff's answer alerted me to this - Even after the popup is gone, there are still two buttons with that btnK name. So, using find_elements rather than find_element, and running .click on the second one works! (Also, using find_elements(plural) for the name attribute makes sense, since as noted in an answer herehttps://stackoverflow.com/questions/5518458/does-a-name-attribute-have-to-be-unique-in-a-html-document. the name attribute is not unique / not a unique identifier.
This is what I told you to use WATIR, If you had used WATIR, this problem wouldn't have arrived. The problem in your code, it's not waiting for visibility. Recently Chrome Driver has added implicit wait for click as well, so it would wait for visibility if you set the implicit wait. Otherwise move to WATIR which is a good wrapper which doesn't wait via driver, In WATIR waiting for element status happens from local language binding, so use this selenium code
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
driver.manage.timeouts.implicit_wait = 10
driver.navigate().to("http://www.google.com")
driver.find_element(:name, "q").send_keys "fff"
driver.find_element(:name, "btnK").click
Remember this selenium code wouldn't work for firefox because implicit wait for click is not added yet for firefox.
Since WATIR is handling the timing from local language binding, it doesn't matter whether you use Chrome or Firefox, it would perfectly work
WATIR Code (Default is Chrome)
require 'watir'
b=Watir::Browser.new
b.goto 'www.google.com'
b.text_field(name: 'q').set 'fff'
b.button(name: 'btnK').click
If you want to drive Firefox
b=Watir::Browser.new :firefox
But you can do the click with javascript (sending javascript to the browser).
b=driver.find_element(:name, "btnK")
driver.execute_script("arguments[0].click();",b)
Jeff explains why the javascript method(selenium with javascript), works on either button but the non-javascript method(selenium without javascript) works only on one button, he wrote "Both of the buttons would work, the JS click will click on any element no matter where it is... or if it's visible, etc. Selenium was designed to interact with the page as a user would so it was unable to click on the element due to the error."

Resources