I've seen various implementations of the wait_for_ajax method that makes Capybara wait until all AJAX requests are completed before moving forward.
I've just switched to using Poltergeist as my JavaScript driver and I'm having trouble getting it to wait for the AJAX to complete on a test (see below)
Below is the implementation that was previously working with Selenium - the only thing I modified was the evaluation script -
Previous: page.evaluate_script("jQuery.active")
Updated: page.evaluate_script("$.active").to_i
If I insert a sleep statement it passes because it has enough time to finish the AJAX call, so I definitely know that's the issue.
Is there any error in this approach?
Thanks!
it "user can log in", js: true do
visit root_path
click_tab("log-in")
# Fill in form
fill_in "user[email]", with: "al-horford#hawks.com"
fill_in "user[password]", with: "sl4mdunkz"
# Click submit and wait for AJAX
within("#log-in") { click_button("Log In")) }
wait_for_ajax
# Expectations
expect(current_path).to eq(home_index_path)
end
def wait_for_ajax
Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
request_count = page.evaluate_script("$.active").to_i
request_count && request_count.zero?
rescue Timeout::Error
end
In my experience, there are some situations wait_for_ajax will not wait for.
In our app, we have a 'to cart'-button that triggers Ajax. It then waits for a callback event before it loads the next page. This is not picked up by wait_for_ajax.
What fixed it, and what is generally a better approach than waiting a given amount of time with sleep(), is to verify that you're on the next page using one of Capybara's waiting finders.
Simply replace expect(current_path).to eq(home_index_path)
with something like expect(page).to have_content("content_home_page_should_have")
That should work.
Related
The session object is cleared after redirect during testing.
First, my test stack. I am running a Rails 4.2.4 app with capybara 2.6.2, capybara-webkit 1.8.0, and rspec 3.3.0. My tests were running without issue until, for no apparent reason whatsoever, they weren't.
Below is my code (condensed to stay on point):
assessment_controller.rb
def create
#assessment_basic = Assessment::Basic.new(params)
if #assessment_basic.valid?
session[:most_recent_zip_code] = #assessment_basic.zip_code
if household.search.present?
update_household_search(#assessment_basic.zip_code)
end
redirect_to dashboard_path
else
render :new
end
end
private
def household
if #household
#household
elsif session[:household_id]
#household = find_household(session[:household_id])
elsif session[:most_recent_zip_code]
#household = Household.create(household_params)
session[:household_id] = #household.id
end
#household
end
As you can see, this is pretty straight-forward. I am receiving a zip code in the params. I store that zip code for later use and use it to create a household object unless one already exists, in which case I return that instance. If a household object is instantiated, I then store its id in the session, return control back to the action and redirect to the dashboard_path having two variables in session. All of this works well, and has worked well for some time now.
However, when I try to access the variables in the dashboard#index action, none of the session variables I stored are present. The feature works, which suggests that my problem is with the bits running my specs. By the way, the spec passes locally. It is when the code is moved to our CI environment that we get the error. We tried three different CI environments (Circle, Semaphore, and Travis) and they all report the same error:
#<NoMethodError: undefined method 'search' for nil:NilClass>
Which basically means, the household could not be recreated and is therefore nil. A closer look shows the reason the household could not be created from session is that the session was cleared.
Can someone help me identify the component(s) involved in ensuring session values persists during tests? Let me know if you need anything else in order to be able to help me.
Hector
it 'a client visits the referrals page with all providers minus the single stop and tax locations from the dashboard page and ' do
user_visits_the_homepage
enter_zip_code('11217', true)
dashboard_page.expect_to_be_on
dashboard_page.click_browse_local_resources
referrals_page.expect_to_be_on
end
def user_visits_the_homepage
visit '/'
expect(page).to have_content t('welcome.where')
end
def enter_zip_code(zip_code = '11217', remain_dashboard = false)
within '.welcome-form:nth-of-type(1)' do
fill_in t('welcome.where'), with: zip_code
click_on t('welcome.get_started')
end
expect(page).to have_content t('header.dashboard')
click_on t('header.your_profile') unless remain_dashboard
expect(page).to have_field t('activerecord.attributes.client_household_member.zip_code'), with: zip_code unless remain_dashboard
end
Two controllers: Users and Tasks.
Main page for Users = Users/user_id.
Form on main page used to input data into tasks model.
This process handled by the Tasks Controller.
Successful input: redirect and load tasks from database, OK, all working.
Unsuccessful input, just need to refresh main page so we keep form input and specialised (non-flash) error messages
I can't seem to get the Tasks Controller to deliver the original page. Error is Missing template users/1(which is the correct syntax if I were to visit in my browser).
Should I be calling an action and passing params? Any help for this beginner would be really appreciated.
def create
#task = current_user.tasks.build(task_params)
if #task.save
flash[:success] = "New task created!"
redirect_to user_url(current_user)
else
flash[:error] = "Task not saved! Please see guidance by form labels"
render "users/#{current_user.id}"
end
end
private
def task_params
params.require(:task).permit(:label, :address, :content)
end
end
users/1 is not a template, it's a path. users/show is the template in this case.
mind that the only reason to use render is to render a template in the scope of your current controller action rather than the normal one.
i.e. you probably need to have #user etc set, or the users/show template will be upset about missing variables.
In this case it might be easier just to redirect_to user_path(id) and allow the users/#show controller action set up the #user variable etc.
Thanks in advance!
Sidekiq is working just fine, but I cannot manage to test it with Devise Async, or should I say that I cannot test the latter?
According to Sidekiq's documentation, when test mode is set to fake!, any job given to a worker is pushed to an array named jobs of that same worker. So it is trivial to test the increase of this array.
But, with Devise Async, it is not so trivial, although its backend includes Sidekiq::Worker. Here's a small list of things that I tried to test:
Devise::Async::Backend::Sidekiq.jobs
Devise::Mailer.deliveries
ActionMailer::Base.deliveries
Devise::Async::Backend::Worker.jobs
None of these testing subjects points an increase in size. Since Devise sends its emails as models callbacks, I tried testing both in a model and in a controller spec. Using Factory Girl and Database Cleaner, I also tried both modes: transaction and truncation. Needless to say that I also tried both modes of Sidekiq: fake! and inline!.
What am I missing?
As mentioned in the documentation, you can check the queue size as
Sidekiq::Extensions::DelayedMailer.jobs.size
Was working on this issue, chanced upon a beautiful implementation done by gitlab which I thought might be helpful in testing devise-async or email that are push via the sidekiq queue.
spec_helper.rb
email_helpers.rb
By adding these lines in spec_helper.rb
# An inline mode that runs the job immediately instead of enqueuing it
require 'sidekiq/testing/inline'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.include EmailHelpers
# other configurations line
end
And adding /spec/support/email_helpers.rb
module EmailHelpers
def sent_to_user?(user)
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
end
def should_email(user)
expect(sent_to_user?(user)).to be_truthy
end
def should_not_email(user)
expect(sent_to_user?(user)).to be_falsey
end
end
To run the test for example testing your forgot password, I am assuming you know rspec, factorygirl, capybara
/spec/features/password_reset_spec.rb
require 'rails_helper'
feature 'Password reset', js: true do
describe 'sending' do
it 'reset instructions' do
#FactoryGirl create
user = create(:user)
forgot_password(user)
expect(current_path).to eq(root_path)
expect(page).to have_content('You will receive an email in a few minutes')
should_email(user)
end
end
def forgot_password(user)
visit '/user/login'
click_on 'Forgot password?'
fill_in 'user[email]', with: user.email
click_on 'Reset my password'
user.reload
end
end
You would notice that in this test implementation
will cause sidekiq to run the job instead of enqueuing it,
The user model email attribute must be called email or you can just replace the code above.
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1 check to see ActionMailer::Base.deliveries is delivering to user.email
I want to perform some tasks in background but with something like "run as". In other words, like a task was launched by the user from the context of his session.
Something like
def perform
env['warden'].set_user(#task_owner_user)
MyService::current_user_dependent_method
end
but I'm not sure it' won't collide with other tasks. I'm not very familiar with Sidekiq.
Can I safely perform separate tasks, each with a different user context, somehow?
I'm not sure what your shooting for with the "run as" context, but I've always setup sidekiq jobs that need a unique object by passing the id in the perform. This way the worker always knows which object it is trying to work on. Maybe this is what you're looking for?
def perform id
user = User.find(id)
user.current_user_dependent_method
end
Then setup a route in a controller for triggering this worker to start, something like:
def custom_route_for_performing_job
#users= User.where(your_conditions)
#users.each do |user|
YourWorker.perform_async user.id
end
redirect_to :back, notice: "Starting background job for users_dependent_method"
end
The proper design is to use a server-side middleware + a thread local variable to set the current user context per job.
class MyServerMiddlware
def call(worker, message, queue)
Thread.current[:current_user] = message['uid'] if message['uid']
yield
ensure
Thread.current[:current_user] = nil
end
end
You'd create a client-side middleware to capture the current uid and put it in the message. In this way, the logic is encapsulated distinctly from any one type of Worker. Read more:
https://github.com/mperham/sidekiq/wiki/Middleware
As preface, I've followed through some tutorials (i.e. Michael Hartl's) though I'm still fairly novice. Forgive any cloudy terminology.
I am trying to build a simple application in Rails 4 that does the following:
User logs into application (currently working with sign-in-with-twitter link and routing)
get "/auth/:provider/callback" => "sessions#create"
get "/signout" => "sessions#destroy", :as => :signout
Once <% if current_user %> is true, I have the view rendering a partial where there will be a list of simple buttons. When the user clicks a button I want the application to tweet on behalf of the current_user a preset string. Ideally, I'd do this all in ruby/rails.
These button functions are where I'm getting hung up. I've read a fistful of documents but there seem to be a lot of conflicting and old answers. Here's a quick list of the ones I think are closest, though not explicit about sending a tweet from a simple button/link in a view:
http://www.sitepoint.com/ruby-social-gems-twitter/
http://richonrails.com/articles/sending-a-tweet-to-twitter
Some call for controllers, a more robust oauth setup (which I have bundle installed and connected to the dev.twitter application, though not fleshed out beyond keys), and whatever else. It's got me turned around and I'm not yet good enough to synthesize all the information. Any help and direction would be great. Below are some other files in the app that might be helpful.
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Hi!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Bye!"
end
end
And omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, '_priv', '_priv'
end
Eep! I'm the author of the second link (RichOnRails). Did you take a look at the example app included with the tutorial? It does almost exactly what you want. If the tweets are hard coded you could approach it in a couple of different ways. If you take a look at the tweets controller, you'll see it takes a parameter called 'message'. Any message passed to that create method will tweet as the current user.
def create
current_user.tweet(twitter_params[:message])
end
The easiest (but not necessarily best) way to adapt this to fit your needs is to have a form for each tweet, and do a hidden field with the message you wish to tweet. The button becomes a submit for that particular form (you can add remote: true if you want to keep the page from refreshing, then use a bit of javascript to update the UI elements). Hope this helps.