Watir cycle through class elements - watir

How can I cycle through all the elements?
I'm looking for something like:
brower.text_field[0](:name, "asdf").click # get the first element
brower.text_field[1](:name, "asdf").click # get the second element
Is there a good documentation for more advanced stuff?
I haven't found anything useful all I got is simple stuff but I'm looking for something where I can chain elements like:
browser.tr(:id, "asdf").td.click
Thank you for your time.

For what you have described you can simply use the :index attribute:
brower.text_field(:name => "asdf", :index => 0).click # get the first element
brower.text_field(:name => "asdf", :index => 1).click # get the second element
Or loop over all text_fields with attribute :name => "asdf":
browser.text_fields(:name => "asdf").each { |elem| elem.click }

To cycle through all matching elements, you are looking for "element collections".
Basically you need to pluralize the method used to get elements and then you can use the [] to get a specific index:
brower.text_fields(:name, "asdf")[0].click # get the first element
brower.text_fields(:name, "asdf")[1].click # get the second element
The element collection includes Enumerable, so there are also a variety of methods for iterating.
In terms of documentation, you can look into:
The Ruby Docs - http://rubydoc.info/gems/watir-webdriver
There are also a couple of in progress books on Leanpub if you search for Watir - https://leanpub.com/book_search?search=watir

Related

Remove a map item in terraform

If I use the following yamldecode, I get the following output:
output "product" {
value = distinct(yamldecode(file("resource/lab/abc.yaml"))["account_list"]["resource_tags"][*]["TAG:product"])
}
Output:
+ product = [
+ "fargate",
+ "CRM",
]
I want fargate to be removed from my output and the expected output is this:
+ product = [
+ "CRM"
]
Please let me know how I can do this.
output "product" {
value = compact([for x in distinct(yamldecode(file("resource/lab/abc.yaml"))["account_list"]["resource_tags"][*]["TAG:product"]) : x == "fargate" ? "" : x])
}
I get this output:
test = [
"enter",
]
compact function solved the problem.
The Terraform language is based on functional principles rather than imperative principles and so it does not support directly modifying an existing data structure. Instead, the best way to think about goals like this is how to define a new value which differs from your existing value in some particular way.
For collection and structural types, the most common way to derive a new value with different elements is to use a for expression, which describes a rule for creating a new collection based on the elements of an existing collection. For each element in the source collection we can decide whether to use it in the result at all, and then if we do decide to use it we can also describe how to calculate a new element value based on the input element value.
In your case you seem to want to create a new list with fewer elements than an existing list. (Your question title mentions maps, but the question text shows a tuple which is behaving as a list.)
To produce a new collection with fewer elements we use the optional if clause of a for expression. For example:
[for x in var.example : x if x != "fargate"]
In the above expression, x refers to each element of var.example in turn. Terraform first evaluates the if clause, and expects it to return true if the element should be used. If so, it will then evaluate the expression immediately after the colon, which in this case is just x and so the result element is identical to the input element.
Your example expression also includes some unrelated work to decode a YAML string and then traverse to the list inside it. That expression replaces var.example in the above expression, as follows:
output "products" {
value = toset([
for x in yamldecode(file("${path.module}/resource/lab/abc.yaml"))["account_list"]["resource_tags"][*]["TAG:product"]: x
if x != "fargate"
])
}
I made some other small changes to the above compared to your example:
I named the output value "products" instead of "product", because it's returning a collection and it's Terraform language idiom to use plural names for collection values.
I wrapped the expression in toset instead of distinct, because from your description it seems like this is an unordered collection of unique strings rather than an ordered sequence of strings.
I added path.module to the front of the file path so that this will look for the file in the current module directory. If your output value is currently in a root module then this doesn't really matter because the module directory will always be the current working directory, but it's good practice to include this so that it's clear that the file belongs to the module.
Therefore returning a set is more appropriate than returning a list because it communicates to the caller of the module that they may not rely on the order of these items, and therefore allows the order to potentially change in future without it being a breaking change to your module.

How to get text which is inside the span tag using selenium webdriver?

