In the below capybara test I want to check that a user can see any number after 'Temperature:' when they visit the root path of my rails application. The value which is stored in the instance variable #temperature is retrieved from an API and is displayed to the user when the page is refreshed.
How can i put a ruby regular expression like 'Temperature: \d' into my spec below?
Spec:
require "rails_helper"
feature "user sees temperature" do
scenario "success" do
visit root_path
expect(page).to have_css 'p', text: 'Temperature: \d'
end
end
View:
<p>Temperature: <%= #temperature %></p>
This is about right, you just have to use regex syntax (//) instead of string one (''):
expect(page).to have_css('p', text: /Temperature: \d/)
Related
on my index page I have this div:
<div class="banner">
<h1 class="glow-header">Galaxy Far, Far Away? Quick Trip to Mars?<br>
Pianeta has you covered.</h1>
<div>
In my testfile this works:
RSpec.describe 'home features' do
it 'displays the name of the app and links to the index-all planets page' do
visit root_path
expect(page).to have_content('Space is full of surprises.')
click_link('Go Beyond')
expect(current_path).to eq('/planets')
expect(page).to have_content('Galaxy Far, Far Away?')
end
end
But I would like it to be working with the h1 included.
I did this:
expect(page).to have_content('<h1 class="glow-header">Galaxy Far, Far Away? Quick Trip to Mars?<br>
Pianeta has you covered.</h1>')
end
But the test failed. What did I do wrong ?
The #has_content?/#has_text? method only checks the text content of the page. It does not look at the HTML tags.
If you want to check for content within a specific HTML element there is a #within method that takes a block and will scope the Capybara lookups within it to be within the matched element. The element referenced by #within must exist or Capybara will raise an exception.
page.within('h1.glow-header') do
expect(page).to have_content('Galaxy Far, Far Away?')
end
If you don't want to deal with scoping using within for a single expectation you could do
expect(page).to have_css('h1.glow-header', text: 'Galaxy Far, Far Away?')
If you've already got a reference to the header you could also do something like
header = find('h1.glow-header')
...
expect(header).to have_text('Galaxy Far, Far Away?')
Additionally you should not be doing expect(current_path).to eq('/planets'). Using RSpecs eq matcher with Capybara will lead to flaky tests as soon as you move to using an asynchronous (JS supporting) driver, because it prevents Capybaras auto waiting/retrying behaviors. Instead you should use the Capybara provided matcher
expect(page).to have_current_path('/planets')
I have capybara feature specs I intend running without explicitly specifying a cassette name. E.g:
scenario "by searching via a title" do
visit root_path
fill_in "search", with: "cheddar"
find(".search-btn").click
expect(page).to have_content(/White Cheddar/)
end
scenario "by searching via a description" do
visit root_path
fill_in "search", with: "cheddar"
select "Description", from: "search_field"
find(".search-btn").click
expect(page).to have_content(/White Cheddars Are Scarce/)
end
Each of these specs goes out to hit a third party API. I want to use a different cassette name for each of these specs without having to explicitly use VCR.use_cassette. I want my feature specs to be clear and concise enough just focusing on the user's action. Is there a way to achieve this?
The way I have handled something like this in the past is by configuring it from within the rails_helper file.
In your Rspec.configure block, you can retrieve details of each running spec and alter things either before, around or after the running of each spec example. That enabled me to have a nice DSL that allows me to specify the cassette name as a vcr tag. So in your case, it can become:
scenario "by searching via a title", vcr: :search_title do
visit root_path
fill_in "search", with: "cheddar"
find(".search-btn").click
expect(page).to have_content(/White Cheddar/)
end
scenario "by searching via a description", vcr: :search_description do
visit root_path
fill_in "search", with: "cheddar"
select "Description", from: "search_field"
find(".search-btn").click
expect(page).to have_content(/White Cheddars Are Scarce/)
end
The changes I had to do to make this work were within the rails_helper file:
RSpec.configure do |c|
c.around(:each, :vcr) do |example|
name = Array(example.metadata.dig(:vcr)).join
options = example.metadata.slice(:record, :match_requests_on)
VCR.use_cassette(name, options) { example.call }
end
end
For each spec example with a vcr tag, grab the spec's metadata and retrieve whatever is stored in the vcr tag as the cassette name. The rest of the lines just activates VCR for only specs having the VCR tag. You can see more of why I did this from this railscast episode
I get the error:
Capybara::ElementNotFound:
Unable to find field "user_email"
And this is the test code:
feature 'User' do
given!(:user) { User.new(email: 'testuserid#example.com', encrypted_password: 'test') }
scenario 'opens sign_up page' do
visit new_user_session_path
expect(page).to have_content 'unique text on the page'
end
scenario 'signs in with invalid email' do
visit new_user_session_path
fill_in('user_email',with: 'ssd')
expect(page).to have_content 'unique text on the page'
end
end
My HTML file consists of this code literally:
unique text on the page
<br>
<input type="text" id="user_email">
So this proves that the path is correct because my first scenario runs correctly. It is visiting the right page. But still I get this error for second scenario in fill_in.
I have also tried element = page.find("user_email"), it gives same error.
What am I possibly doing wrong?
I have been scratching my head like hell.
Usually the reason for this is that the input isn't actually visible on the page. You can verify this by doing
fill_in('user_email', with: 'ssd', visible: false)
If that succeeds in finding the element, then you need to change your test to first perform whatever actions make the field visible before attempting to fill it in.
Your code seems right. Maybe you are visiting wrong url or you have used user_email id once more. But you can give a try with alternative syntax like following :
find("input[id$='user_email']").set "ssd"
Capybara provides a useful method to check a link:
have_link
which as far as I can tell can be used like:
have_link("link_text", :href => "actual link")
However, I don't care about the link_text, rather I just want to check href (as linking the test to the text is more brittle). If I only want to check the href this would be a view test.
How do I use Capybara to check the href without needing to check the text? Maybe i can use regex?
[edit] changed wording based on answer below
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')
While looking to the same answer I found out that it's possible to pass options to first argument of have_link because it's implementation is:
# RSpec matcher for links
# See {Capybara::Node::Matchers#has_link?}
def have_link(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:link, locator, options, &optional_filter_block)
end
So if you don't care about link text you can safely omit first parameter and do this (RSpec syntax):
expect(page).to have_link(href: 'some url')
# or even
expect(page).to have_link(href: /some url regexp/)
Users on web page don't distinguish between "button" and "link styled as button".
Is there a way to add check whether a "button or link" is present on page?
For example Capybara has step:
page.should have_button('Click me')
which does not find links styled as buttons.
Updated answer (should matcher is deprecated in RSpec 3.0+):
expect(page).to have_selector(:link_or_button, 'Click me')
Before:
page.should have_selector(:link_or_button, 'Click me')
Followed from click_link_or_button which is defined here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/actions.rb#L12
def click_link_or_button(locator)
find(:link_or_button, locator).click
end
alias_method :click_on, :click_link_or_button
It calls a selector :link_or_button. This selector is defined here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/selector.rb#L143
Capybara.add_selector(:link_or_button) do
label "link or button"
xpath { |locator| XPath::HTML.link_or_button(locator) }
end
It calls this method: http://rdoc.info/github/jnicklas/xpath/XPath/HTML#link_or_button-instance_method
# File 'lib/xpath/html.rb', line 33
def link_or_button(locator)
link(locator) + button(locator)
end
So i tried to check the presence of the selector and it worked:
page.should have_selector(:link_or_button, 'Click me')
Using the expect syntax
expect(page).to have_selector(:link_or_button, 'Click me')
This works without needing to define a custom matcher.
You can also use a custom matcher
RSpec::Matchers::define :have_link_or_button do |text|
match do |page|
Capybara.string(page.body).has_selector?(:link_or_button, text: text)
end
end
Then do
expect(page).to have_link_or_button('Login')
Personally I would give your button or link an id and look for that using
page.should have_css('#foo')
This way you can refer to the link or button without worrying about its implementation.
I always find this useful: https://gist.github.com/428105
I think you can use the find button instance method:
(Capybara::Element) find_button(locator)
Using id, name, value.
Or if you want a link
(Capybara::Element) find_link(locator)
From: http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders#find_button-instance_method
I had an odd case where some smoke tests marched across various customer-centric login pages that had slight variations on doing the login submit button... Driven by a Cucumber table of user, org, etc.
# A bit of a hack, org_name is normally a subdomain, but sometimes it is the complete domain
def login(user, org_name)
# Use the below to automatically hit each user's org's server
if org_name.include? '.com'
Capybara.app_host = "http://#{org_name}"
else
Capybara.app_host = "http://#{org_name}.mydomain.com"
end
visit '/'
fill_in 'username', :with => user
fill_in 'userpwd', :with => '***'
begin
page.find(:link_or_button, 'submit')
click_on 'submit'
rescue Capybara::ElementNotFound
page.find(:link_or_button, 'Log In')
click_on 'Log In'
rescue Capybara::ElementNotFound
pending "Need to determine how to invoke the Login button for #{org_name} near Line ##{__LINE__} of #{__method__} in #{__FILE__} "
end
# -----------------------
# Work-around for modal popup saying SSL is mismatched if you are using actual production URLs
# The rescue is for cases that do not exhibit the modal pop-up
page.driver.browser.switch_to.alert.accept rescue Selenium::WebDriver::Error::NoAlertPresentError
# Ensure that login was successful
page.should_not have_content 'Login failed'
end
if html:
<a class="top-menu__item">text123
<span class="label">
<span class="label">86</span>
</span>
</a>
not work:
assert page.has_selector?(:link_or_button, text: 'text123')
assert page.should have_selector(:link_or_button, text: 'text123')