Watir and Cucumber. link not clickable - html

I am new to Watir and Cucumber, and I am trying to run an automation to create Live IDs. Below is the HTML for the link that I want to click, the "New" text is what it is showing on webpage. It would lead me to the form to add new contact to my Live account.
<ul class="c_cc" role="presentation" styple="overflow:visible;">
<li class="c_sm c_mcp" id = "new">
<a id href="#" class="c_nobdr t_prs">
<span class="is_c" dir="ltr" style="padding-right: 5px;">
<img class="is_img" src="https://p.pfx.ms/is/invis.gif" onload="this.onload=null;$Do.when('$IS.Init',0,this);" style="width:26px;height:26px;background-position:-1px -1px;background-image:url('https://p.pfx.ms/h/command4.png');" alt="New contact" title />
</span>
"New"
</a>
<span class="c_ms"></span>
</li>
</ul>
the watir code I wrote to click the "New" is below:
#browser.div(:id, "c_header").div(:id, "c_cb0").ul(:class, "c_cc").span(:text, "is_c").when_present.click
I get this error:
Watir::Wait::TimeoutError: timed out after 30 seconds, waiting for {:id=>"is_c", :tag_name=>"span"} to become present
Then I tried below code:
#browser.div(:id, "c_header").div(:id, "c_cb0").ul(:class, "c_cc").span(:text, "New").when_present.click
but this code does not really clicking on the "New" link, so the next form won't show up, and the rest of the code cannot run. Does anyone know any solution to this problem?
I found out a new window popped up, so it could not find the element in the old window. Thanks guys for helping.

In the first watir code snippet, a :text locator is being used for the .span method instead of a :class locator. For example:
browser.ul(:class, "c_cc").span(:text, "is_c").exists? #=> false
browser.ul(:class, "c_cc").span(:class, "is_c").exists? #=> true
In the second watir code snippet, a :text locator with a value of "New" is being used for the .span method. In this case, a .link method should be used. Additionally, the string includes double-quotes, so the double-quotes must be escaped if enclosed in another set of double-quotes (or enclose in single-quotes). For example:
browser.ul(:class, "c_cc").span(:text, "New").exists? #=> false
browser.ul(:class, "c_cc").span(:text, "\"New\"").exists? #=> false
browser.ul(:class, "c_cc").link(:text, "\"New\"").exists? #=> true
browser.ul(:class, "c_cc").link(:text, '"New"').exists? #=> true
So, one of the following examples should work:
browser.link(:text, "\"New\"").when_present.click
browser.link(:text, '"New"').when_present.click
browser.link(:class, "c_nobdr t_prs").when_present.click

Wow. This is old and I have encountered the same behavior. First, this is in a Cucumber step definition. The following works perfectly in open Ruby code. It simply refuses to function as a step.
It finds the link in the table and clicks it. Click doesn't function.
link = browser.table(class: 'alert-table').tbody.rows[1].cells.last.link(text: 'View')
# <Watir::Anchor: located: false; {:class=>"alert-table", :tag_name=>"table"} --> {:tag_name=>"tbody"} --> {:index=>1} --> {:index=>-1} --> {:text=>"View", :tag_name=>"a"}>
link.click
Ruby 2.4
watir (6.16.5)
regexp_parser (~> 1.2)
selenium-webdriver (~> 3.6)

Related

Can't dynamically add an id to div tag in rails helper

