Selenium+Ruby issue because of page reload - ruby-on-rails

I've the following code:
wait.until {driver.find_element(:xpath, "//input[#class='btn btn-success Testserver']")}
element = driver.find_element(:xpath, "//input[#class='btn btn-success Testserver']")
element.click
wait.until {driver.find_element(:xpath, "//input[#class='btn btn-success Testserver2']")}
element = driver.find_element(:xpath, "//input[#class='btn btn-success Testserver2']")
element.click
My problem is the 3rd and the 4th line. When selenium clicks the first element, it causes a page reload. The problem is that the 4th line (the wait.until) finds the element BEFORE the reload is executed. So what happens? Selenium thinks the element is already loaded, it try to continue and after that the pages reload and selenium throw out an error, cause it can't find the element.
Selenium::WebDriver::Error::StaleElementReferenceError: The element reference is stale. Either the element is no longer attached to the DOM or the page has been refreshed.
What can i do? The code works fine when i put a sleep between this lines, but i don't wan't to use sleep cause of bad practice. Is there another way?
Thanks for your help!

Apparently this is a well-known issue with Selenium. It's hard to wait for a new page to load if the old page and the new page both have the element you're testing for.
There are workarounds that include explicitly rescuing from StaleElementReferenceError and using that as a condition to check for so you know when the page has finished reloading. See How to get Selenium to wait for page load after a click.
In ruby it would look something like this (not tested):
def wait_until_reload(&block)
old_element = driver.find_element(:tag_name, 'html')
yield
wait.until do
begin
old_element.first(id: 'doesnt-matter')
false
rescue Selenium::WebDriver::Error::StaleElementReferenceError
true
end
end
end
wait_until_reload { element.click }

Related

Testing a vuetify on rails project with capybara selenium

