Explicit wait for Selenium Webdriver - ruby

Working on the method trying to understand the explicit wait.
require 'rubygems'
require 'selenium-webdriver'
require 'cucumber'
$driver = Selenium::WebDriver.for :firefox
$driver.manage.timeouts.implicit_wait = 3
Then /^do search$/ do
driver = $driver
one_way = driver.find_element(:id, "search.ar.type.code.oneWay").click
sleep 5
from = driver.find_element :xpath => "//div[#class = 'origin column1']//input[#type = 'text']"
from.click
So after one_way radio button is clicked and input form changed , so I put sleep 5 to give it a time element to appear, otherwise would be error "element not visible ...". So I thought it would be good time to understand explicit wait, because I need to wait till element will appear.
wait = Selenium::WebDriver::Wait.new(:timeout => 40)
wait.until {from = driver.find_element(:xpath, "//div[#class = 'origin column1']//input[#type = 'text']")
from.click
}
But getting error "Selenium::WebDriver::Error::ElementNotVisibleError: Element is not currently visible and so may not be interacted with" . Why this code doesn't wait till element appears and click it?

The issue is that the element isn't in the DOM yet, hence your needing to put a time delay in there.
That said, the API doco for ruby says you should do it this way
require 'rubygems' # not required for ruby 1.9 or if you installed without gem
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :firefox
driver.get "http://somedomain/url_that_delays_loading"
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
begin
element = wait.until { driver.find_element(:id => "some-dynamic-element") }
ensure
driver.quit
end
note that the element that you can then .click() is assigned from the wait.until method not the find_element() method as in your code.
However that arbitrary delays don't always work, if the site is busy then the delay may not be long enough.
A better option is to wait for the element to become clickable or visible.
The Java API's have ExpectedConditions convenience methods that cn be used like this...
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));
element.click();
Unfortunatly I don't think Ruby has this yet. You may need to code up your own ExpectedCondition class or package.
I'm not a Ruby dev but here is the idea of a function in Java that could be used to implement your ExpectedCondition
public WebElement findElementInTime(WebDriver driver, By by, int timeoutInSeconds)
{
log.debug("==================+>>>>>> looking for element for "+ timeoutInSeconds + " seconds");
WebElement ret = null;
for (int second = 0; second < timeoutInSeconds; second++) {
try { if (isElementPresent(driver,by)) {
log.debug("found element :-)");
ret = driver.findElement(by);
break;
}} catch (Exception e) {
log.debug("oops... sleeping 1 sec wait for button");
}
log.debug("sleeping 1 sec wait for button");
}
return ret;
}

Related

Cannot get past a modal overlay with Selenium, using Ruby

I am trying to click a button which appears in modal overlay pop up. The element of the button cannot be found using an xpath, which worked for all the steps before. If I try to find it by selecting a css class, it tries to click the overlay. But finding the overlay by itself, isn't possible.
The button that I need to click:
enter image description here
The script:
`
#click export as csv
$wait = Selenium::WebDriver::Wait.new(:timeout => 15)
input = wait.until {
element = driver.find_element(:xpath, '/html/body/div[6]/div/div/div/div[3]/div/button/div')
element if element.displayed?
}
driver.find_element(:xpath, '/html/body/div[6]/div/div/div/div[3]/div/button/div').click
`
I tried a few different things:
Selecting the xpath
`
#click export as csv
$wait = Selenium::WebDriver::Wait.new(:timeout => 15)
input = wait.until {
element = driver.find_element(:xpath, '/html/body/div[6]/div/div/div/div[3]/div/button/div')
element if element.displayed?
}
driver.find_element(:xpath, '/html/body/div[6]/div/div/div/div[3]/div/button/div').click
`
Selecting the css, which gave an interesting error:
`
#click export as csv
$wait = Selenium::WebDriver::Wait.new(:timeout => 15)
input = wait.until {
element = driver.find_element(:css, '.button-spinner-container')
element if element.displayed?
}
driver.find_element(:css, '.button-spinner-container').click
`
The error (it seems to click something else):
0x0000000103082a88 chromedriver + 4123272: element click intercepted: Element ... is not clickable at point (321, 96). Other element would receive the click: ...

ruby selenium execute_script Net::ReadTimeout