I want to get the text which is inside the span. However, I am not able to achieve it. The text is inside ul<li<span<a<span. I am using selenium with python.
Below is the code which I tried:
departmentCategoryContent = driver.find_elements_by_class_name('a-list-item')
departmentCategory = departmentCategoryContent.find_elements_by_tag_name('span')
after this, I am just iterating departmentCategory and printing the text using .text i.e
[ print(x.text) for x in departmentCategory ]
However, this is generating an error: AttributeError: 'list' object has no attribute 'find_elements_by_tag_name'.
Can anyone tell me what I am doing wrong and how I can get the text?
Problem:
As far as I understand, departmentCategoryContent is a list, not a single WebElement, then it doesn't have the find_elements_by_tag_name() method.
Solution:
you can choose 1 of 2 ways below:
You need for-each of list departmentCategoryContent first, then find_elements_by_tag_name().
Save time with one single statement, using find_elements_by_css_selector():
departmentCategory = driver.find_elements_by_css_selector('.a-spacing-micro.apb-browse-refinements-indent-2 .a-list-item span')
[ print(x.text) for x in departmentCategory ]
Test on devtool:
Explanation:
Your locator .a-list-item span will return all the span tag belong to the div that has class .a-list-time. There are 88 items containing the unwanted tags.
So, you need to add more specific locator to separate the other div. In this case, I use some more classes. .a-spacing-micro.apb-browse-refinements-indent-2
You're looping over the wrong thing. You want to loop through the 'a-list-item' list and find a single span element that is a child of that webElement. Try this:
departmentCategoryContent = driver.find_elements_by_class_name('a-list-item')
print(x.find_element_by_tag_name('span').text) for x in departmentCategoryContent
note that the second dom search is a find_element (not find_elements) which will return a single webElement, not a list.

Filter the list generated by a Gremlin traversal and Groovy

I'm doing the following traversal:
g.V().has('Transfer','eventName','Airdrop').as('t1').
outE('sent_to').
inV().dedup().as('a2').
inE('sent_from').
outV().as('t2').
where('t1',eq('t2')).by('address').
outE('sent_to').
inV().as('a3').
select('a3','a2').
by('accountId').toList().groupBy { it.a3 }.collectEntries { [(it.key): [a2 : it.value.a2]]};
So as you can see I'm basically doing a traversal and at the end I'm using groovy with collectEntries to aggregate the results like I need them, which is aggregated by a3 in this case. The results look like this:
==>0xfe43502662ce2adf86d9d49f25a27d65c70a709d={a2=[0x99feb505a8ed9976cf19e757a9536117e6cdc5ba, 0x22019ad32ea3adabae68003bdefd099d7e5e3886]}
(This is GOOD, because the number of values in a2 is at least 2)
==>0x129e0131ea3cc16fe5252d7280bd1258f629f20f={a2=[0xf7958fad496d15cf9fd9e54c0012504f4fdb96ff]}
(This is NOT GOOD, I want to return in my list only those combinations where there are at lest 2 values for a2)
I have tried using filters and an additional where step in the traversal itself but I haven't been able to do it. I'm not sure if this is something I should skip using Groovy in my last line. Any help or orientation would be very much appreciated
I don't think you need to drop into Groovy to get the answer you want. It would be preferable to do this all in Gremlin especially since you intend to filter results which could yield some performance benefit. Gremlin has it's own group() step as well as methods for filtering the resulting Map:
g.V().has('Transfer','eventName','Airdrop').as('t1').
out('sent_to').
dedup().as('a2').
in('sent_from').as('t2').
where('t1',eq('t2')).by('address').
out('sent_to').inV().as('a3').
select('a3','a2').
by('accountId').
group().
by('a3').
by('a2').
unfold().
where(select(values).limit(local,2).count(local).is(gte(2)))
The idea is to build your Map with group() then deconstruct it to entries with unfold(). You the filter each entry with where() by selecting the values of the entry, which is a List of "a2" then counting the items locally in that List. I use limit(local,2) to avoid unnecessary iteration beyond 2 since the filter is gte(2).
The easiest way to do this is with findAll { }.
.groupBy { it.a3 }
.findAll { it.value.a2.size() > 1 }
.collectEntries { [(it.key): [a2: it.value.a2]] }
if some a2 are null, then value.a2 also evaluates to null and filters the results without the need for explicit nullchecks