I often use this site but it had never happened to me to ask a question. Now I am blocked and so it is time to ask the first one.
I need to test a sign up form created with vue 2 and vuetify, server side rendered with ruby on rails, webpack 5.
I configured capybara with selenium chrome headless driver, it works when it comes to interacting with text fields and buttons but when I try to check the checkbox:
(byebug) check('Accept')
*** Capybara::ElementNotFound Exception: Unable to find visible checkbox "Accept" that is not disabled
Vuetify hides the input and replaces it with beautiful div but, what is the best approach to check a v-checkbox?
Signup form
If I add the visible attribute, the input is found but nothing happens. I think I need to interact with some other element?
(byebug) check('Accept', visible: false)
#<Capybara::Node::Element tag="input" path="/HTML/BODY[1]/DIV[1]/DIV[1]/DIV[1]/MAIN[1]/DIV[1]/DIV[2]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[2]/DIV[2]/DIV[1]/DIV[1]/DIV[1]/INPUT[1]">
I also tried this but still nothing happen:
(byebug) page.find('input[type=checkbox]', visible: false).set(true)
#<Capybara::Node::Element tag="input" path="/HTML/BODY[1]/DIV[1]/DIV[1]/DIV[1]/MAIN[1]/DIV[1]/DIV[2]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[2]/DIV[2]/DIV[1]/DIV[1]/DIV[1]/INPUT[1]">
So I also tried the click way but getting this error:
(byebug) page.find('input[type=checkbox]', visible: false).click
*** Selenium::WebDriver::Error::ElementClickInterceptedError Exception: element click intercepted: Element <input aria-checked="false" id="input-96" role="checkbox" type="checkbox" value=""> is not clickable at point (234, 531). Other element would receive the click: <div class="v-input--selection-controls__ripple"></div>
(Session info: headless chrome=85.0.4183.121)
I tried also executing the raw script:
page.execute_script("window.uiApp.$data.terms_and_conditions = true")
The vue app is mounted in this way:
window.uiApp = new Vue({
i18n,
vuetify,
store,
router,
el: id,
render: h => h(App, {
props
})
})
But window.uiApp.$data is empty, so this attempt also seems to fail :( How to access vue component data (without vue web tool)?
I don't know what else to try, thanks in advance
Looking at the HTML shown in your linked image (in the future when asking questions it would be helpful if you included the relevant HTML directly in your question) it looks like you have a label associated with the hidden checkbox that the user can click. In that case you can use
check('Accept', allow_label_click: true)
which, when the actual checkbox is hidden, will click on the associated label instead. If you want that behavior to be on by default you can set Capybara.automatic_label_click = true.
Your other option is to determine exactly which element is actually being shown as the 'checkbox' and use find(...).click to locate that element and click on it.
I changed the checkbox in this way:
<v-checkbox v-model="terms_and_conditions"
#input='$v.terms_and_conditions.$touch()'
#blur='$v.terms_and_conditions.$touch()'
:label="$t('commons.accept')">
</v-checkbox>
<div class="ml-2">
<v-tooltip bottom>
<template v-slot:activator="{ on }">
<a
target="_blank"
href="/users/terms_and_conditions"
#click.stop
v-on="on"
>
{{ $t('session.sign_up.terms') }}
</a>
</template>
{{ $t('session.sign_up.terms_hint') }}
</v-tooltip>
</div>
Thank you

click on img capybara

Git this page https://ru.aliexpress.com/store/product/Original-xiaomi-Redmi-note3-snapdragon-650-4000mAh-13ML-1080P-3G-32G-5-5-screen-octa-core/1986585_32622877163.html?detailNewVersion=&categoryId=5090301
and i need to click on each color modification as i think. All configs and driver works fine cuz` other elements on page i can interact.
Code
page.all(:css, '.item-sku-image img').each_with_index do |mod,i|
find(:xpath,"//img[#title='#{mod['title']}']").find(:xpath, "..").click
puts find(:xpath,"//img[#title='#{mod['title']}']").find(:xpath, "..").find(:xpath, "..")['class'] # with this line i'm checkin` if i clicked on img block cuz his parend node changes it's class to active
end
Have no idea why can i click on every single item or link on this page except this block of img. ( using poltergeist )
There is no need to refind the element, and in fact it should be perfectly fine to click the img element itself.
page.all(:css, '.item-sku-image img').each_with_index do |mod,i|
mod.click # mod.find(:xpath, "./..").click if you do need to click the parent
puts mod.find(:xpath, "./ancestor::li").matches_selector?(:css, '.active')
end

Why can't my Capybara/Poltergeist test select from a jQuery autocomplete field?

UPDATE: I have fixed this problem after lots of painstaking work on my own. I am happy to be a resource to anybody needing a hand with this. Here is a gist of my working setup.
I have tried every solution I could find Google and SO. Here are some different things I have tried:
page.execute_script %Q{$('#{selector}').val('#{value}').trigger('keydown')}
and
fill_in field, with: options[:with]
page.execute_script %Q{ $('##{field}').trigger('focus') }
page.execute_script %Q{ $('##{field}').trigger('keydown') }
This is what fails:
page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
But it's definitely there when I look at it in Firebug and test it in the browser.
Here are all of the details, including a restatement of those above. Remember, the autocomplete field works fine in the browser.
listing_integration_spec.rb
require "spec_helper"
describe "Listing Integration" do
let!(:user) { login_user }
it "lets a user add information listing", js: true do
listing = create(:listing, user: user)
click_link('Additional Information')
click_link('Create')
fill_autocomplete('listings_search', with: listing.item_id)
end
end
spec/support/feature_helper.rb
def fill_autocomplete(field, options = {})
fill_in field, with: options[:with]
page.execute_script %Q{ $('##{field}').trigger('focus') }
page.execute_script %Q{ $('##{field}').trigger('keydown') }
selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains('#{options[:with]}')}
page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
page.execute_script %Q{ $("##{selector}").trigger('mouseenter').click() }
end
ERB from view template
<%= simple_fields_for :listings do |f| %>
<%= f.input :search, label: "Search by Listing", required: true %>
<% end %>
and the Coffeescript:
$("#listings_search").autocomplete
source: (request, response) ->
options =
term: request.term
$.get "/search_listings", options, (data) ->
if data.length == 0
alert "No listings found."
response data
minLength: 2
select: (event, ui) ->
add_listing_hash =
type: "GET"
url: "/add_listing"
data: { id: ui.item.id }
success: () ->
$.ajax(add_listing_hash)
JS drivers are generally meh, they're slow and not single one of them covers 100% of function, and they're often quirky and hard to debug, but I'm sure you've got that figured out by now.
I've got similar piece of code working on rails 3.2, minitest and poltergeist 1.3.0 (an ajaxed dropdown) but it kind of breaks periodically for no good reason (one might say it has a poltergeist? I have already resorted switching that test between selenium and poltergeist a couple times so far), not sure why autocompleter wouldn't work for you but it feels like a bug,
submit issue to https://github.com/jonleighton/poltergeist (you already have? https://github.com/jonleighton/poltergeist/issues/439), try changing to selenium or webkit, see if it works, you can use a different driver in this one test if it gets you out of the woods (it beats losing days of work over a widget that works).
I've found several solutions online, none of which work with current versions of Poltergeist, Capybara, and Autocomplete. But I learned enough from them to make a working helper method, with no sleep calls.
def fill_autocomplete(css_id, page, options = {})
find("#{css_id}").native.send_keys options[:with]
page.execute_script %{ $('#{css_id}').trigger('focus') }
page.execute_script %{ $('#{css_id}').trigger('keydown') }
selector = %{ul.ui-autocomplete li.ui-menu-item:contains("#{options[:select]}")}
expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item')
page.execute_script %{ $('#{selector}').trigger('mouseenter').click() }
end
Example usage:
fill_autocomplete(
'#contact_filter_company',
listing_page,
with: 'acm',
select: 'Acme'
)
I have a page argument because I'm using SitePrism - if you're not, you can strip it out.
I'm using this with:
jQuery UI Autocomplete 1.11.2
poltergeist 1.5.1
capybara 2.4.4
rspec 3.1.0
I was able to test my autocompleting text field with Poltergeist without much trouble. The main thing to know about is Poltergeist's .native.send_keys method.
Hacking together a summary out of the Cucumber steps where these lines of code actually live in my project:
find('#username').native.send_keys "the" # this field autocompletes usernames
wait_until { all('a', text: "the_username").any? }
find('a', text: "the_username").click
Then I submit the form and assert the expected results on the following page in the usual way.
wait_until (a reimplementation of a method which was removed from Capybara 2) takes a block which returns true when we should stop waiting. It's faster than waiting for 5 seconds or whatever every time.
def wait_until(delay = 1)
seconds_waited = 0
while ! yield && seconds_waited < Capybara.default_wait_time
sleep delay
seconds_waited += 1
end
raise "Waited for #{Capybara.default_wait_time} seconds but condition did not become true" unless yield
end
I think perhaps you need a mixture of triggering KEYDOWN, but also setting the keycode to DOWN.
e.g.
var keyEvent = $.Event("keydown");
keyEvent.keyCode = $.ui.keyCode.DOWN;
$("#autocomplete").val("j");
$("#autocomplete").trigger(keyEvent);
Here is a working jsfiddle example showing an item selected by the autocomplete: http://jsfiddle.net/alexkey/74BST/ I'm not sure why you need to trigger keydown twice, but that's a problem to solve separately (if a problem at all).
However I'm not familiar with the unit testing framework you are using, but I hope the above helps.
Credit goes to JQuery AutoComplete, manually select first searched item and bind click
I'm using the example code form: http://api.jqueryui.com/autocomplete/#entry-examples
The autocomplete unit tests that the jquery-ui team uses may come in useful for inspiration: https://github.com/jquery/jquery-ui/tree/master/tests/unit/autocomplete
Also a reference to the keycode: http://api.jqueryui.com/jQuery.ui.keyCode/
I had this problem and no proposed solution could solve it. My tests always failed when trying to find the ul.ui-autocomplete element. I finally noticed, that jQuery autocomplete appends the ul to the end of the html page and NOT to the input field in question. In my spec, I follow the practice of targeting my forms explicitly by within my_form do and doing all the fill_instuff inside this block:
within my_form do
fill_autocomplete …
end
Of course this could never find the ul attached OUTSIDE this form element. My solution was simple: Use jQuery autocomplete's attribute appendTo: '#id_of_input_field' when initializing autocomplete. Now it can find my uland everything works fine.

