Ruby: How can I repeat steps on Cucumber? - ruby

I want to add 100 names in the list. I'm using Calabash, so I have the .feature file:
When I fill the item field with "3"
And press the button Adicionar
And I repeat the previous 2 steps 100 times
My .rb file:
...
When(/^I repeat the previous (\d+) steps (\d+) times$/) do |steps, times|
How can I implement this .rb file? The last thing I tried, I got the error:
Undefined dynamic step: "2" (Cucumber::UndefinedDynamicStep)

Below is a quick hello world example for google (I don't have your code, so can't properly do an example for your site).
The When is what we are interested in really.
Given(/^I navigate to "([^"]*)"$/) do |url|
$driver.navigate.to url
end
When(/^I search for "([^"]*)"$/) do |search_term|
# Loop through 100 times
100.times do
# Clear any text present
$driver.find_element({css: 'input[name="q"]'}).clear
# Type in the request
$driver.find_element({css: 'input[name="q"]'}).send_keys(search_term)
# Fire the request with the return key
$driver.find_element({css: 'input[name="q"]'}).send_keys(:return)
# Give time for the process to complete before a reset (This could also go first)
sleep 1
end
end
Then(/^I (?:should|must) see some results$/) do
wait = Selenium::WebDriver::Wait.new(:timeout => 10)
wait.until { $driver.find_element({css: 'h3.r'}) }
end
Doing a for loop, like in the When above, could also have a maximum set by a new captured integer:
When(/^I search for "([^"]*)" (\d+) times?$/) do |search_term, times|
# Loop through an amount of times
times.to_i.times do
$driver.find_element({css: 'input[name="q"]'}).clear
$driver.find_element({css: 'input[name="q"]'}).send_keys(search_term)
$driver.find_element({css: 'input[name="q"]'}).send_keys(:return)
sleep 1
end
end
This would mean that you don't have to dynamically set up capturing of previous steps (which is doable, I'm sure, but would be a giant amount of effort for what you seem to be wanting to do here).

You have a list of names then you can pass it using the data table and you can write your a combined step as following:
Given I add following names in the list:
|Ram|
|Abdul|
|Scot|
and then you can write the step definitions using nested steps as following in .rb file:
Given(/^I add following names in the list:$/) do |table|
data = table.raw
data.each do |row|
steps %{
When I fill the item field with "#{row[0]}"
And press the button Adicionar
}
end
end
When(/^I fill the item field with "([^"]*)"$/) do |arg1|
pending # Write code to enter the the item
end
When(/^press the button Adicionar$/) do
pending # Write code to click the Adicionar button
end
If you simply want to fill the names with same name "3" then you can write your combined step as following:
Given I fill the item field with "3" 100 times
And then you can write the steps definition as following:
Given(/^I fill the item field with "([^"]*)" (\d+) times$/) do |name, loop_count|
loop_count.to_i.times do
steps %{
When I fill the item field with "#{name}"
And press the button Adicionar
}
end
end
When(/^I fill the item field with "([^"]*)"$/) do |arg1|
pending # Write code to enter the the item
end
When(/^press the button Adicionar$/) do
pending # Write code to click the Adicionar button
end

Related

Handling failures in data driven testing using rspec

