Cucumber Capybara & Rspec
Libraries (documentation)
- Rspec Documentation
- Capybara Documentation
- Factory Girl Documentation
- Webmock Documentation
- Timecop Documentation
- Shoulda Matchers
- Fuubar Release
Cucumber Tables:
- http://www.rubydoc.info/gems/cucumber/1.3.17/Cucumber/Ast/Table
- Class: Cucumber::Ast::OutlineTable
- Inherits:
- Defined in:lib/cucumber/ast/outline_table.rb
- Special Tables:
- https://github.com/cucumber/cucumber/wiki/Calling-Steps-from-Step-Definitions
- http://makandracards.com/makandra/18905-how-to-not-repeat-yourself-in-cucumber-scenarios
- http://www.methodsandtools.com/tools/cucumber.php
Cucumber step definitions:
- https://www.jetbrains.com/help/ruby/2016.1/creating-step-definition.html
- https://github.com/cucumber/cucumber/wiki/Calling-Steps-from-Step-Definitions
- https://cucumber.io/docs/reference
- https://gojko.net/2015/02/25/how-to-get-the-most-out-of-given-when-then/
=== CAPYBARA ===
Interacting with page elements
Summary
When interacting with the page, use action methods like click_on
instead of finder methods like find
whenever possible. Capybara knows the most about what you’re doing with those methods, and can more intelligently handle odd edge cases.
When verifying that elements are on the page as expected, use RSpec matchers like have_css
instead of node methods like text
whenever possible. Capybara can wait for the result you want with a matcher, but doesn’t know what text you’re expecting when you invoke methods on a node.
CAPYBARA (Library, DSL, CheatSheet)
- https://github.com/jnicklas/capybara
- http://www.rubydoc.info/github/jnicklas/capybara
- http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara
- http://www.rubydoc.info/github/jnicklas/capybara/master#the-dsl
- https://www.gridlastic.com/capybara-cucumber-code-example.html
More resources on Capybara: (if you care to read more):
- https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara
- https://quickleft.com/blog/five-capybara-hacks-to-make-your-testing-experience-less-painful/
- https://www.sitepoint.com/basics-capybara-improving-tests/
- https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara
- http://www.slideshare.net/koffeinfrei/functional-testing-with-capybara
- http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Session
- https://quickleft.com/blog/five-capybara-hacks-to-make-your-testing-experience-less-painful/
- http://blog.plataformatec.com.br/2011/02/improving-your-tests-with-capybara-custom-selectors/
- https://www.varvet.com/blog/simple-tricks-to-clean-up-your-capybara-tests/
CAPYBARA SYNTAX and Examples
Make sure to use methods that implicitly wait for an element before doing an action.
Methods that Wait
find(selector)
,find_field
,find_link
, etc.within(selector)
(scoping)has_selector?
,has_no_selector?
& assertions- form & link actions
click_link
,click_button
fill_in
check
,uncheck
,select
,choose
, etc.
Methods that Won’t Wait
visit(path)
current_path
all(selector)
first(selector)
(counter-intuitively)execute_script
,evaluate_script
- simple accessors:
text
,value
,title
, etc.
So the absolute first step in diagnosing a possible race condition, assuming your setup is known to be sound, is to check that you haven’t accidentally used one of these Capybara methods that won’t wait for page content update.
METHODS THAT DO NOT WAIT often forces us to use ‘sleep(x)’, which is bad practice!
see: http://stefan.haflidason.com/testing-with-rails-and-capybara-methods-that-wait-method-that-wont/
Click on Links
When /^I click on New Item$/ do click_link ('New Item') end
can then become… to reuse the same step in other scenarios
When /^I click on (.*)$/ do |link| click_link (link) end
https://stackoverflow.com/questions/6930927/how-do-i-confirm-a-javascript-popup-with-capybara
example:
page.driver.browser.switch_to.alert.accept
# or
page.driver.browser.switch_to.alert.dismiss
# or
page.driver.browser.switch_to.alert.text
http://www.rubydoc.info/github/jnicklas/capybara/Capybara%2FSession%3Aaccept_confirm
example;
accept_confirm
do
click_link
'Destroy'
end
FIND ELEMENT by ID and validate content
these work as step definitions:
better to use the newest “expect”… than “page.has…”
GOOD EXAMPLE:
page.has_xpath?( './/*[@data-automation-id="element-id"]' , :text => 'Text that appears on page' , :visible => true ) |
BETTER EXAMPLE: only confirms that xpath is present!
expect(page).to have_xpath( './/*[@data-automation-id="element-id"]' ) |
BETTER EXAMPLE: finds the xpath and confirms the content value
expect(find(:xpath, "//*[@data-automation-id='element-id']" )).to have_content( 'Text that appears on page' ) |
Use Within, so you are not scoping the whole page
within(:xpath, "//*[@class='bla-bla-bla']" ) do expect(find( 'element-header' )).to have_content(header) end OR within(:xpath, "//*[@class='register-button']" ) do should have_link(href: account_creation_url) end |
EXACT TEXT MATCH
or element contains text
see:
- http://stackoverflow.com/questions/16283149/rails-cucumber-capybara-matching-exact-link-text
- http://www.rubydoc.info/github/jnicklas/capybara/#Exactness
- http://stackoverflow.com/questions/23259274/in-capybara-does-exact-or-capybara-exact-apply-to-have-selector
NOTE: Capybara won’t consider the content to exist if it is not visible.
Scenario: ‘Exact match‘ example
Given an element on the page with the text string “chocolate cake”
When using ‘expect(content).to eq(cake)‘ step definition
Then my test will Fail because “cake” does not equal to “chocolate cake”
Scenario: ‘Contains text’ example
Given an element on the page with the text string “chocolate cake”
When using ‘should have_content(cake)‘ step definition
Then my test will Pass because “cake” is found within the string “chocolate cake”
1- EXACT MATCH EXAMPLES:
Example with clicking on an element by its name: Exact Match
# Will match links that exactly match 'Password'
click_link(
'Password'
,
:exact
=>
true
)
find(:xpath,$PATH[
'edu_text'
]).text.should match(eduText)
2- CONTAINS TEXT EXAMPLES: (not exact match)
Examples with clicking on an element by its name: Contains Text
# Will match links that contain the word 'Password' - ex 'Password Confirmation'
click_link(
'Password'
,
:exact
=>
false
)
if you use the following: it will check if content exists but not exact content match!
but useful if dictionary labels keep changing, you can assert that only part of the text is present.
should have_content(arg1)
within(scope)
do
page.should have_content(content)
end
3- USING CAPYBARA EXACT = TRUE:
ALSO you can use Following for: Capybara.exact = true
expect(page).to have_content(arg1)
OR
expect(tooltiptext).to eq(arg1)
HREF
try if these work:
link = page.find(:css,
'a[href="actual link"]'
)
find_link(
'link_text'
)[:href].should ==
'url'
find_link(
'link_text'
)[:target].should ==
'_blank'
HAS_LINK
https://github.com/jnicklas/capybara
has_link_spec.rb
# frozen_string_literal: true Capybara::SpecHelper.spec '#has_link?' do before do @session.visit('/with_html') end
it "should be true if the given link is on the page" do expect(@session).to have_link('foo') expect(@session).to have_link('awesome title') expect(@session).to have_link('A link', :href => '/with_simple_html') expect(@session).to have_link(:'A link', :href => :'/with_simple_html') expect(@session).to have_link('A link', :href => /\/with_simple_html/) end
it "should be false if the given link is not on the page" do expect(@session).not_to have_link('monkey') expect(@session).not_to have_link('A link', :href => '/non-existant-href') expect(@session).not_to have_link('A link', :href => /non-existant/) end end
Capybara::SpecHelper.spec '#has_no_link?' do before do @session.visit('/with_html') end
it "should be false if the given link is on the page" do expect(@session).not_to have_no_link('foo') expect(@session).not_to have_no_link('awesome title') expect(@session).not_to have_no_link('A link', :href => '/with_simple_html') end
it "should be true if the given link is not on the page" do expect(@session).to have_no_link('monkey') expect(@session).to have_no_link('A link', :href => '/non-existant-href') expect(@session).to have_no_link('A link', :href => /\/non-existant-href/) end end
—
Capybara doesn’t provide a link_to method so I’m not sure where you’re getting that from (rails provides a link_to for generating links in the view). To find a link based on just its href using capybara you could do
link = page.find(:css, 'a[href="actual link"]')
or if you’re looking to assert that the element exists
page.assert_selector(:css, 'a[href="actual link"]')
or – if using RSpec
expect(page).to have_selector(:css, 'a[href="actual link"]')
Since have link by default searches for substrings in the link text you can also do
expect(page).to have_link(nil, href: 'actual link')
or
page.assert_selector(:link, nil, href: 'actual link')
Current HOST, Current PATH, Current URL
Step def:
# current_host.should == $BASE_URL_SHORT+(localsite)
# current_path.should == destination
# current_url.should == $BASE_URL_SHORT+(localsite)+(destination)
or
expect(page).to have_current_path(destination)
EXAMPLES:
EXPECTED: Path to equal
"https://complete-url"
with
this
STEP DEF:
expect(page).to have_current_path(destination, url:
true
)
RESULT:
expected
"https://site/path?var1=this&var2=that"
to equal
"/path"
with
this
STEP DEF:
expect(page).to have_current_path(destination, only_path:
true
)
RESULT:
PASS!
TO PRINT OUT in command prompt:
puts
"\n\n current_host --->"
, current_host
puts
"\n\n current_path --->"
, current_path
puts
"\n\n current_url --->"
, current_url
puts
"\n\n current_scope --->"
, current_scope
RESULT examples:
current_host --->, https:
//www-staging.yoursite.com,
current_path --->, /qa-automation/fsb-buy/buy-container/individual-plc/acd-popup,
current_url --->, https:
//www-staging.yoursite.com/qa-automation/fsb-buy/buy-container/individual-plc/acd-popup?plc=ACD&term=1-MONTH&support=BASIC&quantity=1
current_scope --->, #<Capybara::Node::Element:
0x000000062b83c8
>,
read: http://stackoverflow.com/questions/5228371/how-to-get-current-path-with-query-string-using-capybara
If the current page is /people?search=name
but you only care that it’s on the /people
page regardless of the param, you can send the only_path
option:
expect(page).to have_current_path(people_path, only_path:true)
Additionally, if you want to compare the entire URL:
expect(page).to have_current_path(people_url, url:true)
ASSERT URLs
http://stackoverflow.com/questions/5153550/capybara-assert-attributes-of-an-element
To Try:
page.should have_link( 'View Clowns' , :href => '/clowns?ordered_by=clumsyness' ) |
ASSERT ATTRIBUTES
http://stackoverflow.com/questions/5153550/capybara-assert-attributes-of-an-element
# Check for a link that has a "disabled" class : page.should have_css( "a.my_link.disabled" ) page.should have_xpath( "//a[@class='disabled']" ) # Check for a link that has a "disabled" attribute: page.should have_css( "a.my_link[disabled]" ) page.should have_xpath( "//a[@class='disabled' and @disabled='disabled']" ) # Check that the element is visible find( "a.my_link" ).should be_visible find(:xpath, "//a[@class='disabled']" ).should be_visible |
Simply you can use page.has_css? method
page.has_css?( '.class_name' ) |
this will return true if element exists.
Do some action based on validation.
page.has_css?( '.class_name' ) do #some code end |
You can actually use already existing methods defined by Capybara matchers. assert has_no_field?( 'Username' ) Furthermore there are additional methods available the can help you in finding different types of elements in your page has_link? , has_no_link? has_button?, has_no_button? has_field?, has_no_field? has_checked_field?, has_no_checked_field? has_select?, has_no_select? has_checked_field or has_no_check_field |
page.should have_no_selector(:xpath, "//*[@data-automation-id='element-id']" ) |
Example: page has CSS
if css style changed between being disabled and not disabled.
ie. when button is enabled, the class = “wd-button ptdlb-download-button wd-uppercase”
when button is disabled, the class = “wd-button ptdlb-download-button wd-uppercase wd-button-disabled”
page.should have_no_css( '.wd-button-disabled' , :visible => true ) page.should have_css( '.wd-button-disabled' , :visible => true ) |
ASSERT ATTRIBUTE IS VISIBLE OR NOT
to try:
assert element is present, regardless of visibility
these might not work though?
page.should have_css( '#some_element' , :visible => false ) |
assert visible element is not present
page.should have_no_css( '#some_element' , :visible => true ) |
My working examples:
expect (find(:xpath, "//*[@class='wd-font-21 element-name hide']" )).should be_visible find(:css, "#some_element" ).should be_visible expect (find(:xpath, "//*[@class='wd-font-21 element-name hide']" )).should_not be_visible find(:css, "#comment_stream_list li[data-id='#{@id3}']" ).should_not be_visible |
_______________________________________________________________________________________________
or https://www.varvet.com/blog/introducing-capybara-2-1/
VISIBILITY
check out: https://makandracards.com/makandra/11133-how-to-test-print-stylesheets-with-cucumber-and-capybara
“LOCAL STORAGE”
We have a before hook already set in place. just add the tag before your scenario
@StorageClear |
page.execute_script( "localStorage.clear()" ) |
or
page.execute_script( 'if (localStorage && localStorage.clear) localStorage.clear()' ) or visit($BASE_URL) Capybara.page.execute_script( "localStorage.clear()" ) |
Browser SESSION STORAGE
We have a before hook already set in place. just add the tag before your scenario
@SessionStorageClear |
here is the code example:
visit($BASE_URL) Capybara.page.execute_script( "sessionStorage.clear()" ) |
“DEMANDBASE” (dmdbase_full) in Local Storage
Delete “DMDBASE_FULL” object
1- Function to delete existing ‘dmdbase_full’ key and value
page.execute_script( "localStorage.removeItem('dmdbase_full')" ) |
GET “DMDBASE_FULL” object
2- funtion to get/read the value of ‘dmdbase_full’ value
you first need to visit the page.
visit( 'https://site.com ) dmdbase1 = page.evaluate_script( "localStorage.getItem('dmdbase_full')" ) puts "\n\n dmdbase1 --->" ,dmdbase |
SET “DMDBASE_FULL” object
3- funtion to define new value of ‘dmdbase_full’ from feature file variable named “countrycode” which stores the 2-letter value ex. FR
page.execute_script( 'localStorage.setItem("dmdbase_full",":false:false:false:false:false:false:' +countrycode+ ':")' ) |
EXAMPLE with Local Storage Clear:
Scenario Outline: test 1 Given page x loads "<dmd>" Examples: | dmd | | GB | | DE | |
Given(/^page x loads "([^" ]*)"$/) do |dmd| ####### 1- funtion to clear localStorage ############ visit( 'https://site.com/page' ) # Capybara.page.execute_script("localStorage.clear()") page.execute_script( 'if (localStorage && localStorage.clear) localStorage.clear()' ) f
####### 2- funtion to get existing value of dmdbase_full ############ visit( 'https://site.com/page ' ) dmdbase1 = page.evaluate_script( "localStorage.getItem('dmdbase_full')" ) # puts "\n\n dmdbase1 --->",dmdbase1 # puts "\n\n" ####### 3- funtion to delete existing dmdbase_full ############ page.execute_script( "localStorage.removeItem('dmdbase_full')" ) ####### 4- funtion to define new value of dmdbase_full from feature file if using an examples data table ############ country = page.execute_script( 'localStorage.setItem("dmdbase_full",":false:false:false:false:false:false:' +dmd+ ':")' ) # puts "\n\n dmd --->",dmd # puts "\n\n country --->",country ####### 5- funtion to get value of new dmdbase_full ############ visit( 'https://yoursite.com/page' ) # sleep 10 dmdbase2 = page.evaluate_script( "localStorage.getItem('dmdbase_full')" ) # puts "\n\n dmdbase2 --->",dmdbase2 end |
COOKIES
se the gem “show_me_the_cookie” included in the GEMFILE.
see:
gem install show_me_the_cookies
examples:
API # puts a string summary of the cookie show_me_the_cookie(cookie_name) # returns a hash of the cookie # form: {:name, :domain, :value, :expires, :path} get_me_the_cookie(cookie_name) # puts a string summary of all cookies show_me_the_cookies # returns an array of cookie hashes # form: [{:name, :domain, :value, :expires, :path, :secure}] get_me_the_cookies # deletes the named cookie delete_cookie(cookie_name) # removes session cookies and expired persistent cookies expire_cookies # creates a cookie create_cookie(cookie_name, cookie_value) # creates a cookie for the path or domain create_cookie(cookie_name, cookie_value, :path => "..." , :domain => "..." ) |
also
Show_me_the_cookie gem has the following methods.
We can also create helper methods such as:
if cookie exist or does not exist:
the use the function:
cookies_should_contain(key, value) |
cookies_should_not_contain(key, value) |
see: https://github.com/nruth/show_me_the_cookies/blob/master/spec/spec_helper.rb
wondering if we already have this as part of the gem install though. might be worth checking out.
Reload Page
page.evaluate_script(
"window.location.reload()"
)
Maximize Browser Window
Capybara.page.current_window.maximize |
Resize Browser Window
Capybara.page.current_window.resize_to( 414 , 736 ) |
SELECT OPTION FROM DROPDOWN
OPTION 1:
Basically here we are looking into a specific path in which we are looking to find a path option[2], meaning the second element from the dropdown list as an example.
The first example below is looking for a reference from the xpath.yml file. You can either go with option 1 or option 2 which is by adding a direct path there.
- find(:xpath,$PATH[‘your_path_reference’])
- find(‘#yourPath’)
find(:xpath,$PATH[ 'your_path_reference' ]).find(:xpath, 'option[2]' ).select_option |
OPTION 2:
using value of element in dropdown and name of dropdown vs xpath:
Gherkin and step definition:
Example 1 with a “examples data table” if you have more than one variation to test:
When I select my "<Country>" from the dropdown Examples: | Country | CountryCode | | Canada | CA | | France | FR | --- When(/^I select my "([^" ]*)"$/) do |country| select(country, :from => 'element-id' ) end Explanation: select(Canada, :from => 'name of dropdown' ) |
Example 2 with an argument:
When I select "canada" from the dropdown When(/^I select "([^" ]*)"$/) do |arg1| select(arg1, :from => 'element-name-or-id ) end |
OPTION 3: to be tested out if still works, as post was 6 years old and Capybara syntax may have changed
From: https://makandracards.com/makandra/864-test-that-a-select-option-is-selected-with-cucumber
also check out: https://makandracards.com/makandra/996-test-that-a-select-field-contains-an-option-with-cucumber
Example:
Then /^ "([^" ]*) " should be selected for " ([^ "]*)" (?: within "([^\"]*)" )?$/ do |value, field, selector| with_scope(selector) do field_labeled(field).find(:xpath, ".//option[@selected = 'selected'][text() = '#{value}']" ).should be_present end end |
Check / Uncheck a ‘checkbox’
# Check
find(:css,
"#cityID[value='62']"
).set(
true
)
# Uncheck
find(:css,
"#cityID[value='62']"
).set(
false
)
Checkbox is checked or Not checked
checkbox = find(:xpath,$PATH[ 'checkbox-id' ]) checkbox.should be_checked |
checkbox = find(:xpath,$PATH[ 'heckbox-id' ]) checkbox.should_not be_checked |
POPUPS & ALERTS
should have a “if” statement
references:
https://stackoverflow.com/questions/2458632/how-to-test-a-confirm-dialog-with-cucumber
https://stackoverflow.com/questions/6930927/how-do-i-confirm-a-javascript-popup-with-capybara
https://github.com/teamcapybara/capybara/issues/29
http://toolsqa.com/selenium-webdriver/handling-of-alerts-javascript-alerts-and-popup-boxes/ (this is Java code but can likely re-use the selenium code)
Infinite scroll
see: http://www.tweetegy.com/2013/05/testing-infinite-scroll-using-rspec-without-sleep-or-wait-until/
=== RSPEC ====
identifying and verifying that elements are on the page as expected
rspec (rspec guidelines with ruby)
- http://betterspecs.org/
- rspec_rails_cheetsheet.rb
- https://github.com/rspec/rspec-expectations
- Tutorials:
TESTING API
Use RSPEC and not Capybara for testing APIs
- http://matthewlehner.net/rails-api-testing-guidelines/
- https://www.varvet.com/blog/capybara-and-testing-apis/
RSPEC MATCHERS
see: https://github.com/rspec/rspec-collection_matchers
EXAMPLE:
Basic usage
First of all, you need to require rspec-collection matchers. Add the following line to your spec_helper.rb
:
require 'rspec/collection_matchers'
Using rspec-collection_matchers
you can match the number of items in a collection directly, e.g.:
it 'matches number of items in a collection' do
expect([1,2,3]).to have_at_least(3).items
end
You can also match the number of items returned by a method on an object, e.g.:
class Cart
def initialize(*products)
@products = products
end
attr_reader :products
end
it 'matches number of items returned from a method' do
cart = Cart.new('product a', 'product b')
expect(cart).to have_at_most(2).products
end
The last line of the example expresses an expected outcome: if cart.products.size <= 2
then the example passes, otherwise it fails with a message like:
expected at most 2 products, got 3
expect(collection).to have(n).items
expect(collection).to have_exactly(n).items
expect(collection).to have_at_most(n).items
expect(collection).to have_at_least(n).items
more ?
#Model @user.should have(1).error_on(:username) # Checks whether there is an error in username @user.errors[:username].should include("can't be blank") # check for the error message #Rendering response.should render_template(:index) #Redirecting response.should redirect_to(movies_path) #Capybara Matchers response.body.should have_content("Hello world") response.body.should have_no_content("Hello world") response.body.should have_css("input#movie_title") response.body.should have_css("input#movie_title", :value => "Twelve Angry Men") response.body.should have_css("input", :count => 3) #True if there are 3 input tags in response response.body.should have_css("input", :maximum => 3) # True if there or fewer or equal to 3 input tags response.body.should have_css("input", :minimum => 3) # True if there are minimum of 3 input tags response.body.should have_css("input", :between => 1..3) # True if there 1 to 3 input tags response.body.should have_css("p a", :text => "hello") # True if there is a anchor tag with text hello response.body.should have_css("p a", :text => /[hH]ello(.+)/i) # True if there is a anchor tag with text matching regex response.body.should have_xpath("//a") response.body.should have_xpath("//a",:href => "google.com") response.body.should have_xpath("//a[@href => 'google.com']") response.body.should have_xpath("//a[contains(.,'some string')]") response.body.should have_xpath("//p//a", :text => /re[dab]i/i, :count => 1) # can take both xpath and css as input and can take arguments similar to both have_css and have_xpath response.body.should have_selector(:xpath, "//p/h1") response.body.should have_selector(:css, "p a#movie_edit_path") # For making capybara to take css as default selector Capybara.default_selector = :css response.body.should have_selector("input") #checks for the presence of the input tag response.body.should have_selector("input", :value =>"Twelve Angry Men") # checks for input tag with value response.body.should have_no_selector("input") # For making capybara to take css as default selector Capybara.default_selector = :xpath response.body.should have_selector("//input") #checks for the presence of the input tag response.body.should have_selector("//input", :value =>"Twelve Angry Men") # checks for input tag with value # To access elements inside form response.body.should have_field("FirstName") # checks for presence of a input field named FirstName in a form response.body.should have_field("FirstName", :value => "Rambo") response.body.should have_field("FirstName", :with => "Rambo") response.body.should have_link("Foo") response.body.should have_link("Foo", :href=>"googl.com") response.body.should have_no_link("Foo", :href=>"google.com")
Hash rocket is not a valid XPath symbol (it leads to “Invalid expression” exception).
Please use an “equals” sign: it should be “//a[@href = ‘google.com’]” instead of “//a[@href => ‘google.com’]”.
However, regarding response.body.should have_css("input#movie_title", :value => "Twelve Angry Men")
, :value is not actually a valid key for have_css, and :text doesn’t actually match against the value attribute of input fields I’ve found.
You can either do something like have_css("input#movie_title[value=\"Twelve Angry Men\"]")
, or user the have_field and :with key.
This works but using page.should instead of response.body.should
You’d better use the new :expect
syntax like expect(page).to have_
With capybara 2.4.4, if you want to find a field with a value, use the following
expect(page).to have_field("My Field", with: "the value in the field")