I'm having a very weird problem. Here's my view:
<h1>All Deals</h1>
<%= sanitize print_grouped_deals(#deals) %>
Here's my deals_helper.rb
def print_grouped_deals(grouped_deals_by_date)
grouped_deals_by_date.map do |(date, deals)|
%(<div id='#{date.to_s}-deals'>
<h3>#{brief_time date}</h3>
#{deal_paragraphs_for_group(deals)}</div>)
end.join
end
def deal_paragraphs_for_group(deals)
deals.map do |deal|
%(<p>#{"<span class='warning'>POSSIBLY EXPIRED! -</span>" if deal.probably_expired?} #{link_to deal.headline, deal}</p>)
end.join
end
Of note is the 3rd line in the first method in the second snippet. I cannot get it to add an id to my div tag! If I change <div id='#{date.to_s}-deals'> to <div class='#{date.to_s}-deals'> it adds the class no problem but if I keep it as id= then it just creates a simple <div> tag with no attributes.
Lest we imagine it's something to do with generating multiple divs with ids (although the ids will be different), I've also tried generating a simple <div id="thing" /> from the helper, and I get the same empty div tags as a result.
WTF?
You have to pass a whitelist of attributes to the sanitize helper https://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html#method-i-sanitize
To allow id attribute
<%= sanitize print_grouped_deals, attributes: %w(id) %>
To set the default allowed tags or attributes across your application
# In config/application.rb
config.action_view.sanitized_allowed_tags = ['div', 'h3']
config.action_view.sanitized_allowed_attributes = ['id', 'class']

Accessing sequential same elements in Watir Ruby

I am trying to access sequential elements and don't know how to advance the the script down the page. So, here is a snippet of the code.
<div class="card highlighted">
<div class="card-fix">
<a class="card-remove icon" title="Don't show member again" onclick="$jq( '#search-results' ).msg( 'remove', this )" data-userid="93872246">
X
</a>
<dl></dl>
<div class="card-actions">
<a class="quickview" title="Quick view" onclick="$jq('#dialog-profile').msg('show', '/profileinfo/SearchProfi…lTseEdmEQ==&lid=1&searchType=S&pageNumber=1'); return false;" href="#"></a>
<a class="button button-primary" title="Favorite her!" href="/matchbook/addEntry.aspx?uid=Mu/BwYPn0nxhWlTseEdmEQ==&pn=1&rn=4&tp=S&handle=Annie5170&lid=1065"></a>
</div>
</div>
<div class="card-fix">
<a class="card-remove icon" title="Don't show member again" onclick="$jq( '#search-results' ).msg( 'remove', this )" data-userid="108272583"></a>
<dl></dl>
<div class="card-actions">
<a class="quickview" title="Quick view" onclick="$jq('#dialog-profile').msg('show', '/profileinfo/SearchProfi…DA3R3daaQ==&lid=1&searchType=S&pageNumber=1'); return false;" href="#"></a>
<a class="button button-primary" title="Favorite her!" href="/matchbook/addEntry.aspx?uid=y98x/Mj+hc+61DA3R3daaQ==&pn=1&rn=4&tp=S&handle=dancer4498&lid=1065"></a>
</div>
</div>
In other words I need to continue looping through these quickviews and then running a small script and doing it all again. Then go to the next page and do it again...etc. However, if I use this now in a loop it will only pick up the first quickview 5 times.
# 5.times do |n|
# browser.link(:title => 'Quick view').click
# sleep (2)
# browser.link(:class => "icon dialog-abandon").click
# # sleep (2)
# end
So my question is how can I have it advance through all of the quick views which there are 18 per page.
It would also be great to be able to save the
data-user-id="93872246"
and the portion after the handle in the href, ie.
handle=Annie5710
in a file in a hash as well.
Thanks and I appreciate all of the help.
You have to do this:
//get total title in a page
totalTitleSize=browser.link(:title => 'Quick view').size
and looping it:
totalTitleSize.times do |n|
browser.link(:title => 'Quick view', :index=> n).click
sleep (2)
//test it or use index in it If you need
browser.link(:class => "icon dialog-abandon").click
sleep (2)
end
this should be added to the code.
browser.link(:title => 'Quick view', :index=> n).click
now everything works fine.
As the other answers show, the problem is that the line:
browser.link(:title => 'Quick view').click
Will always click the first matching link. Using the :index allows you to override the default first match. However, I would argue the better solution is to get a collection of matching links and iterate through that. It will save you from manually handling the index.
browser.links(:title => 'Quick view').each do |link|
link.click
sleep (2)
browser.link(:class => "icon dialog-abandon").click
sleep (2)
end
Notice here that we have created a collection using links that we iterate though using each.
However, since you also want to collect other data, you might want to iterate through the parent divs:
browser.divs(:class => 'card-fix').each do |parent_div|
puts parent_div.link(:class => 'card-remove').data_userid
#=> "93872246"
href = parent_div.button(:class => 'button-primary').href
puts href[/handle=([^&]+)/, 1]
#=> "Annie5170"
parent_div.link(:class => "quickview").click
sleep (2)
browser.link(:class => "icon dialog-abandon").click
sleep (2)
end

Controlling the existence of an attribute

I have a problem with the Slim template engine in a Sinatra project. I have an edit form to be filled when the route is triggered. There is an issue with HTML select option. I need something like this when the edit form is loaded. Notice that Mrs. option is selected:
<select name="person[title]" id="person[title]">
<option value="Mr.">Mr.</option>
<option value="Mrs." selected>Mrs.</option>
</select>
I tried:
option[value="Mrs." "#{person.title == :mrs ? 'selected' : ''}"]
The exception was about an attribute error. Then I tried something like this:
option[value="Mrs." selected="#{person.title == :mrs ? true : false}"]
but then the output was something like this:
<option value"Mrs." selected="false">Mrs.</option>
I guess the string"false" is interpreted as true. That failed. I tried some combinations with round brackets but couldn't get it to work.
How could I set the selected attribute of an option in a select list in Slim?
For an attribute, you can write ruby code after the =, but if the ruby code has spaces in it, you have to put parentheses around the ruby code:
option[value="1" selected=("selected" if #title=="Mrs.")] "Mrs."
See "Ruby attributes" here: http://rdoc.info/gems/slim/frames.
The brackets are optional, so you can also write it like this:
option value="1" selected=("selected" if #title=="Mrs.") "Mrs."
Or, instead of brackets, you can use a different delimiter:
option {value="1" selected=("selected" if #title=="Mrs.")} "Mrs."
Here it is with some code:
slim.slim:
doctype html
html
head
title Slim Examples
meta name="keywords" content="template language"
body
h1 Markup examples
p This example shows you how a basic Slim file looks like.
select
option[value="1" selected=("selected" if #title=="Mr.")] "Mr."
option[value="2" selected=("selected" if #title=="Mrs.")] "Mrs."
Using Slim in a standalone ruby program without rails:
require 'slim'
template = Slim::Template.new(
"slim.slim",
pretty: true #pretty print the html
)
class Person
attr_accessor :title
def initialize title
#title = title
end
end
person = Person.new("Mrs.")
puts template.render(person)
--output:--
<!DOCTYPE html>
<html>
<head>
<title>
Slim Examples
</title>
<meta content="template language" name="keywords" />
</head>
<body>
<h1>
Markup examples
</h1>
<p>
This example shows you how a basic Slim file looks like.
</p>
<select><option value="1">"Mr."</option><option selected="selected" value="2">"Mrs."</option></select>
</body>
</html>
I guess the string "false" is interpreted as true.
Yes. The only things that evaluate to false are false itself and nil. Any number(including 0), any string (including ""), and any array(including []), etc. are all true.
Not pertinent to your problem, but perhaps useful to some future searcher...I guess Slim looks up instance variables in whatever object you pass as an argument to render. So if you want to provide a whole bunch of values for the template, you can write:
require 'slim'
template = Slim::Template.new(
"slim.slim",
pretty: true #pretty print the html
)
class MyVals
attr_accessor :count, :title, :animals
def initialize count, title, animals
#count = count
#title = title
#animals = animals
end
end
vals = MyVals.new(4, "Sir James III", %w[ squirrel, monkey, cobra ])
puts template.render(vals)
slim.slim:
doctype html
html
head
title Slim Examples
meta name="keywords" content="template language"
body
p =#count
p =#title
p =#animals[-1]
Neither OpenStruct nor Struct work with render() even though they seem like natural candidates.

Indenting generated markup in Jekyll/Ruby

Well this is probably kind of a silly question but I'm wondering if there's any way to have the generated markup in Jekyll to preserve the indentation of the Liquid-tag. World doesn't end if it isn't solvable. I'm just curious since I like my code to look tidy, even if compiled. :)
For example I have these two:
base.html:
<body>
<div id="page">
{{content}}
</div>
</body>
index.md:
---
layout: base
---
<div id="recent_articles">
{% for post in site.posts %}
<div class="article_puff">
<img src="/resources/images/fancyi.jpg" alt="" />
<h2>{{post.title}}</h2>
<p>{{post.description}}</p>
Read more
</div>
{% endfor %}
</div>
Problem is that the imported {{content}}-tag is rendered without the indendation used above.
So instead of
<body>
<div id="page">
<div id="recent_articles">
<div class="article_puff">
<img src="/resources/images/fancyimage.jpg" alt="" />
<h2>Gettin' down with responsive web design</h2>
<p>Everyone's talking about it. Your client wants it. You need to code it.</p>
Read more
</div>
</div>
</div>
</body>
I get
<body>
<div id="page">
<div id="recent_articles">
<div class="article_puff">
<img src="/resources/images/fancyimage.jpg" alt="" />
<h2>Gettin' down with responsive web design</h2>
<p>Everyone's talking about it. Your client wants it. You need to code it.</p>
Read more
</div>
</div>
</div>
</body>
Seems like only the first line is indented correctly. The rest starts at the beginning of the line... So, multiline liquid-templating import? :)
Using a Liquid Filter
I managed to make this work using a liquid filter. There are a few caveats:
Your input must be clean. I had some curly quotes and non-printable chars that looked like whitespace in a few files (copypasta from Word or some such) and was seeing "Invalid byte sequence in UTF-8" as a Jekyll error.
It could break some things. I was using <i class="icon-file"></i> icons from twitter bootstrap. It replaced the empty tag with <i class="icon-file"/> and bootstrap did not like that. Additionally, it screws up the octopress {% codeblock %}s in my content. I didn't really look into why.
While this will clean the output of a liquid variable such as {{ content }} it does not actually solve the problem in the original post, which is to indent the html in context of the surrounding html. This will provide well formatted html, but as a fragment that will not be indented relative to tags above the fragment. If you want to format everything in context, use the Rake task instead of the filter.
-
require 'rubygems'
require 'json'
require 'nokogiri'
require 'nokogiri-pretty'
module Jekyll
module PrettyPrintFilter
def pretty_print(input)
#seeing some ASCII-8 come in
input = input.encode("UTF-8")
#Parsing with nokogiri first cleans up some things the XSLT can't handle
content = Nokogiri::HTML::DocumentFragment.parse input
parsed_content = content.to_html
#Unfortunately nokogiri-pretty can't use DocumentFragments...
html = Nokogiri::HTML parsed_content
pretty = html.human
#...so now we need to remove the stuff it added to make valid HTML
output = PrettyPrintFilter.strip_extra_html(pretty)
output
end
def PrettyPrintFilter.strip_extra_html(html)
#type declaration
html = html.sub('<?xml version="1.0" encoding="ISO-8859-1"?>','')
#second <html> tag
first = true
html = html.gsub('<html>') do |match|
if first == true
first = false
next
else
''
end
end
#first </html> tag
html = html.sub('</html>','')
#second <head> tag
first = true
html = html.gsub('<head>') do |match|
if first == true
first = false
next
else
''
end
end
#first </head> tag
html = html.sub('</head>','')
#second <body> tag
first = true
html = html.gsub('<body>') do |match|
if first == true
first = false
next
else
''
end
end
#first </body> tag
html = html.sub('</body>','')
html
end
end
end
Liquid::Template.register_filter(Jekyll::PrettyPrintFilter)
Using a Rake task
I use a task in my rakefile to pretty print the output after the jekyll site has been generated.
require 'nokogiri'
require 'nokogiri-pretty'
desc "Pretty print HTML output from Jekyll"
task :pretty_print do
#change public to _site or wherever your output goes
html_files = File.join("**", "public", "**", "*.html")
Dir.glob html_files do |html_file|
puts "Cleaning #{html_file}"
file = File.open(html_file)
contents = file.read
begin
#we're gonna parse it as XML so we can apply an XSLT
html = Nokogiri::XML(contents)
#the human() method is from nokogiri-pretty. Just an XSL transform on the XML.
pretty_html = html.human
rescue Exception => msg
puts "Failed to pretty print #{html_file}: #{msg}"
end
#Yep, we're overwriting the file. Potentially destructive.
file = File.new(html_file,"w")
file.write(pretty_html)
file.close
end
end
We can accomplish this by writing a custom Liquid filter to tidy the html, and then doing {{content | tidy }} to include the html.
A quick search suggests that the ruby tidy gem may not be maintained but that nokogiri is the way to go. This will of course mean installing the nokogiri gem.
See advice on writing liquid filters, and Jekyll example filters.
An example might look something like this: in _plugins, add a script called tidy-html.rb containing:
require 'nokogiri'
module TextFilter
def tidy(input)
desired = Nokogiri::HTML::DocumentFragment.parse(input).to_html
end
end
Liquid::Template.register_filter(TextFilter)
(Untested)

Rails nested html helper

I'm creating a helper in my rails app where I create a navigation link.
Now I want to add an caret to this link so I can get a nice arrow at the end.
My html should look like this:
<li class='dropdown'>
<a class='dropdown-toggle' data-toggle='dropdown' href='#'
Dropdown
<b class='caret'></b>
</a>
Now I got my helpers setup like this:
content_tag(:li, class: 'active dropdown') do
link_to( text, link, class: 'dropdown-toggle' ) do
content_tag(:b, class: 'caret')
end
end
But when I do this I got this error message:
undefined method `stringify_keys' for "/":String
I also want to add some item to my dropdown so I need to nested some more but I don't know how. Is there anybody who could help me and point me in the right direction?
Thanks!
You’re passing a block to link_to so you shouldn’t pass it link text, as shown in the docs. Try this:
content_tag(:li, class: 'active dropdown') do
link_to(link, class: 'dropdown-toggle' ) do
"#{text}#{content_tag(:b, "", class: 'caret')}".html_safe
end
end