How do I confirm a javascript popup with Capybara? - ruby-on-rails

I've tried several examples found online, but with no luck. I am looking to confirm the confirm message of a delete link. The last attempt was the code below, but that resulted in an Capybara::NotSupportedByDriverError error.
def confirm_dialog
page.evaluate_script('window.confirm = function() { return true; }')
end

Adding an answer for those hitting this in 2016 and beyond. You can now use Capybara directly to accept a confirmation box. You do this by wrapping the code that causes the confirmation box to appear in the accept_confirm function.
accept_confirm do
click_link 'Destroy'
end

First of all switch to using Selenium as the driver by putting an #javascript tag in front of your scenario.
The following code in your cucumber step will then confirm the dialogue:
page.driver.browser.switch_to.alert.accept
# or
page.driver.browser.switch_to.alert.dismiss
# or
page.driver.browser.switch_to.alert.text
As #NobbZ said, this question has been asked and answered before here: How to test a confirm dialog with Cucumber?.
More selenium documentation available here too: http://code.google.com/p/selenium/wiki/RubyBindings#JavaScript_dialogs

for capybara-webkit:
page.driver.browser.accept_js_confirms
page.driver.browser.reject_js_confirms
which is still working, but the documentation says also:
page.driver.accept_js_confirms!
page.driver.accept_js_confirms!
See https://github.com/thoughtbot/capybara-webkit , search "accept_js_confirms"

I've had timing issues with browser dialogs in a CI environment so I'm polling for a dialog before accepting it:
def accept_browser_dialog
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
wait.until {
begin
page.driver.browser.switch_to.alert
true
rescue Selenium::WebDriver::Error::NoAlertPresentError
false
end
}
page.driver.browser.switch_to.alert.accept
end

I had to use a sleep in the webkit test since it would fail everynow and then otherwise.
Here is what I came up with after reading everyones posts:
if page.driver.class == Capybara::Selenium::Driver
page.driver.browser.switch_to.alert.accept
elsif page.driver.class == Capybara::Webkit::Driver
sleep 1 # prevent test from failing by waiting for popup
page.driver.browser.accept_js_confirms
else
raise "Unsupported driver"
end

try to add :js => true to your test.
RSpec’s metadata feature can be used to switch to a different driver.
Use :js => true to switch to the javascript driver, or provide a
:driver option to switch to one specific driver. For example:
it 'will use the default js driver' :js => true do
...
end

In Capybara its very simple to accept the model window. Even we can do the same in selenium but its little tough for people who are not aware about selenium.
page.accept_modal #This will accept the modal window
page.dismiss_modal #This will Reject/Dismiss the modal window

I would guess that you have to add selenium to your gem-file and configure it and capybara that capybara uses selenium as the driver.
I think also that How to test a confirm dialog with Cucumber? is very similar to your question, especially the accepted answer.

Related

Selenium-chromedriver: Cannot construct KeyEvent from non-typeable key

I updated my Chrome and Chromedriver to the latest version yesterday, and since then I get the following error messages when running my Cucumber features:
....
unknown error: Cannot construct KeyEvent from non-typeable key
(Session info: chrome=98.0.4758.80) (Selenium::WebDriver::Error::UnknownError)
#0 0x55e9ce6a4093 <unknown>
#1 0x55e9ce16a648 <unknown>
#2 0x55e9ce1a9866 <unknown>
#3 0x55e9ce1cbd29 <unknown>
.....
I try to fill a text field with Capybara's fill_in method. While debugging I noticed that Capybara has problems especially with the symbols # and \. Every other character can be written into the text field without any problems.
The code that triggers the error looks like this
def sign_in(user)
visit new_sign_in_path
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_button 'Sign in'
end
user.email contains a string like "example1#mail.com".
I work with Rails 6.1.3.1, Cucumber 5.3.0, Chromedriver 98.0.4758.48, capybara 3.35.3
The error only occurs on features that are tagged with #javascript
Do you have any ideas what causes this error or how to fix it?
For now the easiest is to pin to an earlier version of the chrome driver, so add this to your capybara config
In ruby
# /test/support/system/capybara_config.rb
require 'webdrivers/chromedriver'
Webdrivers::Chromedriver.required_version = '97.0.4692.71'
Hopefully this issue will be addressed in future chromedriver releases, it has been raised and is discussed here
I also played around with overriding the fill_in method.
This is less than ideal, and actually OS dependent, so please provide better solution or update this answer. I will try to update as my research progresses.
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
# overriding the `fill_in` helper for filling in strings with an `#` symbol
def fill_in(locator = nil, with:, currently_with: nil, fill_options: {}, **find_options)
return super unless with.include? "#"
find_options[:with] = currently_with if currently_with
find_options[:allow_self] = true if locator.nil?
element = find(:fillable_field, locator, **find_options)
email_front, email_back = with.split("#")
element.send_keys(email_front)
page.driver.browser.action
.key_down(Selenium::WebDriver::Keys[:alt])
.send_keys('g')
.key_up(Selenium::WebDriver::Keys[:alt])
.perform
element.send_keys(email_back)
end
end
It seems something has changed in the new version of ChromeDriver and it is no longer possible to send some special chars directly using send_keys method.
In this link you will see how it is solved (in C#) --> Selenium - SendKeys("#") write an "à"
And regarding python implementation, check this out --> https://www.geeksforgeeks.org/special-keys-in-selenium-python/
Specifically, my implementation was (using MAC):
driver.find_element('.email-input', 'user#mail.com')
Now I had to change it by:
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
emailParts = 'user#mail.com'.split('#')
emailElement = driver.find_element('.email-input')
emailElement.send_keys(emailParts[0])
action = ActionChains(driver)
action.key_down(Keys.ALT).send_keys('2').key_up(Keys.ALT).perform()
emailElement.send_keys(emailParts[1])
I had the same problem with Python, and it seems that using only ActionChains solves the problem in an easy way.
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
def safe_send_keys(
driver,
input_selector: str,
input_text: str,
selector_type = By.CSS_SELECTOR
):
driver.find_element(selector_type, input_selector).click()
action = ActionChains(driver)
action.send_keys(input_text)
action.perform()
# Example
driver = webdriver.Chrome()
# ... Go to your web page
email = "email_with_at#email.com"
selector = "input_selector"
safe_send_keys(driver, selector, email)
This answer is for Java and WebDriverManager users.
You can specify chrome driver version like this before start chrome.
WebDriverManager.chromedriver().browserVersion("97").setup();
You can work around this problem.
Update
This ChromeDriver bug has fixed
Just use JavascriptExecutor, for example:
JavascriptExecutor jse = (JavascriptExecutor) getDriver();
jse.executeScript("arguments[0].setAttribute('value', arguments[1])", googleEmailWebElement, email);
We had the same issue with my co-worker today.
What worked for us was removing any language from the Chrome settings, but the English (I'm using the Polish language as well and it seems like it's not causing the issue).
Machine: MacBook Pro with macOS Monterey 12.1; Test Framework: Java 11/Selenium
Chrome v.98 - Language settings
I was getting the same error for python. If Google chrome automatically updated to version 98 don't update your chromedriver to 98 also, use 97 instead. This solved my issue.
I am using Selenium with Java. I have been automating login functions without any problem until the Google Chrome update. After the update, I started getting the same error as well. I just changed my keyboard language to "US" and the problem is solved.
Update: Recently (mid-February) my ChromeDriver stopped giving the same error. I am not sure if there has been a fix as I didn't see a new release since I started having problems but my Java/Selenium automation codes run without any error regardless of the keyboard language and send characters like '#'. So, no need to switch the keyboard language to "US" anymore.
I have same problem. I solved it for Selenium java like:
String arroba = Keys.chord(Keys.ALT, "2");
String[] mailSplit = mail.split("#");
userInput.sendKeys(mailSplit[0]);
userInput.sendKeys(arroba);
userInput.sendKeys(Keys.BACK_SPACE);
userInput.sendKeys(mailSplit[1]);
This is hopefully a temporary problem that will be solved in a later release
https://github.com/SeleniumHQ/selenium/issues/10318
A test macro using JS to fill in the field solved my needs (Ruby) for basic scenarios like
fill_in :user_email, with: user.email
Test macro:
def fill_in_chrome98_tmp_fix(locator, with:)
element = find(:fillable_field, locator)
dom_id = element.native.dom_attribute("id")
raise "No DOM ID" if dom_id.blank?
page.execute_script("document.getElementById('#{dom_id}').value = '#{with}'")
end
This issue affected me as well. This answer is for Python. This is how I currently set up my selenium tests in django. My chromium browser is snap version 98.0.4758.80.
#classmethod
def setUpClass(cls):
super().setUpClass()
options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox') # Must be the very first option
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument('--disable-software-rasterizer')
options.add_argument('--disable-dev-shm-usage')
options.add_argument("--remote-debugging-port=9222")
options.add_argument("--no-default-browser-check")
options.add_argument("--no-first-run")
options.add_argument("--disable-default-apps")
s = Service(
ChromeDriverManager(
# version='98.0.4758.80',
# version='98.0.4758.48',
version='97.0.4692.71',
log_level=logging.WARNING,
chrome_type=ChromeType.CHROMIUM).install())
cls.selenium = webdriver.Chrome(
service=s,
options=options)
cls.selenium.implicitly_wait(1)
Update of Google Chrome from the previous Version: 97.0.4692.99-1 to the newest one Version: 98.0.4758.80-1 also affect my code.
I am using Python and I couldn't send any more characters as #, ^.
For now, I simply downgrade the version of Google Chrome.
It affected on such a structure:
self.driver.find_element_by_id('id_password').send_keys(password), where e.g. password contains ^.
Link to a simple downgrade of chrome version on linux machines:
https://makandracards.com/makandra/486433-how-to-downgrade-google-chrome-in-ubuntu

How to Close a Browser with capybara?

In my code i am opening a browser as follow :-
Capybara.current_driver = :selenium
include Capybara::DSL
describe 'Auro' do
specify "OMX Manual Order" do
visit 'https://omx.ordermotion.com/en/console.asp'
end
How can i close this browser?
Have Tried following ,but no luck:-
Capybara.current_session.driver.reset!
page.execute_script "window.close();"
If you're only using selenium, then the following should work:
page.driver.quit
However, if you ever want to switch between different webdrivers, then you might want to add a condition or two. This is what I use:
page.driver.quit unless (Capybara.current_driver == :webkit || Capybara.current_driver == :sauce)
:webkit refers to the headless capybara-webkit and :sauce refers to Sauce Labs, but you can use that code for whichever webdrivers you want to use.
Hope that helps!
Try some of these
page.driver.browser.close
or
window = page.current_window
window.close
Keep in mind that if you have no other windows to switch to, an error will be raised
Try giving this a shot:
page.execute_script "window.close();"

Poltergeist/Capybara test unable to find CSS intermittently

I'm using Capybara and Poltergeist and cannot for the life of me get all my tests to consistently pass. I have this one issue in particular with a date selector. It should be really simple - user clicks on input, out puts a selection of months (first image). A month is then clicked, and then a day selection appears (second image), on which a day of the month is selected.
Now, my code looks as follows:
all(:css, 'input.from_date').last.click
expect(page).to have_css(".datepicker-months")
within(:css, '.datepicker-months') { find('.month', :text => 'Jun', match: :first).click }
expect(page).not_to have_css(".datepicker-months")
expect(page).to have_css(".datepicker-days")
within(:css, '.datepicker-days') { find('.day', :text => work[:start_date].stamp('31').to_i.to_s, match: :first).click } #.to_i.to_s used to remove leading zeros
page.assert_no_selector('.datepicker-days')
Sometimes it passes, but most of the time it says:
expected to find css ".datepicker-days" but there were no matches
or
expected not to find css ".datepicker-months", found 1 match: "« 2015 » JanFebMarAprMayJunJulAugSepOctNovDec"
If I try and debug this by calling binding.pry, I can run the commands step by step in the console and it works perfectly. My timeout is set to more than enough (i think). Any ideas why this test fails intermittently?
My config:
Capybara.javascript_driver = :poltergeist
Capybara.default_wait_time = 60
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, {
timeout: 60,
js_errors: false,
phantomjs_logger: File.open("log/phantomjs.log", "a")})
end
UPDATE:
When adding sleep(0.5) after every step in the process, it passes each and every time. This is bad practice and I'm supposed to be able to write tests without doing this. :/
Whenever I run into these inconsistencies, I try to find other things to assert against so that you don't have to count on sleep.
If there are any sort of animations that start/stop, you can try to assert against those.
You can also try to be more specific in your current expectation, since it seems like you had no trouble clicking on the object you found
expect(page).not_to have_css('.datepicker-months .month', :text => 'Jun')
If any of that gets you any farther let us know.

