I am attempting to test my rails app's alert messages. I have the following code that is produced inside of Twitter Bootstrap:
<div class="alert fade in alert-success">
<button class="close" data-dismiss="alert">×</button>
Signed out successfully.
</div>
What I am trying to do is pull out just the "Signed out successfully."
At the moment I have:
def alert_text
#page.find('div.alert').text
end
But that gives me "× Signed in successfully."
In my test I am doing:
assert_equal "Signed in successfully.", my_page.alert_text
If I change the assertion to include the ×, I am getting a invalid multibyte char (US-ASCII) error. Is there an easy way to pull out the text and ignore the button? I would wrap the text in a separate div, but it is generated in the gem so I can't.
Since its hard to see what else is going on, I would add a custom element with id="signedOut" or something around the specific text, and access it by id. It won't mess with your boostrap styling.
Alternatively, try being more specific in the find. You may be using two classes that start with alert.
def alert_text
#page.find('div.alert fade in alert-success').text
end
If you have two div's using this class, you can add on to the end of it or just go into the class definition in your bootstrap folder and add one more layer to it. (Bootstrap uses js and css just like the rest of us, although it helps get things off the ground pretty quickly).
Related
I'm looking to add files to an <input type="file">
Here is a snippet of the html
<span class="btn btn-xs btn-primary btn-file"> #found
<span class="blahicon blahicon-upload"></span>
Browse
<input type="file" data-bind="value: fileName, event: { change: uploadImagesOnChange }"
accept="blah/txt" multiple=""> #not found
</span>
and here's the capybara and ruby
within_frame('frame1') do
within_frame('frame2') do
within(:xpath, [containing span xpath]) do # finds this
find(:xpath, './/*[#type="file"]').send_keys('C:\Users\...\blah.txt') #ElementNotFound
end
end
end
I see no hidden block and it's super scoped. Any thoughts?
Instead of using within(:xpath, [containing span xpath]) can you directly check the xpath of input like: has_xpath?(".//span[contains(text(),'Browse')]/input")
and if it is returning true then try with using
find(:xpath, ".//span[contains(text(),'Browse')]/input").send_keys ('C:\Users\...\blah.txt')
If you are aware about the 'pry' gem then you can debug this thing by trying various xpath combinations instead of just running the whole script, So you will get to know the actual problem.
I think there is mistake in your xpath
.//*[#type="file"]
Change to
.//*[#type='file']
as browser can detect attribute value with double quotes(" ") but inside script you need to use it in single quote (' ')
Also can make different combinations of your XPath like
//input[#type='file']
Judging by the class btn-file on the wrapper class, it's probable you're using Bootstrap and one of the "standard" methods to hide the actual file input element so that it can be style the same across multiple browsers. There are a number of ways of hiding the button from just setting display:none on it to the more "modern" method of expanding it to the same size as the replacement button and seeting it's opacity to 0 so it become a transparent overlay on the replacement.
The basic strategy to dealing with this kind of setup in Capybara is to use execute_script to make the element visible first and then use attach_file or set as normal. For instance if your site is using the opacity method of hiding the file element you could do something like
within_frame('frame1') do
within_frame('frame2') do
within(:xpath, [containing span xpath]) do # finds this
file_input = find(:file, visible: :all)
page.driver.browser.execute_script("$(arguments[0]).css('opacity',1)", file_input.native)
file_input.set('C:\Users\...\blah.txt')
end
end
end
Note - this code assumes you're using jQuery in your page and will only work with the selenium driver since it uses seleniums driver specific ability to pass elements from capybara to selenium in the execute_script call. If not using jQuery the JS will need to change and if using another driver you would need to find the element in the JS script using DOM methods and then modify its opacity.
I want to scrape all the names of the users who commented below a youtube video.
I'm using ruby and nokogiri.
require 'rubygems'
require 'nokogiri'
require 'open-uri'
url = "https://www.youtube.com/watch?v=tntOCGkgt98"
doc = Nokogiri::HTML(open(url))
doc.css(".comment-thread-renderer > .comment-renderer").each do |comment|
name = comment.css("#comment-section-renderer-items .g-hovercard").text
puts name
end
But it's not working, I'm not getting any output, no error either.
I won't be able to give you a solution, but at least I can give you a couple of hints that may help you to move forward.
The code you have is not working because the comments section is loaded via an ajax call after the page is loaded. If you do a hard reload in your browser, you will see that there is a spinner icon and a Loading... text in the sections comment, waiting for the content to be loaded. When Nokogiri gets the page via the http request, it gets the html content that you see before the comments are loaded. As a matter of fact the place where the contents will be later added looks like:
<div id="watch-discussion" class="branded-page-box yt-card">
<div id="comment-section-renderer"
class="comment-section-renderer vve-check"
data-visibility-tracking="CCsQuy8iEwjr3P3u1uzNAhXIepAKHRV9D8Ao-B0=">
<div class="action-panel-loading">
<p class="yt-spinner ">
<span class="yt-spinner-img yt-sprite" title="Loading icon">
</span>
<span class="yt-spinner-message">Loading...</span>
</p>
</div>
</div>
</div>
That is the reason why you won't find the divs you are looking for, because they aren't part of the html you have.
Looking at the network console in the browser, it seems that the ajax request to get the comments data is being sent to https://www.youtube.com/watch_fragments_ajax?v=tntOCGkgt98&tr=time&distiller=1&ctoken=EhYSC3RudE9DR2tndDk4wAEAyAEA4AEBGAY%253D&frags=comments&spf=load. As you can see the v parameter is the video id, however there are a couple of caveats:
There is a ctoken param, which you can get by scraping the original page contents. It is inside a <script> tag, in the form of
'COMMENTS_TOKEN': "<token>".
However, you still need to send a session_token as a form data in the body of the AJAX request (which is a POST). That I don't know where is coming from :(.
I think that you will be pushing the limits of Nokogiri here, as AFAIK it is not intended to follow ajax requests or handling Javascript. Maybe the ruby Selenium driver is better suited for this.
HTH
I think you need name.css("#comment-section..."
The each statement will iterate over the elements, using the variable name.
You may want to use node instead of name:
doc.css(".comment-thread-renderer > .comment-renderer").each do |node|
name = node.css("#comment-section-renderer-items .g-hovercard").text
puts name
end
I wrote this rails app using nokogiri to see all the tags that a page has before any javascript is run in the browser. The source code is here, so you can adjust it if you need to add more info about the node in the view.
That can easily tell you if the particular tag element that you are looking for is something you can retrieve without having to do some JS eval.
Most web crawlers don't support client-side rendering, which gives you an idea that it's not a trivial task to execute JS when scraping content.
YouTube is a dynamically rendered JavaScript website, though it could be parsed with Nokogiri without using Selenium or another package. Try open the Network tab in dev tools, scroll to the comment section, and see what request being send.
You need to make a post request in order to fetch comments data. You can preview the output in the "Preview" tab.
Preview output:
Which is equivalent to this comment:
Note: Since this comment brings very little value, this answer will be updated with the attached code once there will be an available solution.
Iam working with test automation .for that am using ruby capybara to write test scripts.Using
Ruby cabybara code i want to check a text is present is not inside dev element
how can i possible?
<div class="modal-header">
<h3 class="orderCompleteNoEmailLabel">
Your order is placed, but one more step is needed to complete it.
</h3>
</div>
Here i want to check the text Your order is placed, but one more step is needed to complete it. is present or not.
the following xpath should return true if the text contains the given string
//h3[contains(., "Your order is placed")]=true()
Iam working with test automation .for that am using ruby capybara to wite test scripts.Using
Ruby cabybara code i want to check a text is present is not inside dev element
how can i possible?
<div class="modal-header">
<h3 class="orderCompleteNoEmailLabel">
Your order is placed, but one more step is needed to complete it.
</h3>
</div>
Here i want to check the text Your order is placed, but one more step is needed to complete it. is present or not.
Personally I would use this:
page.should have_css('div.modal-header', :text => "Your order is placed, but one more step is needed to complete it.")
you could also use:
page.should have_content('Your order is placed, but one more step is needed to complete it.')
Here is a link to a pretty useful list for capybara methods.
https://gist.github.com/zhengjia/428105
I'm having a problem getting the back button function to work in Rhomobile.
I've tried various methods of url_for(:index, :back => ....) etc etc and nothing seems to work. The problem with this method is (even if it worked) that it only allows navigation to a set place, rather than a dynamic history/back navigation.
The closest I've come to a working solution is by using this in the application_helper:
def page_back
WebView.navigate_back
end
and then Back in the view.
This works, and i can navigate across views and even controllers. However, it generates a "Error loading page" error, even though it does actually render the right page...
Does anybody have any ideas?
Ok this is what I did in the end. I've decided against using rhodes now but here is what I came up with for this problem:
Added data-add-back-btn="true" to:
<div data-role="page" data-add-back-btn="true">
Then:
<div data-role="header" data-position="inline">
<h1>Title</h1>
Back
</div>