Ruby on Rails - run Ruby File.open only when link is clicked - ruby

In my application I have a link_to that I use for voting purposes. This link_to does different things if user_signed_in? or not.
When user IS signed in, they can vote, otherwise a modal opens for them to sign up or login.
What I want to achieve is to count how many users has clicked on voting buttons but didn't vote because of sign up.
I was thinking to do a easy ruby operation and add 1 and date for each click.
Here is my link:
= link_to_unless has_voted?(#image.id, 1), (image_tag 'vote/thumb-up.png'),user_signed_in?? vote_image_path : File.open("public/count.txt", "a+") { |file| file.puts ([1, Date.today]).join }, data: {target: user_signed_in?? "" : "#login-modal", toggle: user_signed_in? ? "" : 'modal'}, remote: true
Everything works fine when user is signed in, but when I want to run:
File.open("public/count.txt", "a+") { |file| file.puts ([1]).join }
is when things goes wrong.
This adds the number, but instead of adding number to count.txt file only when link_to is clicked, it also adds 1 for each visit/refresh.
How can I make File.open("public/count.txt", "a+") { |file| file.puts ([1]).join } only run when link_to is clicked and not on each visit/refresh.
I have been thinking to make a route and add the function there and redirect back, but then I can't open sign up modal and at same time I do not want to refresh the page and send user back and forward.

Seems to me like it would be a good fit for server-generated JavaScript responses.
To summarize the concept:
create a link which triggers an AJAX request to the server (using link_to with remote: true)
let the server decide whether to count the vote or increase the voted-but-not-logged-in-counter and show the modal. Based on this it will generate the corresponding JavaScript which will be executed on the client
the client receives a JavaScript response and executes it
It is your decision whether the modal functionality is already loaded from the start and the server just returns showLoginModal(); or whether the server response contains the whole JavaScript to create the modal.

Related

How can I iterate through an array of links and check information on the page to which each refers?

I have a page that lists job openings at my company. I want to use Capybara tests to click on each of the links and determine if the target page contains certain content.
I have the step below. The next step in the process checks for the content on the new page.
When("I click on each job title") do
page.all('.job-box').each do |item|
within(item) do
find('a').click
end
end
end
Once the first link is clicked the browser goes to a different page. At that point, I would like to check the content on that page, then return to the previous step to check the next link. What actually happens is that the code clicks the first link, and then attempts to click the second link. Since the first link sent it to another page, the second link is no longer present, so it returns a stale element error.
Obviously, I need a way to deal with multiple pages before continuing with the next step in the iteration, but I haven't found any documentation that addresses that problem.
You probably want to use page.go_back (or page.driver.go_back depending on the driver you're using). Something like this:
When("I click on each job title") do
page.all('.job-box').each do |item|
within(item) do
find('a').click
# do stuff
end
# roll back (granting you went but one click away)
page.go_back
end
end
As you've recognized the issue here is that the elements are stale when you return to the previous page. Depending on what limitations you're willing to accept there are multiple ways to deal with this. The bigger issue you have is that you're doing this in Cucumber which really doesn't have support for scoping/looping in its test structure. If you're willing for this to only work on a specific browser/platform you can use the respective key modifiers on click to have the browser open a new tab for each job entry and then your next step could run through all the open tabs checking those
When("I click on each job title") do
page.all('.job-box').each do |item|
item.find('a').click(:alt, :cmd) # alt,cmd click in Chrome MacOS opens in new tab
end
end
But really you're going to be better off doing something like
When("I click on each job title and verify info") do
page.all('.job-box').each do |item|
window = page.window_opened_by do
item.find('a').click(:alt, :cmd)
end
page.within_window(window) do
# verify whatever needs to be verified
end
window.close
end
end
Thomas Walpole's answer got me close enough to figure it out. Here is what ended up working:
When("I click on each job title and verify info") do
page.all('.job-box').each do |item|
new_window = window_opened_by do
url = item.find('a')[:href]
within_window open_new_window do
visit url
end
end
page.within_window(new_window) do
# do stuff
end
end
end
It doesn't close each new tab after it checks it, which could be a problem if you are working with a lot of links. I only have about 15, so it wasn't an issue for me. They all closed when the browser closed after the test finished.

Click on buttons on the page that has got the same class using capybara with siteprism

There are 20 different buttons to expect and needs to be clicked through to expect and verify the urls inside the code. I have tried different ways to implement my tests but they are failing.
I'm trying something like:
page.all(:class => 'action red').each do |button|
c = button.find(:class => 'action view red')
c.click
page.driver.browser.switch_to.window(#new_window)
expect('some element on those 20 different browsers sessions before closing them')
page.driver.browser.close
end
end
I'm getting this error:
ArgumentError: invalid keys :class, should be one of :count, :minimum,
:maximum, :between, :text, :visible, :exact, :match, :wait
Any can help me in the code how to perform get the elements of all the 20 buttons, store them and click them to expect the url each of them before closing it
Your "buttons" aren't buttons - since they are <a> elements they are actually links, styled to look like buttons.
Assuming that clicking each of these links actually opens a new window (since you're attempting to switch to a new window) then the code would be something like
page.all(:link, class: ['action', 'red']).each do |link|
win = page.window_opened_by { link.click }
page.within_window(win) do
expect(page).to ... # whatever you need to expect
end
win.close()
end
Note this doesn't use any driver specific (.driver.browser...) methods - you should stay away from them whenever possible since they are generally a sign you're doing something wrong. Additionally, the :class option wasn't universally available on all of Capybaras builtin selector types until v2.10, so you will need to be using a newer version of Capybara for that.

Reusing a cucumber step definition on a single page Angular app

I'm using watir-webdriver with the page-object gem to drive cucumber tests for a single web page app built in angular js.
The site uses a multiple stage registration process, filling a series of details each time then clicking a 'next' button. (which is enabled when all details are completed for that page)
The issue occurs when I'm attempting to reuse the following step definition:
And(/^I click next$/) do
#registration = RegistrationPage.new(#browser)
#registration.click_next
end
Which calls the page-object:
class RegistrationPage
include PageObject
button(:next, :value => "Next")
def click_next
#browser.wait_while { next_element.disabled? }
next_element.click
end
end
For the first section, the Next button is found correctly, however on the second call to the step definition, the button cannot be found, despite the button being verifiably enabled and otherwise identical.
I see that you've answered your question but in case it helps to explain it to anyone else:
The issue is likely that to watir it isn't the same next button on each page and all of the next buttons are in scope all of the time. Therefore to click the correct one you would have to use the index property of the button you want i.e. index: 1 for the second page and index 2 for the the button on the third page.

Capybara (with selenium-webdriver/firefox) doesn't always wait for page to load before looking for content

Within my site, I have two types of pages, one for each "thing" and the user's homepage.
When you visit a page for a thing, you can click a link for "Request a Thing", which sends off an ajax call and then removes the link from that page.
If you then go to your homepage, you'll see an indication that you have requested a "thing" (and the request is now in Stage1).
Some other stuff happens elsewhere and eventually the request is in Stage2.
At that point, on your homepage you'll see a link for Stage3, clicking on it changes the state of the request to Stage3. An ajax call changes the link to Stage4 without reloading the page.
If click on the Stage4 link, the state of the request is now in Stage4 and the link is removed completely.
If you then reload your homepage, the indication of the request is no longer there at all.
Here's my test for this:
describe "Clicking Through Stages" do
let(:user1) { FactoryGirl.create(:user) }
let!(:thing) { FactoryGirl.create(:thing) }
subject { page }
before do
sign_in user1
visit thing_path(thing.id)
click_on "Request a Thing"
wait_for_ajax
# This is how I mimic the "other stuff" that happens to get it to Stage2.
user.requests.first.request_states.create(state: 2)
visit user_path(user1)
click_on "Stage3"
click_on "Stage4"
visit user_path(user1)
end
self.use_transactional_fixtures = false
it "should remove the request info", js: true do
should_not have_content "Request for THG123"
end
after do
sign_out
end
end
Note on wait_for_ajax:
I was finding that some of the times, the test would fail because user.requests.first was nil, I assume this was happening because it was trying to issue the create before the ajax call for 'click on "Request a Thing"' finished (which is where the Request object is initially created), so I inserted the wait_for_ajax call based on what I read at http://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara.
Some times the test passes. When it fails, I get:
Failure/Error: should_not have_content "Request for THG123"
expected #has_content?("Request for THG123") to return false, got true
So I can tell that the "should_not have_content" is running before the final "visit user_path(user1)" has reloaded the page. I've read that this type of call should rely on Capybara's built in waiting mechanism (and I bumped up the default_wait_time to 5) even for a negative condition, but it does not appear to be doing so in this case.
Similar questions about waiting for a second page to load have offered up the kludgy solution of putting a second-page-specific expect(page).to have_content in there, but that won't work when reloading the same page.
Suggestions?

Rails form, load new record page rather than redirecting to it

I'm creating my first app in rails.
Basically, I have a new customer form, normally when you enter a new customer you are redirected to the record you created.
However as I am loading all my pages via ajax I want to load the new record in rather than re-direct to it.
I already have the form firing via ajax, I just need to know how I can access the new record URL to load it into my container.
Hope that makes sense. Thanks in advance!
You can add an option :remote => true to your form_for helper method, so that instead of page redirect the form gets posted via ajax.
For Ex :
<%= form_for(#post, :remote => true) do |f| %>
...
<% end %>
Then create a new template named create.js.erb which will get rendered after create method has been executed.
In this template, you can display the details of the new record you created.
For Ex :
$('some_element').replaceWith('<%=#posts.name %>');
Edit: You mentioned using load in your javascript. I would generally avoid this as you're making two round trips to the server. 1) Create 2) Get html to load into a div. Additionally, if there is an error that happens, it will be more difficult to catch and do the "good thing", whatever that might be.
Once you've added the remote: true option to your form, you need to deal with what happens on the server side of things.
Assuming your using REST, you'll have a controller with a create action in it. Normally, this method is creating the item and then subsequently returning the HTML for the create page. Instead of this, we need to create a javascript view for the action. This will tell the calling page what to when this action is hit.
Let's assume your form has a div called "new_record_form" and that you have another div called "new_records". You'll want to blank out the form elements in the form, effectively resetting it. You'll also want to add the new record to the "new_records" div.
To add the record to the new records div, you might do something like this.
$("#new_records").append("#{#new_record.name}");
When you submit the form, you should see this added. One great way to debug issues is to use the inspector. If you're in chrome, right click anywhere, inspect element and select network. Do this prior to form submission. You'll be able to see the ajax call and the response. Your logs will also be helpful.
Don't forget to blank out the form as well.
Note: You mentioned all your pages are ajax, but I highly suggest you evaluate if this makes 100% sense due to the various issues that result. Not saying this is the best article on the subject but might be worth a read.

Resources