I am using rspec to do some data driven testing. My test reads from a csv file, grabs an entry which is inserted into the text box on the page and is then compared to expected text which is also read from the csv file. All this is working as expected, I am able to read and compare without any issues.
Below is my code:
Method for reading csv file:
def user_data
user_data = CSV.read Dir.pwd + '/user_data.csv'
descriptor = user_data.shift
descriptor = descriptor.map { |key| key.to_sym }
user_data.map { |user| Hash[ descriptor.zip(user) ] }
end
Test:
describe "Text box tests" do
before :all do
#homepage = Homepage.new
end
it "should display the correct name" do
visit('http://my test url')
sleep 2
user_data.each do |entry|
#homepage.enter_name(entry[:name])
#homepage.click_go()
sleep 2
begin
expect(page).to have_css("#firstname", text: entry[:expected_name])
end
end
end
end
The problem is with failures. If I have a failure with one of the tests (i.e the expected text is not displayed on the page) then the test stops and all subsequent entries in the csv are not tested. If I put in a rescue after the expect statement like this:
rescue Exception => error
puts error.message
Then the error is logged to the console, however at the end of my test run it says no failures.
So basically I am looking for is, in the event of a failure for my test to keep running(until all entries in the csv have been covered), but for the test run to be marked as failed. Does anyone know how I can achieve this?
Try something like this:
context "when the user is on some page" do
before(:context) { visit('http://example.org/') }
user_data.each do |entry|
it "should display the correct name: #{entry[:name]}" do
#homepage.enter_name(entry[:name])
#homepage.click_go
expect(page).to have_css("#firstname", text: entry[:expected_name])
end
end
end
You will also need to change def user_data to def self.user_data
I would advise mapping over the entries and calling the regular capybara method has_css? instead of the rspec helper method. It would look like this:
results = user_data.map do |entry|
#homepage.enter_name(entry[:name])
#homepage.click_go()
sleep 2
page.has_css?("#firstname", text: entry[:expected_name])
end
expect(results.all?) to be_truthy
if you want to keep track of which ones failed, you cann modify it a bit:
missing_entries = []
user_data.each do |entry|
#homepage.enter_name(entry[:name])
#homepage.click_go()
sleep 2
has_entry = page.has_css?("#firstname", text: entry[:expected_name])
unless has_entry
missing_entries.push entry[:expected_name]
end
end
expect(missing_entries).to be_empty

How do I make a word optional in a Cucumber step definition?

I have a step definition, below, that does what I want it to do i.e. it checks the url of the page against the 'page' element of 'PAGES' hash.
Then(/^I should( still)? be at the "(.*)" page$/) do |still, page|
BROWSER.url.should == PAGES[page]
end
The step definition is used for both
I should be at the ... page
I should still be at the ... page
However, I don't need "still" to be passed into the block. I just need it to be optional for matching to the step but not passed into the block. How can I do that?
Thanks.
You want to mark the "still" group as non-capturing. This is done by starting the group with ?:.
Then(/^I should(?: still)? be at the "(.*)" page$/) do |page|
BROWSER.url.should == PAGES[page]
end

How to select all links in a page and store it in an array in capybara?

When(/^I search for all links on homepage$/) do
within(".wrapper") do
all("a")[0].text
end
all_link = []
all_link << all("a")[0].text
all_link.each do |i|
puts i
end
end
This is the code i have written to get the text of the link. But here only one link text is available. I have to manually provide all 'a' values to call up the elements and store the text. Is there some other way in which I can use the url and call up all the links and the related text to that link and store it in the array?
all_links = all("a").map { |ele| [ ele[:href], ele.text ] }
This would give you an array of pairs with the URLs and their associated text (I assume you mean the text inside the <a> element, not the web page you get by following on the link).
By the way, to output them for debugging purposes, a simpler way is
puts all_links.inspect
When(/^I search for all links on homepage$/) do
within(".wrapper") do
all_links = all("a").map(&:text) # get text for all links
all_links.each do |i|
puts i
end
end
end

How can I perform an action based on the contents of a div with Selenium Webdriver?

