mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Add has_style? and associated matchers/assertions
This commit is contained in:
parent
faa45e1354
commit
df804763c9
17 changed files with 201 additions and 17 deletions
|
@ -11,6 +11,7 @@ Release date: unreleased
|
|||
* `execute_async_script` can now be called on elements to run the JS in the context of the element
|
||||
* `:download` filter option on `:link' selector
|
||||
* Window#fullscreen
|
||||
* `Element#style` and associated matchers
|
||||
|
||||
### Fixes
|
||||
|
||||
|
|
|
@ -406,6 +406,7 @@ module Capybara
|
|||
require 'capybara/queries/match_query'
|
||||
require 'capybara/queries/ancestor_query'
|
||||
require 'capybara/queries/sibling_query'
|
||||
require 'capybara/queries/style_query'
|
||||
|
||||
require 'capybara/node/finders'
|
||||
require 'capybara/node/matchers'
|
||||
|
|
|
@ -81,9 +81,15 @@ module Capybara
|
|||
# @!method assert_xpath
|
||||
# see {Capybara::Node::Matchers#assert_not_matches_selector}
|
||||
|
||||
## Assert element has the provided CSS styles
|
||||
#
|
||||
# @!method assert_style
|
||||
# see {Capybara::Node::Matchers#assert_style}
|
||||
|
||||
%w[assert_selector assert_no_selector
|
||||
assert_all_of_selectors assert_none_of_selectors
|
||||
assert_matches_selector assert_not_matches_selector].each do |assertion_name|
|
||||
assert_matches_selector assert_not_matches_selector
|
||||
assert_style].each do |assertion_name|
|
||||
class_eval <<-ASSERTION, __FILE__, __LINE__ + 1
|
||||
def #{assertion_name} *args, &optional_filter_block
|
||||
self.assertions +=1
|
||||
|
|
|
@ -15,7 +15,8 @@ module Capybara
|
|||
[%W[assert_#{assertion} must_have_#{assertion}],
|
||||
%W[refute_#{assertion} wont_have_#{assertion}]]
|
||||
end + [%w[assert_all_of_selectors must_have_all_of_selectors],
|
||||
%w[assert_none_of_selectors must_have_none_of_selectors]] +
|
||||
%w[assert_none_of_selectors must_have_none_of_selectors],
|
||||
%w[assert_style must_have_style]] +
|
||||
%w[selector xpath css].flat_map do |assertion|
|
||||
[%W[assert_matches_#{assertion} must_match_#{assertion}],
|
||||
%W[refute_matches_#{assertion} wont_match_#{assertion}]]
|
||||
|
@ -163,6 +164,12 @@ module Capybara
|
|||
#
|
||||
# @!method wont_have_current_path
|
||||
# see {Capybara::SessionMatchers#assert_no_current_path}
|
||||
|
||||
##
|
||||
# Expectation that element has style
|
||||
#
|
||||
# @!method must_have_style
|
||||
# see {Capybara::SessionMatchers#assert_style}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -75,12 +75,15 @@ module Capybara
|
|||
#
|
||||
# Retrieve the given CSS styles
|
||||
#
|
||||
# element.style('color') # => Computed value of CSS 'color' style
|
||||
# element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles
|
||||
#
|
||||
# @param [String] Names of the desired CSS properties
|
||||
# @return [Hash] Hash of the CSS property names to computed values
|
||||
#
|
||||
def style(*styles)
|
||||
styles = styles.flatten.map(&:to_s)
|
||||
raise ArgumentError, "You must specify at least one CSS style" if styles.empty?
|
||||
result = begin
|
||||
begin
|
||||
synchronize { base.style(styles) }
|
||||
rescue NotImplementedError => e
|
||||
begin
|
||||
|
@ -89,7 +92,6 @@ module Capybara
|
|||
raise e
|
||||
end
|
||||
end
|
||||
styles.length == 1 ? result[styles[0]] : result
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -441,8 +443,6 @@ module Capybara
|
|||
%(Obsolete #<Capybara::Node::Element>)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
STYLE_SCRIPT = <<~JS
|
||||
(function(){
|
||||
var s = window.getComputedStyle(this);
|
||||
|
@ -453,7 +453,7 @@ module Capybara
|
|||
}
|
||||
return result;
|
||||
}).apply(this, arguments)
|
||||
JS
|
||||
JS
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,6 +56,21 @@ module Capybara
|
|||
false
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Checks if a an element has the specified CSS styles
|
||||
#
|
||||
# element.has_style?( 'color' => 'rgb(0,0,255)', 'font-size' => /px/ )
|
||||
#
|
||||
# @param styles [Hash]
|
||||
# @return [Boolean] If the styles match
|
||||
#
|
||||
def has_style?(styles, **options)
|
||||
assert_style(styles, **options)
|
||||
rescue Capybara::ExpectationNotMet
|
||||
false
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Asserts that a given selector is on the page or a descendant of the current node.
|
||||
|
@ -97,6 +112,24 @@ module Capybara
|
|||
end
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Asserts that an element has the specified CSS styles
|
||||
#
|
||||
# element.assert_style( 'color' => 'rgb(0,0,255)', 'font-size' => /px/ )
|
||||
#
|
||||
# @param styles [Hash]
|
||||
# @raise [Capybara::ExpectationNotMet] If the element doesn't have the specified styles
|
||||
#
|
||||
def assert_style(styles, **options)
|
||||
query_args = _set_query_session_options(styles, options)
|
||||
query = Capybara::Queries::StyleQuery.new(*query_args)
|
||||
synchronize(query.wait) do
|
||||
raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Asserts that all of the provided selectors are present on the given page
|
||||
# or descendants of the current node. If options are provided, the assertion
|
||||
# will check that each locator is present with those options as well (other than :wait).
|
||||
|
|
41
lib/capybara/queries/style_query.rb
Normal file
41
lib/capybara/queries/style_query.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Capybara
|
||||
# @api private
|
||||
module Queries
|
||||
class StyleQuery < BaseQuery
|
||||
def initialize(expected_styles, session_options:, **options)
|
||||
@expected_styles = expected_styles.each_with_object({}) { |(style, value), str_keys| str_keys[style.to_s] = value }
|
||||
@options = options
|
||||
@actual_styles = {}
|
||||
super(@options)
|
||||
self.session_options = session_options
|
||||
|
||||
assert_valid_keys
|
||||
end
|
||||
|
||||
def resolves_for?(node)
|
||||
@node = node
|
||||
@actual_styles = node.style(*@expected_styles.keys)
|
||||
@expected_styles.all? do |style, value|
|
||||
if value.is_a? Regexp
|
||||
@actual_styles[style] =~ value
|
||||
else
|
||||
@actual_styles[style] == value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message
|
||||
+"Expected node to have styles #{@expected_styles.inspect}. " \
|
||||
"Actual styles were #{@actual_styles.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_keys
|
||||
%i[wait]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24,7 +24,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|||
string_node[name]
|
||||
end
|
||||
|
||||
def style(styles)
|
||||
def style(_styles)
|
||||
raise NotImplementedError, "The rack_test driver does not process CSS"
|
||||
end
|
||||
|
||||
|
|
|
@ -225,6 +225,24 @@ module Capybara
|
|||
end
|
||||
end
|
||||
|
||||
class HaveStyle < Matcher
|
||||
def initialize(*args)
|
||||
@args = args
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
wrap_matches?(actual) { |el| el.assert_style(*@args) }
|
||||
end
|
||||
|
||||
def does_not_match?(_actual)
|
||||
raise ArgumentError, "The have_style matcher does not support use with not_to/should_not"
|
||||
end
|
||||
|
||||
def description
|
||||
"have style"
|
||||
end
|
||||
end
|
||||
|
||||
class BecomeClosed
|
||||
def initialize(options)
|
||||
@options = options
|
||||
|
@ -355,6 +373,12 @@ module Capybara
|
|||
HaveSelector.new(:table, locator, options, &optional_filter_block)
|
||||
end
|
||||
|
||||
# RSpec matcher for element style
|
||||
# See {Capybara::Node::Matchers#has_style?}
|
||||
def have_style(styles, **options)
|
||||
HaveStyle.new(styles, options)
|
||||
end
|
||||
|
||||
%w[selector css xpath text title current_path link button field checked_field unchecked_field select table].each do |matcher_type|
|
||||
define_method "have_no_#{matcher_type}" do |*args, &optional_filter_block|
|
||||
NegatedMatcher.new(send("have_#{matcher_type}", *args, &optional_filter_block))
|
||||
|
|
|
@ -29,9 +29,8 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|||
end
|
||||
|
||||
def style(styles)
|
||||
styles.inject({}) do |memo, style|
|
||||
memo[style] = native.css_value(style)
|
||||
memo
|
||||
styles.each_with_object({}) do |style, result|
|
||||
result[style] = native.css_value(style)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -63,6 +63,11 @@ $(function() {
|
|||
$('title').text('changed title')
|
||||
}, 400)
|
||||
});
|
||||
$('#change-size').click(function() {
|
||||
setTimeout(function() {
|
||||
document.getElementById('change').style.fontSize = '50px';
|
||||
}, 500)
|
||||
});
|
||||
$('#click-test').on({
|
||||
click: function(e) {
|
||||
var desc = "";
|
||||
|
|
26
lib/capybara/spec/session/assert_style_spec.rb
Normal file
26
lib/capybara/spec/session/assert_style_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Capybara::SpecHelper.spec '#assert_style', requires: [:css] do
|
||||
it "should not raise if the elements style contains the given properties" do
|
||||
@session.visit('/with_html')
|
||||
expect do
|
||||
@session.find(:css, '#first').assert_style(display: 'block')
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it "should raise error if the elements style doesn't contain the given properties" do
|
||||
@session.visit('/with_html')
|
||||
expect do
|
||||
@session.find(:css, '#first').assert_style(display: 'inline')
|
||||
end.to raise_error(Capybara::ExpectationNotMet, 'Expected node to have styles {"display"=>"inline"}. Actual styles were {"display"=>"block"}')
|
||||
end
|
||||
|
||||
it "should wait for style", requires: %i[css js] do
|
||||
@session.visit('/with_js')
|
||||
el = @session.find(:css, '#change')
|
||||
@session.click_link("Change size")
|
||||
expect do
|
||||
el.assert_style({ 'font-size': '50px' }, wait: 3)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
25
lib/capybara/spec/session/has_style_spec.rb
Normal file
25
lib/capybara/spec/session/has_style_spec.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Capybara::SpecHelper.spec '#has_style?', requires: [:css] do
|
||||
before do
|
||||
@session.visit('/with_html')
|
||||
end
|
||||
|
||||
it "should be true if the element has the given style" do
|
||||
expect(@session.find(:css, '#first')).to have_style(display: 'block')
|
||||
expect(@session.find(:css, '#first').has_style?(display: 'block')).to be true
|
||||
expect(@session.find(:css, '#second')).to have_style('display' => 'inline')
|
||||
expect(@session.find(:css, '#second').has_style?('display' => 'inline')).to be true
|
||||
end
|
||||
|
||||
it "should be false if the element does not have the given style" do
|
||||
expect(@session.find(:css, '#first').has_style?('display' => 'inline')).to be false
|
||||
expect(@session.find(:css, '#second').has_style?(display: 'block')).to be false
|
||||
end
|
||||
|
||||
it "allows Regexp for value matching" do
|
||||
expect(@session.find(:css, '#first')).to have_style(display: /^bl/)
|
||||
expect(@session.find(:css, '#first').has_style?('display' => /^bl/)).to be true
|
||||
expect(@session.find(:css, '#first').has_style?(display: /^in/)).to be false
|
||||
end
|
||||
end
|
|
@ -53,12 +53,12 @@ Capybara::SpecHelper.spec "node" do
|
|||
|
||||
describe "#style", requires: [:css] do
|
||||
it "should return the computed style value" do
|
||||
expect(@session.find(:css, '#first').style('display')).to eq 'block'
|
||||
expect(@session.find(:css, '#second').style(:display)).to eq 'inline'
|
||||
expect(@session.find(:css, '#first').style('display')).to eq('display' => 'block')
|
||||
expect(@session.find(:css, '#second').style(:display)).to eq('display' => 'inline')
|
||||
end
|
||||
|
||||
it "should return multiple style values" do
|
||||
expect(@session.find(:css, '#first').style('display', :'line-height')).to eq({ 'display' => 'block', 'line-height' => '25px' })
|
||||
expect(@session.find(:css, '#first').style('display', :'line-height')).to eq('display' => 'block', 'line-height' => '25px')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -72,6 +72,10 @@
|
|||
<a href="#" id="change-title">Change title</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#" id="change-size">Change size</a>
|
||||
</p>
|
||||
|
||||
<p id="click-test">Click me</p>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -112,6 +112,12 @@ class MinitestTest < Minitest::Test
|
|||
assert_matches_xpath(find(:select, 'form_title'), './/select[@id="form_title"]')
|
||||
refute_matches_xpath(find(:select, 'form_title'), './/select[@id="form_other_title"]')
|
||||
end
|
||||
|
||||
def test_assert_style
|
||||
skip "Rack test doesn't support style" if Capybara.current_driver == :rack_test
|
||||
visit('/with_html')
|
||||
assert_style(find(:css, '#second'), display: 'inline')
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe 'capybara/minitest' do
|
||||
|
@ -126,6 +132,6 @@ RSpec.describe 'capybara/minitest' do
|
|||
reporter.start
|
||||
MinitestTest.run reporter, {}
|
||||
reporter.report
|
||||
expect(output.string).to include("17 runs, 44 assertions, 0 failures, 0 errors, 0 skips")
|
||||
expect(output.string).to include("18 runs, 44 assertions, 0 failures, 0 errors, 1 skips")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -116,6 +116,12 @@ class MinitestSpecTest < Minitest::Spec
|
|||
it "handles failures" do
|
||||
page.must_have_select('non_existing_form_title')
|
||||
end
|
||||
|
||||
it "supports style expectations" do
|
||||
skip "Rack test doesn't support style" if Capybara.current_driver == :rack_test
|
||||
visit('/with_html')
|
||||
find(:css, '#second').must_have_style('display' => 'inline')
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe 'capybara/minitest/spec' do
|
||||
|
@ -130,7 +136,7 @@ RSpec.describe 'capybara/minitest/spec' do
|
|||
reporter.start
|
||||
MinitestSpecTest.run reporter, {}
|
||||
reporter.report
|
||||
expect(output.string).to include("18 runs, 41 assertions, 1 failures, 0 errors, 0 skips")
|
||||
expect(output.string).to include("19 runs, 41 assertions, 1 failures, 0 errors, 1 skips")
|
||||
# Make sure error messages are displayed
|
||||
expect(output.string).to include('expected to find visible select box "non_existing_form_title" that is not disabled but there were no matches')
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue