rails 3.1, capybara-webkit, how to execute javascript inside link? - ruby-on-rails-3

Can I execute a javascript in a link with capybara click_link('next_page') ?
The link looks like this:
<a onclick="$('#submit_direction').attr('value', '1');$('#quizForm').submit()" id="next_page" href="#">Next Question</a>
I read at capybara at github that I can submit a form by click at its submit button like this:
click_on('Submit Answer')
But, in my case, I need to submit the form using javascript in a link, so, how to test the link that has javascript inside ? isn't click_link('next_page') sufficient ?
EDIT
after setting :js=> true my test looks like this:
it "should pass when answering all correct", :js=>true do
login_as(student, :scope => :student)
visit ("/student_courses")
#page.execute_script("$('#submit_direction').attr('value', '1');$('#quizForm').submit()")
trace "HTML:------------", page.html
end
Before :js=> true, I could visit the page normally, But, I've noticed that the page cannot be visited after :js=> true, here is the error I got after visiting the page:
Started GET "/student_courses" for 127.0.0.1 at 2012-01-23 06:29:26
+0200 (5010.7ms) UPDATE "students" SET "last_sign_in_at" = '2012-01-23 04:29:26.274285', "current_sign_in_at" = '2012-01-23
04:29:26.274285', "last_sign_in_ip" = '127.0.0.1',
"current_sign_in_ip" = '127.0.0.1', "sign_in_count" = 1, "updated_at"
= '2012-01-23 04:29:26.276279' WHERE "students"."id" = 1 SQLite3::BusyException: database is locked: UPDATE "students" SET
"last_sign_in_at" = '2012-01-23 04:29:26.274285', "current_sign_in_at"
= '2012-01-23 04:29:26.274285', "last_sign_in_ip" = '127.0.0.1', "current_sign_in_ip" = '127.0.0.1', "sign_in_count" = 1, "updated_at"
= '2012-01-23 04:29:26.276279' WHERE "students"."id" = 1 HTML:------------__
Internal
Server Error
Internal Server Error
cannot rollback transaction - SQL statements in progress
WEBrick/1.3.1 (Ruby/1.9.3/2011-10-30) at
127.0.0.1:34718
so, why SQLite3::BusyException: database is locked now ?!

I just spent 8 hours resolving a similar issue, and I found the solution. The fix is so simple, I could cry.
First, diagnosis
The reason you're getting "SQLite3::BusyException: database is locked" is that you are launching an asynchronous thread, namely a form submission, that ends up losing the "database write" race to your test's main thread. In effect, as your test has already completed and is running your "after each" database cleanup routine (defined in your spec_helper), the form action has only just begun trying to run the business logic (which relies on the data that your test after:each hook is busy destroying).
This problem is a lot more likely to occur with tests that click on an AJAX POST button and then terminate without asserting something about the view change.
Second, the fix
As it turns out, Capybara is designed to "synchronize" all your requests. But only if you implicitly let it. Notice that after your form submission, you're not having Capybara look at your page. Therefore it thinks you're done and takes your data out of scope (while your form submission thread is hanging in the background.)
Simply add the following line of code to the end of your test, and it should suddenly work:
page.should_not have_content "dude, you forgot to assert anything about the view"
Third, make it pretty
Don't use execute_script. That's what Capybara is for. But also don't rely on "click_on" because it's not a great abstraction. You need to know too much about its internals. Instead, use CSS selectors like so:
page.find("#submit_button").click
And one more thing - your test shouldn't try to manipulate the DOM. A good Selenium test assumes to have to follow the same steps a normal user follows.
So in conclusion
it "should pass when answering all correct", :js => true do
login_as(student, :scope => :student)
visit ("/student_courses")
page.find(".my-js-enabled-button").click
page.find("#submit_button").click
# Synchronizes your view to your database state before exiting test,
# Therefore makes sure no threads remain unfinished before your teardown.
page.should_not have_content "dude, you forgot to expect / assert anything."
end

To expand on Alex's comment Capybara won't execute JS unless it's explicitly turned on for the given tests.
To do this, use :js => true on either your describe block or individual test eg.
describe "in such and such a context", :js => true do
# some stuff
end

It should work. with capybara, you are essentially test your application through the browser, and it will behave the same.

Related

How to clean DB with Rails 5, Minitest, Capybara JS (Selenium)?

I have set up a very simple rails 5 project to narrow down my problem:
https://github.com/benedikt-voigt/capybara_js_demo
In this project the data mutation done by the Capybara JS is not deleted, neither by Rails nor by the Database cleaner I added.
The following great blog argues, that no DatabaseCleaner is needed:
http://brandonhilkert.com/blog/7-reasons-why-im-sticking-with-minitest-and-fixtures-in-rails
but this works only for fixtures, not for the mutation done by an out-of-thread Capybara test.
I added the Database cleaner, but this also needed work.
Does anybody has a sample setup?
From a quick look at your test I would say it's leaving data because the data is actually being added after DatabaseCleaner cleans. The click_on call occurs asynchronously, so when your assert_no_content call happens there's no guarantee the app has handled the request yet or the page has changed yet and since the current page doesn't have the text 'Name has already been taken' on it the assertion passes and the database gets cleaned. While that is happening the click gets processed by the app and the new data is created after the cleaning has occurred. You need to check/wait for content that will appear on the page after the click - something like
page.assert_text('New Foo Created')
You should only be asserting there is no content once you already know the page has changed, or you're expecting something to disappear from the current page.
I solved now the problem by setting the DB connection to one
class ActiveRecord::Base
mattr_accessor :shared_connection
##shared_connection = nil
def self.connection
##shared_connection || ConnectionPool::Wrapper.new(:size => 1) { retrieve_connection }
end
end
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
as describe here:
https://mattbrictson.com/minitest-and-rails
I uploaded the working repo here:
https://github.com/benedikt-voigt/capybara_js_demo_working

Rails 4 Capybara + Selenium, Data-confirm

I have this problem with my code.This page inside my application is using http basic authentication. I'm testing my application with Rspec and Capybara, but I have problem with data confirmation when deleting comment, it just can't seem to pass.
Here is my test code:
before(:each) do
page.driver.browser.authorize ENV['ADMIN_LOGIN'], ENV['ADMIN_PASSWORD']
...
end
...
it "should redirect to create new inspiring post and create new post on click" do
visit admin_path
click_link("create_inspi")
fill_in("Title",:with => "Test title")
fill_in("Short",:with => "Test short desc")
fill_in("Body",:with => "Test body desc")
fill_in("Date",:with => "12.12.2012")
click_button("Create new post")
test(
have_content("Test title"),
have_content("12.12.2012")
)
end
example "when inside Link to all... you can delete particular comment",:js => true do
visit admin_path
click_link("Link to all comments in Web development and Motivation category")
click_link(#webdev_comment.id)
page.driver.browser.accept_js_confirms
end
Now for my test by default I use Capybara::RackTest::Driver, but now since I can't find a way to confirm data confirmation with RackTest I have to use Selenium driver to make this data confirmed. But now when using this Selenium driver for my last example test I found next problem:
1) testing admin webpage when inside Link to all... you can delete particular comment
Failure/Error: page.driver.browser.authorize ENV['ADMIN_LOGIN'], ENV['ADMIN_PASSWORD']
NoMethodError:
undefined method `authorize' for #<Selenium::WebDriver::Driver:0x00000003e6ad68>
# ./spec/features/admin.rb:13:in `block (2 levels) in <top (required)>'
MY QUESTION IS:
How to authenticate basic http with selenium?
-------------- EDITED ------------------
I found a way to log in into admin with selenium driver by using this code
example "when inside Link to all... you can delete particular comment",:js => true do
visit root_path
#url = current_url
#tablica = #url.split("http://")
#correct_url = #tablica[1]
admin_selenium_path = "http://#{ENV["ADMIN_LOGIN"]}:#{ENV["ADMIN_PASSWORD"]}##{#correct_url}admin"
page.visit admin_selenium_path
click_link("Link to all comments in Web development and Motivation category")
click_link(#webdev_comment.id)
page.driver.browser.accept_js_confirms
end
But now , even though it's logging in , there is literally no data inside test database, and it's throwing errors around saying that particular object couldn't be found.
before(:each) do
# page.driver.browser.authorize ENV['ADMIN_LOGIN'], ENV['ADMIN_PASSWORD']
#mystory = Mystory.create(name: "My story:",body: "My name is Matthew...")
end
This code above inside before block is not creating a required #mystory record inside database for capybara with selenium driver.
Any ideas how to populate database for capybara with selenium driver?
I'm not sure if it's exactly the same issue but I add this somewhere above/before to skip over confirm dialogs with Capybara
page.evaluate_script('window.confirm = function() { return true; }')

populate_page_with method gives an error "Undefined method send_keys" when used with selenium web-driver

I am using page object gem with selenium web-driver. I am trying to automate gmail sign in page. So to enter mail_id and password I am using populate_page_with method.
I am storing my login credentials in a variable "data"
data = { :mail_id => 'abc#abc.com', :mail_password=> '12345' }
And calling populate_page_with method like below
populate_page_with data
When I am trying run the script it gives an error Undefined method send_keys.
But the implementation working fine when I am trying to automate yahoo mail sign in page.
My page object class is
class GmailSignInPage
include PageObject
button :gsubmit, :id => 'signIn'
text_field :mail_id, :id => 'Email'
text_field :mail_password, :id => 'Passwd'
def log_in_to_gmail(data = {})
self.mail_password_element.when_visible
populate_page_with data
self.gsubmit
end
end
My step-definition is
Given /^I navigate to gmail page$/ do
data = { :mail_id => 'abc#abc.com', :mail_password=> '12345' }
on(GmailSignInPage).log_in_to_gmail data
end
In supports/env.rb, I have added PageFactory class also
World(PageObject::PageFactory)
If I modify my log_in_to_gmail method like below then also I am getting same exception
undefined method 'send_keys' for #<NoMethodError: undefined method 'current' for Time:Class> (NoMethodError)
def log_in_to_gmail(data = {})
self.mail_password = data['mail_id']
self.mail_password = data['mail_password']
self.gsubmit
end
But if I use send_keys method its working fine except warning message
def log_in_to_gmail(data = {})
mail_id_element.send_keys data['mail_id']
mail_password_element.send_keys data['mail_password']
self.gsubmit
end
And the warning message is
*** DEPRECATION WARNING
*** You are calling a method named bridge at C:/jruby-1.7.6/lib/ruby/gems/shared/gems/page-object-0.9.2/lib/page-object/elements/element.rb:27:in 'wait_for_document_ready'.
*** This method does not exist in page-object so it is being passed to the driver.
*** This feature will be removed in the near future.
*** Please change your code to call the correct page-object method.
*** If you are using functionality that does not exist in page-object please request it be added.
So I think, this is not the issue with populate_page_with method because even assignment operator = gives same exception. This may be due to page_object gem unable handle gmail sign-in page.
Ok, lets try this again.
I have created a test and copy pasted all of the sample code you have above (Using the populate_page_with() method). I did make one key change to my code compared to yours:
Yours
on(GmailSignInPage).log_in_to_gmail data
Mine
GmailSignInPage.new(#browser).log_in_to_gmail data
I don't know what gem the on() method from your code is from. I could guess, but I want to make this answer more fact based that my previous one. :)
Once I did this, I was able to successfully sign in to Google. So there is nothing unusual about the Google sign in page or any limitation I can come across in the page-object gem or anything wrong with your approach.
So the only things that are different between us is the one line of code I put above, our dev environments, other dependent gems each of us are using.
Try replacing the above line and see if that works.
If not, I would recommend seeing if you have a gem conflict of some sort. Reason I suspect this is due to the strange exception methods you are getting:
undefined method 'send_keys' for # (NoMethodError)
The send_keys part I get, but 'current'? No 'current' method is being called and the Time class is no where to be seen in this example. That's one issue to look at isolating and seeing if you can clean up.

Capybara: Verify that ajax-loaded content is not present

visit "/whatever"
expect(page).to have_no_content "This is loaded in asynchronously"
This should fail, but passes. Why? When the page first loads, the content isn't there, and Capybara reasonably doesn't wait for it.
What is the blessed way to wait for this content, now that Capybara 2.0 has declared wait_until bad news?
visit "/whatever"
wait_for_ajax do
expect(page).to have_no_content "This is loaded in asynchronously"
end
Then, in spec_helper:
def wait_for_ajax
sleep(Capybara.default_wait_time)
yield
end
Note that this waits to the end of Capybara's default_wait_time every time it runs this spec. But I'm not sure how else you'd check for something like that. If your ajax executes quickly, you can compensate for this by setting the default_wait_time to something like 1 second.
visit "/whatever"
wait_for_ajax do
expect(page).to have_no_content "This is loaded in asynchronously"
end
Then, in spec_helper:
def wait_for_ajax
page.evaluate_script("jQuery.active") == 0
yield
end
This will evaluate the script until it is true, then run your assertion.

Cucumber scenario fails with Selenium but pass if disabled

I'm in the process of setting up Selinium to work with Cucumber and Capybara in my Rails app.Currently, my env.rb configuration file contains only:
Capybara.default_driver = :selenium
Cucumber::Rails::World.use_transactional_fixtures = false
I'm not even sure if the second line is necessary ?
The point is, I have Given step definition that creates Model data
Given /^a question named Question1$/ do
#question = Question.create!(name: 'question1')
end
And, in view checkbox and label are created for that 'question1' entry.Now, When step definition is checking that checkbox:
check "question1"
and that scenario passes.
The problem is that when driver is switched to Selenium, the label and checkbox are not rendered on the page, as if #question = Question.create!(name: 'question1') is not executed, and that scenario fails:
Unable to find checkbox "question1" (Capybara::ElementNotFound)
Found the solution. env.rb should contain
DatabaseCleaner.strategy = :truncation