1
0
Fork 0
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:
Thomas Walpole 2018-06-20 11:43:21 -07:00
parent faa45e1354
commit df804763c9
17 changed files with 201 additions and 17 deletions

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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).

View 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

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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 = "";

View 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

View 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

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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