Using contextMenu plugin with fileTree plugin without losing draggable support

The server is running off Ruby on Rails. Javascript is written using Coffeescript.
I am currently using the jQuery File Tree plugin with the Context Menu plugin. Both are working at the moment, with one minor problem. I also have draggable enabled in the file tree and it will not cancel after the left click is released. In the picture below, the mouse has been released already.
Mouse up but draggable hasn't cancelled
The page this is loaded on has one div:
<div id="file_tree"></div>
And this div is filled by another page from a different controller. The code is:
<ul class="jqueryFileTree" style="display: none;">
<% #contents[0].each do |directory| %>
<li class="directory collapsed"><%= directory %></li>
<% end %>
<% #contents[1].each do |file| %>
<li class="file ext_<%= File.extname(file)[1..-1] %>"><%= file %></li>
<% end %>
</ul>
This page also loads the draggable code:
$ ->
#settings =
revert: true
helper: "clone"
containment: "#file_tree"
axis: "y"
scroll: true
cursorAt:
top: -2
$(".directory").draggable(#settings)
$(".file").draggable(#settings)
And the context menu is added like this:
$("#file_tree").contextMenu { menu: 'filetree_context_menu' }, (action, element, position) =>
#on_context_menu(action, element, position, data.root_directory)
Where #on_context_menu is simply a function (it has no bearing on this problem though).
The problem can be stopped (the clone returns to its original) if you click outside the #file_tree div. However, it appears that click, mousedown, and mouseup events are handled correctly. Also, the context menu has no problems and will not induce the draggable problem.
In regards to my searching around for fixes, I have found nothing. The File Tree plugin has an okay amount of information floating around, but the Context Menu one seems to have next to nothing (must be unpopular?). And none of those have both at the same time. If you need some other information, please state.
EDIT: I figured out that this is actually because the plugin needs to call e.stopPropogation() on the mouseup event to prevent it from opening a real context menu. Anybody know how to manually perform a stopdrag() event? It's not working if I call $(this).trigger('stopdrag') in case you were going to suggest that.
Eventually fixed this problem (after quite a while, and while procrastinating to get a Tumbleweed). Just added a check that it was the right button that was clicked, because it was breaking drag by running e.stopPropagation() on BOTH clicks. It's much cleaner than my original work-around that I scrapped.
if(e.button == 2) {
e.stopPropagation();
}

Mechanize not recognizing anchor tags via CSS selector methods

(Hope this isn't a breach of etiquette: I posted this on RailsForum, but I haven't been getting much response from there recently.)
Has anyone else had problems with Mechanize not recognizing anchor tags via CSS selectors?
The HTML looks like this (snippet with white space removed for clarity):
<td class='calendarCell' align='left'>
10
<p style="margin-bottom:15px; line-height:14px; text-align:left;">
<span class="sidenavHeadType">
Current Events</span><br />
<b><a href="http://www.mysite.org/index.php/site/
Clubs/banks_and_the_fed" class="a2">Banks and the Fed</a></b>
<br />
10:30am- 11:45am
</p>
I'm trying to collect the data from these events. Everything is working except getting the anchor within the <p>. There's clearly an <a> tag inside the <b>, and I'm going to need to follow that link to get further details on this event.
In my rake task, I have:
agent.page.search(".calendarCell,.calendarToday").each do |item|
day = item.at("a").text
item.search("p").each do |e|
anchor = e.at("a")
puts anchor
puts e.inner_html
end
end
What's interesting is that the item.at("a") always returns the anchor. But the e.at("a") returns nil. And when I do inner_html on the p element, it ignores the anchor entirely. Example output:
nil
<span class="sidenavHeadType">
Photo Club</span><br><b>Indexing Slide Collections</b>
<br>
2:00pm- 3:00pm
However, when I run the same scrape directly with Nokogiri:
doc.css(".calendarCell,.calendarToday").each do |item|
day = item.at_css("a").text
item.css("p").each do |e|
link = e.at_css("a")[:href]
puts e.inner_html
end
end
It recognizes the inside the , and it will return the href, etc.
<span class="sidenavHeadType">
Bridge Party</span><br><b>Party Bridge</b>
<br>
7:00pm- 9:00pm
Mechanize is supposed to use Nokogiri, so I'm wondering if I have a bad version or if this affects others as well.
Thanks for any leads.
Never mind. False alarm. In my Nokogiri task, I was pointing to a local copy of the page that included the anchors. The live page required a login, so when I browsed to it, I could see the a tags. Adding the login to the rake task solved it.

Resources