How to close all the windows before the next test in a test suite?

[7] pry(#<RSpec::Core::ExampleGroup::Nested_1>)> page.execute_script "window.close()"
Selenium::WebDriver::Error::NoSuchWindowError: Script execution failed. Script: window.close();
The window could not be found
[8] pry(#<RSpec::Core::ExampleGroup::Nested_1>)> page.driver.browser.window_handles
=> ["f1-2"]
I had a browser open with two tabs, the above command does close one but the last tab never closes. It is open but when I try to run page.execute_script "window.close()" it gives the above error.
page.driver.browser.window_handles.each do |handle|
page.driver.browser.switch_to.window(handle)
page.execute_script "window.close()"
end
The above code was working for me sometime back but doesnt work anymore. It gives the same error.
UPDATE:
When I use,
page.driver.browser.window_handles.each do |handle|
page.driver.browser.switch_to.window(handle)
page.driver.browser.close
end
it gives the following error Selenium::WebDriver::Error::UnknownError: 'auto_id' does not refer to an open tab
Two ways you can do it
In line with your technique using JS. You would first need to switch back to your first browser window (window_handle) and then perform "window.close()". (Not Preferred) (Not sure why its not working now for you, did you upgrade server version or different browser?)
Simply use #driver.quit (Preferred)
Update
Just write this once. This will close all windows.
after(:each) do
#driver.quit
end
If you want to close only one browser tab/window/popup, switch to that window_handle and then perform
#driver.close();
page.driver.browser.close closes current tab towards the end and the last (second) tab closes itself after each example.
in case if you are using cucumber, you can the use the BEFORE/AFTER hooks .please refer similar question on stackoverflow
for some more on cucumber,please refer this Cucumber Hooks

With Capybara, how do I switch to the new window for links with "_blank" targets?

Perhaps this isn't actually the issue I'm experiencing, but it seems that when I "click_link" a link with target="_blank", the session keeps the focus on the current window.
So I either want to be able to switch to the new window, or to ignore the _blank attribute - essentially, I just want it to actually go to the page indicated by the link so I can make sure it's the right page.
I use the webkit and selenium drivers.
I submitted my findings thus far below. A more thorough answer is much appreciated.
Also, this only works with selenium - the equivalent for the webkit driver (or pointing out where I could discover it myself) would be much appreciated.
Capybara >= 2.3 includes the new window management API. It can be used like:
new_window = window_opened_by { click_link 'Something' }
within_window new_window do
# code
end
This solution only works for the Selenium driver
All open windows are stores in Selenium's
response.driver.browser.window_handles
Which seems to be an array. The last item is always the window that was most recently opened, meaning you can do the following to switch to it.
Within a block:
new_window=page.driver.browser.window_handles.last
page.within_window new_window do
#code
end
Simply refocus for current session:
session.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
Referenced on the capybara issues page: https://github.com/jnicklas/capybara/issues/173
More details on Selenium's window switching capabilities: http://qastuffs.blogspot.com/2010/10/testing-pop-up-windows-using-selenium.html
This is now working with Poltergeist. Although window_handles is still not implemented (you need a window name, i.e. via a JavaScript popup):
within_window 'other_window' do
current_url.should match /example.com/
end
Edit: Per comment below, Poltergeist now implements window_handles since version 1.4.0.
Capybara provides some methods to ease finding and switching windows:
facebook_window = window_opened_by do
click_button 'Like'
end
within_window facebook_window do
find('#login_email').set('a#example.com')
find('#login_password').set('qwerty')
click_button 'Submit'
end
More details here: Capybara documentation
I know this is old post, but for what its worth in capybara 2.4.4
within_window(switch_to_window(windows.last)) do
# in my case assert redirected url from a prior click action
expect(current_url).to eq(redirect['url'])
end
Seems like it is not possible with capybara-webkit right now: https://github.com/thoughtbot/capybara-webkit/issues/271
:-(
At the same time https://github.com/thoughtbot/capybara-webkit/issues/129 claims it is possible to switch windows with within_window.
Also https://github.com/thoughtbot/capybara-webkit/issues/47 suggests that page.driver.browser.switch_to().window(page.driver.browser.window_handles.last) works. Ah well, on to code reading.
The code at https://github.com/thoughtbot/capybara-webkit/blob/master/lib/capybara/webkit/browser.rb at least has some references that suggest that the API that works for webdriver / firefox is also working for webkit.
Now within_window implemented for capybara-webkit http://github.com/thoughtbot/capybara-webkit/pull/314 and here you can see how to use it http://github.com/mhoran/capybara-webkit-demo
As of May 2014 the following code works on capybara-webkit
within_window(page.driver.browser.window_handles.last) do
expect(current_url).to eq('http://www.example.com/')
end
To explicitly change window, you use switch_to_window
def terms_of_use
terms_window = window_opened_by do
click_link(#terms_link)
end
switch_to_window(terms_window)
end
An after that method browser will work in the new page, instead of wrap everything in a within_window block
This works for me in capybara-webkit:
within_window(windows.last) do
# code here
end
(I'm using capybara 2.4.1 and capybara-webkit 1.3.0)
You can pass a name, url or title of the window also
(But now its dipricated)
let(:product) { create :product }
it 'tests' do
visit products_path
click_link(product.id)
within_window(product_path(product)) do
expect(page).to have_content(product.title)
end
end
You can pass a labda or a proc also
within_window(->{ page.title == 'Page title' }) do
click_button 'Submit'
end
wish it stretches the method usage to more clearly understaing
I had this issue when opening links in an gmail window: I fixed it like this:
Given /^(?:|I )click the "([^"]*)" link in email message$/ do |field|
# var alllinks = document.getElementsByTagName("a");
# for (alllinksi=0; alllinksi<alllinks.length; alllinksi++) {
# alllinks[alllinksi].removeAttribute("target");
# }
page.execute_script('var alllinks = document.getElementsByTagName("a"); for (alllinksi=0; alllinksi<alllinks.length; alllinksi++) { alllinks[alllinksi].removeAttribute("target"); }')
within(:css, "div.msg") do
click_link link_text
end
end
The best idea is to update capybara to the latests version (2.4.1) and just use
windows.last
because page.driver.browser.window_handles is deprecated.
The main implementation (window_opened_by) raises an error for me:
*** Capybara::WindowError Exception: block passed to #window_opened_by opened 0 windows instead of 1
So, I resolve it by this solution:
new_window = open_new_window
within_window new_window do
visit(click_link 'Something')
end
page.driver.browser.window_handles
# => ["CDwindow-F7EF6D3C12B68D6B6A3DFC69C2790718", "CDwindow-9A026DEC65C3C031AF7D2BA12F28ADC7"]
I personally like the following approach since it works correctly regardless of JS being enabled or not.
My Spec:
it "shows as expected" do
visit my_path
# ...test my non-JS stuff in the current tab
switch_to_new_tab
# ...test my non-JS stuff in the new tab
# ...keep switching to new tabs as much as necessary
end
# OR
it "shows as expected", js: true do
visit my_path
# ...test my non-JS stuff in the current tab
# ...also test my JS stuff in the current tab
switch_to_new_tab
# ...test my non-JS stuff in the new tab
# ...also test my JS stuff in the new tab
# ...keep switching to new tabs as much as necessary
end
Test helpers:
def switch_to_new_tab
current_browser = page.driver.browser
if js_enabled?
current_browser.switch_to.window(current_browser.window_handles.last)
else
visit current_browser.last_request.fullpath
end
end
def js_enabled?
Capybara.current_driver == Capybara.javascript_driver
end

Resources