I'm using Capybara::Selenium from my script (not tests) for getting some image from external site. Page loads fine, all images are loaded also and I see them, but any attempt to execute function page.session.driver.evaluate_script always throws Net::ReadTimeout: Net::ReadTimeout with #<TCPSocket:(closed)>.
Full code:
require 'capybara-webkit'
require 'selenium-webdriver'
JS_GET_IMAGE = <<~EJSGETIMAGE
var img = document.getElementById('requestImage');
const cvs = document.createElement('canvas');
cvs.width = img.width;
cvs.height = img.height;
cvs.getContext('2d').drawImage( img, 0, 0 );
return cvs.toDataURL("image/png");
EJSGETIMAGE
session = Capybara::Session.new :selenium
page = session.visit Cfg.site.url
driver = session.driver.browser
driver.manage.timeouts.script_timeout = 5000
#img = driver.execute_async_script JS_GET_IMAGE
Okay, I started to test very simple script, but that also gone to the same error.
page.session.driver.browser.execute_async_script("setTimeout(arguments[0], 2000)")
Also I used session = Capybara::Session.new :selenium_headless and got the same error.
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
selenium-webdriver (3.142.3)
capybara (3.28.0)
capybara-webkit (1.15.1)
community/geckodriver 0.24.0-1
firefox 68.0.1 (64-битный)
Any help is very appreciated.
Small thing first - there is no need to load capybara-webkit if you're using the Selenium driver.
Now onto the main issue. There is no need to call methods directly on driver when executing JS, rather you should just be calling the Capybara methods execute_script, evaluate_script, or evaluate_async_script. The evaluate_xxx methods are for when you expect a return value, the execute_script method is for when you don't care about any return value. evaluate_async_script receives a callback function as the last argument which needs to be called to return a value, but your JS_GET_IMAGE doesn't appear to ever do that (nor really need to since it's not async) so it would be better to just use evaluate_script. The other requirement for evaluate_script is that the code evaluated needs to be a single statement. To meet that requirement we can use an IIFE.
require "capybara/dsl"
JS_GET_IMAGE = <<~EJSGETIMAGE
(function(){
var img = document.getElementById('requestImage');
const cvs = document.createElement('canvas');
cvs.width = img.width;
cvs.height = img.height;
cvs.getContext('2d').drawImage( img, 0, 0 );
return cvs.toDataURL("image/png");
})()
EJSGETIMAGE
session = Capybara::Session.new :selenium
session.visit Cfg.site.url
#img = session.evaluate_script JS_GET_IMAGE
although IMHO it would be better to have Capybara find the element and pass it to the JS function making it more flexible and taking advantage of Capybaras waiting for elements to appear
require "capybara/dsl"
JS_GET_IMAGE = <<~EJSGETIMAGE
(function(img){
const cvs = document.createElement('canvas');
cvs.width = img.width;
cvs.height = img.height;
cvs.getContext('2d').drawImage( img, 0, 0 );
return cvs.toDataURL("image/png");
})(arguments[0])
EJSGETIMAGE
session = Capybara::Session.new :selenium
session.visit Cfg.site.url
img_element = session.find('#requestImage')
#img = session.evaluate_script JS_GET_IMAGE, img_element

Set Icon of InternetExplorer Application to ico file [Ruby 1.9.3]

I have created a relatively simple Ruby script which allows the user to select an item from a drop down box. I do this by creating an instance of internet explorer and then automating it from ruby.
Preferably I would want to take the UI further and change the ICON and possibly even the window caption.
Note: I do realise this is 're-inventing the wheel' as it were, as there are many Gems available that aid in the creation of GUIs. However, unfortunately I need a solution in pure standard Ruby.
Here is my code currently:
# To learn how to use win32 libraries see here:
# http://phrogz.net/programmingruby/lib_windows.html
# https://ruby-doc.org/stdlib-2.2.0/libdoc/win32ole/rdoc/WIN32OLE.html
require 'win32ole'
require 'Win32API'
def combobox(sTitle, aOptions)
ie = WIN32OLE.new('InternetExplorer.Application')
#set various options
ie.resizable = false
ie.toolbar = false
ie.registerAsDropTarget = false
ie.statusBar = false
ie.navigate("about:blank")
sleep(0.1) until !ie.busy
ie.width=450
ie.height=190
ie.left = ie.document.parentWindow.screen.width / 2 - 200
ie.top = ie.document.parentWindow.screen.height / 2 - 75
#Set window icon (C++)
# HANDLE icon = LoadImage(fgDisplay.Instance, "c:\\icon.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
# SendMessage(instance, (UINT)WM_SETICON, ICON_BIG, (LPARAM)icon);
#icon = LoadImage(null,"my/file/name/as/string.ico",IMAGE_ICON=1,0 or 32,0 or 32,LR_DEFAULTSIZE||LR_LOADFROMFILE) #LR_DEFAULTSIZE = 0x00000040; LR_LOADFROMFILE = 0x00000010;
#SendMessage(HWND, WM_SETICON, ICON_BIG, icon)
#User32 LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
#User32 SendMessageA: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx
#define WM_SETICON 0x0080
#ICON_BIG 1
#ICON_SMALL 0
#Define constants:
_IMAGE_ICON = 1
_LR_LOADFROMFILE = 0x00000040
_LR_DEFAULTSIZE = 0x00000010
_WM_SETICON = 0x0080
_ICON_BIG = 1
_ICON_SMALL = 0
#Set icon
icoPath = "C:\Users\jwa\Desktop\Icons\toxic.ico" #"l" --> "v"
icon = Win32API.new("user32","LoadImage",["v","p","i","i","i","i"],"p").call(0,icoPath, _IMAGE_ICON, 0,0, _LR_DEFAULTSIZE || _LR_LOADFROMFILE)
puts icon
Win32API.new("user32","SendMessageA",["l","i","i","p"],"i").call(ie.hwnd,_WM_SETICON,_ICON_SMALL,icon)
#Set window content
s = "
<html>
<head>
<title>#{sTitle}</title>
</head>
<script type=\"text/javascript\">window.bWait=true</script>
<script>
submitForm = function(){
document.url = document.getElementById(\"entries\").selectedIndex
}
</script>
<body bgColor=Silver>
<center>
<b>#{sTitle}<b>
<p>
<select id=entries size=1 style='width:250px'>
<option selected></option>"
#Add options
aOptions.each do |item|
s += "<option>#{item}</option>"
end
s += " </select>
<p>
<button id=but0 onclick='submitForm()'>OK</button>
</center>
</body>
</html>"
ie.document.writeLn(s)
ie.document.body.scroll = "no"
ie.document.body.style.borderStyle = "outset"
ie.document.body.style.borderWidth = "3px"
ie.document.all.entries.Focus
ie.visible = true
#Bring window to front
Win32API.new("user32","BringWindowToTop",["l"],"i").call(ie.hwnd)
#wait till url not about:blank
begin
until (ie.document.url!="about:blank") do
sleep(0.1)
end
rescue
return -1
end
#Get id and hide ie
id=ie.document.url
id=id.to_i
ie.visible = false
#Return id-1
return id-1
end
In general the script works however I am yet to get the custom icon working.
#Set icon
icoPath = "C:\Users\jwa\Desktop\Icons\toxic.ico" #"l" --> "v"
icon = Win32API.new("user32","LoadImage",["v","p","i","i","i","i"],"p").call(0,icoPath, _IMAGE_ICON, 0,0, _LR_DEFAULTSIZE || _LR_LOADFROMFILE)
puts icon ### ==> 0
Win32API.new("user32","SendMessageA",["l","i","i","p"],"i").call(ie.hwnd,_WM_SETICON,_ICON_SMALL,icon) ### ==> Doesn't do anything (likely due to icon==0)
Is there anything obvious which is wrong with this code?
Note: Again, I would love to use a Gem like ffi for this, however that is unfortunately not possible.
Nice solution, I'm late with this but would like to show you what I use for such tasks. I used to use green_shoes for such tasks but got tired of the not native look of the controls and by other limitations (eg no grid's).
On the other hand there is autohotkey which is powerfull and has a very good GUI but is difficult to code in (surely compared with Ruby).
So I tried autohotkey.dll, it lets you use autohotkey code and so also GUI from Ruby. Works very well.
You need to download and register the dll.
https://autohotkey.com/board/topic/39588-autohotkeydll/
Here an example of a dropdown selection (there is a real combo also).
require 'win32ole'
def choose_from_dropdown *list
selections = list.join('|')
code = %Q{
Gui, Add, DropDownList, r50 w200 vChoice, #{selections}
Gui, Show, x131 y91 h67 w227, Make your choice
Hotkey,Enter,a1,ON
Return
GuiClose:
Gui, Submit, nohide
return
a1:
Gui, Submit, nohide
return
}
ahk = WIN32OLE.new("AutoHotkey.Script")
ahk.AhkTextDll(code)
choice = nil
while ahk.ahkReady == 1 do
sleep 1
choice = ahk.ahkgetvar('Choice')
if choice && choice != ""
ahk.ahkTerminate
return choice
end
end
rescue => e
puts e.message
puts e.backtrace
end
puts choose_from_dropdown %w{one two three}
In such lists I like to scroll with the up and down keys and confirm with the Enter key, so that is what I implemented, other implementations with Ok button etc are also possible.
Well... This is embarrassing. It was a really nooby error... I keep forgetting that in Ruby \ escapes characters in a string so:
#Set icon
icoPath = "C:\Users\jwa\Desktop\Icons\toxic.ico"
actually returned "C:UsersjwaDesktopIconstoxic.ico" which of course is not the file path... I needed to do:
#Set icon
icoPath = "C:\\Users\\jwa\\Desktop\\Icons\\toxic.ico"
Oops. Also, it appears that win32api uses "p" for pointers within ruby. So when it comes to using the handle returned by win32api in the seticon statement, i.e.
Win32API.new("user32","SendMessageA",[args*],"i")
the final parameter of args* should be a long "l" instead of a pointer. Like this:
Win32API.new("user32","SendMessageA",["l","i","i","l"],"i").call(ie.hwnd,_WM_SETICON,_ICON_SMALL,icon)
also it is important to note that you should call both _ICON_SMALL and _ICON_BIG. _ICON_SMALL is the icon in the window itself and _ICON_BIG is the icon in the alt-tab menu.
Full code for changing the icon of a iexplorer application:
#Define constants:
_IMAGE_ICON = 1
_LR_LOADFROMFILE = 0x00000040
_LR_DEFAULTSIZE = 0x00000010
_WM_SETICON = 0x0080
_ICON_BIG = 1
_ICON_SMALL = 0
#Set icon
icoPath = "C:\\Users\\jwa\\Desktop\\Icons\\toxic.ico" #"l" --> "v"
icon = Win32API.new("user32","LoadImageA",["l","p","l","l","l","l"],"p").call(0,icoPath, _IMAGE_ICON, 0,0, _LR_DEFAULTSIZE | _LR_LOADFROMFILE | 0)
Win32API.new("user32","SendMessageA",["l","i","i","l"],"i").call(ie.hwnd,_WM_SETICON,_ICON_SMALL,icon) # affects title bar
Win32API.new("user32","SendMessageA",["l","i","i","l"],"i").call(ie.hwnd,_WM_SETICON,_ICON_BIG,icon) # affects alt-tab

Wait for an image to load using Capybara SitePrism

How can I wait for an image to load using Capybara? Here's my app code:
-- <img src="data:image/jpeg.*" class="some class" alt="Image">
I have tried using the wait_for method using those class and img attributes with no luck. It never waited for that image to load.
img elements have a complete property that indicates whether the image is loaded or not. Therefore something along the lines of
start = Time.now
while true
break if find('img.some_class')['complete']
if Time.now > start + 5.seconds
fail "Image still not loaded"
end
sleep 0.1
end
would wait up to 5 seconds for the image to finish loading, assuming the img element is already on the page.
This could also be implemented as a custom selector with something along the lines of
Capybara.add_selector(:loaded_image) do
css { |locator_class| "img.#{locator_class}" }
filter(:loaded, default: true, boolean: true) { |node, value| not(value ^ node['complete']) }
end
which could then be called as
find(:loaded_image, 'some_class')
*** Note: I haven't actually tried the custom selector code, but it should be close enough to provide guidance

How do you automatically confirm a modal in ruby using page-object?

I have a standard rails application with a delete link. This delete link comes up with a browser popup modal (using rails confirm option).
I am currently attempting to test the delete function with Cucumber, Selenium-Webdriver (or Watir-Webdriver, still haven't decided), and the page-object gem.
Once the modal has been triggered, anything I do on the page gives me the following error:
Modal dialog present (Selenium::WebDriver::Error::UnhandledAlertError)
I have been looking all over, but cannot find a way to handle this condition. If possible, I would like to continue using the PageFactory module in the page-object gem.
How can I dismiss/accept the modal?
I figured out a way to do this, but haven't decided upon the exact implementation.
In Javascript you can overwrite any function, which means you can overwrite the confirm
This means that you can run the following code to disable any popups.
def disable_popups
# don't return anything for alert
browser.execute_script("window.alert = function() {}")
# return some string for prompt to simulate user entering it
browser.execute_script("window.prompt = function() {return 'my name'}")
# return null for prompt to simulate clicking Cancel
browser.execute_script("window.prompt = function() {return null}")
# return true for confirm to simulate clicking OK
browser.execute_script("window.confirm = function() {return true}")
# return false for confirm to simulate clicking Cancel
browser.execute_script("window.confirm = function() {return false}")
end
If you put this inside the initalize_page function of a page-object then the dialogs are automatically removed.
def initialize_page
disable_popups
end
Or you could do it right before the pop is triggered
def delete
disable_popups
delete_link # => clicks the link
end
References:
Testing Webpages with Javascript Popups Correctly
Dismissing Pesky Javascript Dialogs with Watir
The page object gem has methods to handle javascript popups - see the original page-object gem post. In your case, I believe you want the confirm method::
(String) confirm(response, frame = nil, &block)
Override the normal confirm popup so it does not occurr.
Examples:
message = #popup.confirm(true) do
#page.button_that_causes_confirm
end
Parameters:
what (bool) — response you want to return back from the confirm popup
frame (defaults to: nil) — optional parameter used when the confirm is nested within a frame
block — a block that has the call that will cause the confirm to display
Returns:
(String) — the message that was prompted in the confirm
There is similar for the alert and prompt popups.
In your page-object, I would define:
class MyPage
link(:delete_link, :id=>'delete')
def delete()
confirm(true){ delete_link }
end
end
Then when you call page.delete, it will click the link and confirm the popup.

Resources