Can't access an element by a data- attribute with an underscore

Good day everyone!
I have an element
<tbody class="cp-ads-list__table-item _sas-offers-table__item cp-ads-list__table- item_state-deposit" data-card_id="16676514">
I'd like to access it by the data-card_id tag, but when I try the following
#browser.tbody(:data_card_id => "16676514").hover
I get an error
unable to locate element, using {:data_card_id=>"16676514", :tag_name=>"tbody"} (Watir::Exception::UnknownObjectException)
I guess my code would have worked if the tag were "data-card-id", but it's "data-card_id".
How do I access my element by this attribute?
Problem
You are right that the problem is the underscore in the data attribute. As seen in the ElementLocator, when building the XPath expression, all underscores are converted to dashes (in the else part of the statement):
def lhs_for(key)
case key
when :text, 'text'
'normalize-space()'
when :href
# TODO: change this behaviour?
'normalize-space(#href)'
when :type
# type attributes can be upper case - downcase them
# https://github.com/watir/watir-webdriver/issues/72
XpathSupport.downcase('#type')
else
"##{key.to_s.gsub("_", "-")}"
end
end
Solution - One-Off
If this is the only data attribute that is using underscores (rather than dashes), I would probably manually build the XPath or CSS expression.
#browser.tbody(:css => '[data-card_id="16676514"]').hover
Solution - Monkey Patch
If using underscores is a standard on the website, I would probably consider monkey patching the lhs_for method. You could monkey patch the method so that you only change the first underscore for data attributes:
module Watir
class ElementLocator
def lhs_for(key)
puts 'hi'
case key
when :text, 'text'
'normalize-space()'
when :href
# TODO: change this behaviour?
'normalize-space(#href)'
when :type
# type attributes can be upper case - downcase them
# https://github.com/watir/watir-webdriver/issues/72
XpathSupport.downcase('#type')
else
if key.to_s.start_with?('data')
"##{key.to_s.sub("_", "-")}"
else
"##{key.to_s.gsub("_", "-")}"
end
end
end
end
end
This would then allow your original code to work:
#browser.tbody(:data_card_id => "16676514").hover

Traverse the tree with Capybara

I want to do something that I think should be quite simple but can't work it out.
Using Cucumber and Capybara
In a table I want to click a button in the same row, but a different cell.
i.e. I want to find the button 'Change Role' that is in the same row as the name "Bob Perkins"
click_button "Change Role" within
I want to do something like
within find("td", text:"Bob Perkins").parent #where this would be the "tr" row
click_link_or_button("Change Role")
But that's giving me an ambigous match on "Change Role". Sure, there are more on the page, but only one of them is in the parent node of where it should be...
I'm not sure how many people are fluent in xpath, but if you find it as confusing as I do, you can chain finds to utilize xpath only as necessary. So for instance:
find("td", text:"Bob Perkins").find(:xpath, '..') # Gives you the TR
After having a bit of a play with capybara it looks as if the parent method returns the whole html document
e = find "table > thead"
=> #<Capybara::Element tag="thead">
e.parent
=> #<Capybara::Document>
The default selector type for capybara is CSS and that does not support the parent selector
It may be worth looking at XPath as with XPath you can perform a contains
e = find :xpath, "//table[thead]"
=> #<Capybara::Element tag="table">
e = find :xpath, "//table[thead]", text: "TOTAL"
=> #<Capybara::Element tag="table">
So looking at your code you could try this:
e = find :xpath, "//tr[td[contains(text(),'Bob Perkins')]]"
=> #<Capybara::Element tag="tr">
OR
e = find :xpath, "//tr[td]", text: 'Bob Perkins'
=> #<Capybara::Element tag="tr">
So you should get something like this
within find(:xpath, "//tr[td[contains(text(),'Bob Perkins')]]") do
click_link_or_button("Change Role")
end
Best of luck.
You are almost close the answer.
Corrected one is the:
within(find("td", text:"Bob Perkins").parent){ click_link_or_button("Change Role") }

Resources