I have a Ruby application using Selenium Webdriver and Nokogiri. I want to choose a class, and then for each div corresponding to that class, I want to perform an action based on the contents of the div.
For example, I'm parsing the following page:
https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies
It's a page of search results, and I'm looking for the first result with the word "Adoption" in the description. So the bot should look for divs with className: "result", for each one check if its .description div contains the word "adoption", and if it does, click on the .link div. In other words, if the .description does not include that word, then the bot moves on to the next .result.
This is what I have so far, which just clicks on the first result:
require "selenium-webdriver"
require "nokogiri"
driver = Selenium::WebDriver.for :chrome
driver.navigate.to "https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies"
driver.find_element(:class, "link").click
You can get list of elements that contains "adopt" and "Adopt" by XPath using contains() then use union operator (|) to union results from "adopt" and "Adopt". See code below:
driver = Selenium::WebDriver.for :chrome
driver.navigate.to "https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies"
sleep 5
items = driver.find_elements(:xpath,"//div[#class='g']/div[contains(.,'Adopt')]/h3/a|//div[#class='g']/div[contains(.,'adopt')]/h3/a")
for element in items
linkText = element.text
print linkText
element.click
end
The pattern to handle each iteration will be determined by the type of action executed on each item. If the action is a click, then you can't list all the links to click on each of them since the first click will load a new page, making the elements list obsolete.
So If you wish to click on each link, then one way is to use an XPath containing the position of the link for each iteration:
# iteration 1
driver.find_element(:xpath, "(//h3[#class='r']/a)[1]").click # click first link
# iteration 2
driver.find_element(:xpath, "(//h3[#class='r']/a)[2]").click # click second link
Here is an example that clicks on each link from a result page:
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
wait = Selenium::WebDriver::Wait.new(timeout: 10000)
driver.navigate.to "https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies"
# define the xpath
search_word = "Puppies"
xpath = ("(//h3[#class='r']/a[contains(.,'%s')]" % search_word) + ")[%s]"
# iterate each result by inserting the position in the XPath
i = 0
while true do
# wait for the results to be loaded
wait.until {driver.find_elements(:xpath, "(//h3[#class='r']/a)[1]").any?}
# get the next link
link = driver.find_elements(:xpath, xpath % [i+=1]).first
break if !link
# click the link
link.click
# wait for a new page
wait.until {driver.find_elements(:xpath, "(//h3[#class='r']/a)[1]").empty?}
# handle the new page
puts "Page #{i}: " + driver.title
# return to the main page
driver.navigate.back
end
puts "The end!"
I don't code in ruby, but one way you could do it in python is:
driver.find_elements
notice how elements is plural, I would grab all the links and put them into an array like.
href = driver.find_elements_by_xpath("//div[#class='rc]/h3/a").getAttribute("href");
Then get all of the descriptions the same way. Do a for loop for every element of description, if the description has the word "Adoption" in it navigate to that website.
for example:
if description[6] has the word adoption find the string href[6] and navigate to href[6].
I hope that makes sense!

How to make sure that multiple elements of same class have been loaded on page using watir?

I am using cucumber and watir. The question is in reference to the code below:
When(/^I click on all 'Show more'$/) do
#browser.links(:class, "more-matches").each do |d|
if d.text == "Show more"
d.click
end
end
end
Now, when the test case reaches this step-definition, the test case is shown as passed without clicking on all the links captured using #browser.links(:class, "more-matches").
The particular code does not get implemented may be because the ajax call has not been completed yet and the array holds zero elements and does not loop through. The code works if I introduce a "sleep 2" at the beginning of this step definition. Can anyone tell me how to handle this case by adding a code so that the ajax call has completed and the array holds all the elements and loops successfully. I have also tried adding the code:
if #browser.execute_script('return jQuery.active').to_i == 0
but it did not work as well.
Kindly suggest a way that the step definition gets executed and does not pass because of empty array.
Using Element#wait_until_present
Usually, you would know how many links should be present. Therefore, you could wait until the expected number of links are present.
When(/^I click on all 'Show more'$/) do
# Wait for the expected number of links to appear
# (note that :index is zero-based, hence the minus 1)
expected_number = 5
#browser.link(:class => "more-matches",
:index => (expected_number-1)).wait_until_present
# Click the links
#browser.links(:class, "more-matches").each do |d|
if d.text == "Show more"
d.click
end
end
end
If you do not know how many links are expected, it makes it a bit more difficult to ensure consistency. However, you might be able to get away with just checking that at least one link is present. Hopefully if one is present, all of the others are present.
When(/^I click on all 'Show more'$/) do
# Wait until at least one link appears
#browser.link(:class => "more-matches").wait_until_present
# Click the links
#browser.links(:class, "more-matches").each do |d|
if d.text == "Show more"
d.click
end
end
end
Using Browser#wait_until
An alternative approach is to use wait_until. The waiting for at least 5 links can be re-written as:
When(/^I click on all 'Show more'$/) do
# Wait for the expected number of links to appear
expected_number = 5
#browser.wait_until do
#browser.links(:class => "more-matches").length >= expected_number
end
# Click the links
#browser.links(:class, "more-matches").each do |d|
if d.text == "Show more"
d.click
end
end
end

Resources