diff --git a/History.md b/History.md index 0568062fb..c9f75f6c8 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,7 @@ Release date: unreleased * Workaround geckodriver/firefox send_keys issues as much as possible using the Selenium actions API * Workaround lack of HTML5 native drag and drop events when using Selenium driver with Chrome and FF >= 62 +* `Capybara.predicates_wait` option which sets whether or not Capybaras matcher predicate methods (`has_css?`, `has_selector?`, `has_text?`, etc.) default to using waiting/retrying behavior (defaults to true) # Version 3.5.1 Release date: 2018-08-03 diff --git a/lib/capybara.rb b/lib/capybara.rb index 4ea806df8..58f0acf95 100644 --- a/lib/capybara.rb +++ b/lib/capybara.rb @@ -84,6 +84,7 @@ class << self # [server = Symbol] The name of the registered server to use when running the app under test (Default: :webrick) # [default_set_options = Hash] The default options passed to Node::set (Default: {}) # [test_id = Symbol/String/nil] Optional attribute to match locator aginst with builtin selectors along with id (Default: nil) + # [predicates_wait = Boolean] Whether Capybaras predicate matchers use waiting behavior by default (Default: true) # # === DSL Options # @@ -484,6 +485,7 @@ module Selenium; end config.reuse_server = true config.default_set_options = {} config.test_id = nil + config.predicates_wait = true end Capybara.register_driver :rack_test do |app| diff --git a/lib/capybara/node/document_matchers.rb b/lib/capybara/node/document_matchers.rb index 646a2ef7e..e25dddd1a 100644 --- a/lib/capybara/node/document_matchers.rb +++ b/lib/capybara/node/document_matchers.rb @@ -38,9 +38,7 @@ def assert_no_title(title, **options) # @return [Boolean] # def has_title?(title, **options) - assert_title(title, options) - rescue Capybara::ExpectationNotMet - false + make_predicate(options) { assert_title(title, options) } end ## @@ -50,9 +48,7 @@ def has_title?(title, **options) # @return [Boolean] # def has_no_title?(title, **options) - assert_no_title(title, options) - rescue Capybara::ExpectationNotMet - false + make_predicate(options) { assert_no_title(title, options) } end private diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb index 5558fc9e9..b7db196a2 100644 --- a/lib/capybara/node/matchers.rb +++ b/lib/capybara/node/matchers.rb @@ -36,10 +36,8 @@ module Matchers # @option args [Range] :between (nil) Range of times that should contain number of times text occurs # @return [Boolean] If the expression exists # - def has_selector?(*args, &optional_filter_block) - assert_selector(*args, &optional_filter_block) - rescue Capybara::ExpectationNotMet - false + def has_selector?(*args, **options, &optional_filter_block) + make_predicate(options) { assert_selector(*args, options, &optional_filter_block) } end ## @@ -50,10 +48,8 @@ def has_selector?(*args, &optional_filter_block) # @param (see Capybara::Node::Finders#has_selector?) # @return [Boolean] # - def has_no_selector?(*args, &optional_filter_block) - assert_no_selector(*args, &optional_filter_block) - rescue Capybara::ExpectationNotMet - false + def has_no_selector?(*args, **options, &optional_filter_block) + make_predicate(options) { assert_no_selector(*args, options, &optional_filter_block) } end ## @@ -66,9 +62,7 @@ def has_no_selector?(*args, &optional_filter_block) # @return [Boolean] If the styles match # def has_style?(styles, **options) - assert_style(styles, **options) - rescue Capybara::ExpectationNotMet - false + make_predicate(options) { assert_style(styles, options) } end ## @@ -554,10 +548,8 @@ def refute_matches_selector(*args, &optional_filter_block) # @param (see Capybara::Node::Finders#has_selector?) # @return [Boolean] # - def matches_selector?(*args, &optional_filter_block) - assert_matches_selector(*args, &optional_filter_block) - rescue Capybara::ExpectationNotMet - false + def matches_selector?(*args, **options, &optional_filter_block) + make_predicate(options) { assert_matches_selector(*args, options, &optional_filter_block) } end ## @@ -590,10 +582,8 @@ def matches_css?(css, **options, &optional_filter_block) # @param (see Capybara::Node::Finders#has_selector?) # @return [Boolean] # - def not_matches_selector?(*args, &optional_filter_block) - assert_not_matches_selector(*args, &optional_filter_block) - rescue Capybara::ExpectationNotMet - false + def not_matches_selector?(*args, **options, &optional_filter_block) + make_predicate(options) { assert_not_matches_selector(*args, options, &optional_filter_block) } end ## @@ -683,10 +673,8 @@ def assert_no_text(*args) # @macro text_query_params # @return [Boolean] Whether it exists # - def has_text?(*args) - assert_text(*args) - rescue Capybara::ExpectationNotMet - false + def has_text?(*args, **options) + make_predicate(options) { assert_text(*args, options) } end alias_method :has_content?, :has_text? @@ -697,10 +685,8 @@ def has_text?(*args) # @macro text_query_params # @return [Boolean] Whether it doesn't exist # - def has_no_text?(*args) - assert_no_text(*args) - rescue Capybara::ExpectationNotMet - false + def has_no_text?(*args, **options) + make_predicate(options) { assert_no_text(*args, options) } end alias_method :has_no_content?, :has_no_text? @@ -745,6 +731,13 @@ def _set_query_session_options(*query_args, **query_options) query_options[:session_options] = session_options query_args.push(query_options) end + + def make_predicate(options) + options[:wait] = 0 unless options.key?(:wait) || session_options.predicates_wait + yield + rescue Capybara::ExpectationNotMet + false + end end end end diff --git a/lib/capybara/session/config.rb b/lib/capybara/session/config.rb index 266ee0868..f9faddcc2 100644 --- a/lib/capybara/session/config.rb +++ b/lib/capybara/session/config.rb @@ -7,7 +7,8 @@ class SessionConfig OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements automatic_reload match exact exact_text raise_server_errors visible_text_only automatic_label_click enable_aria_label save_path asset_host default_host app_host - server_host server_port server_errors default_set_options disable_animation test_id].freeze + server_host server_port server_errors default_set_options disable_animation test_id + predicates_wait].freeze attr_accessor(*OPTIONS) diff --git a/lib/capybara/session/matchers.rb b/lib/capybara/session/matchers.rb index 1eb69268a..3ac8bc8db 100644 --- a/lib/capybara/session/matchers.rb +++ b/lib/capybara/session/matchers.rb @@ -47,9 +47,7 @@ def assert_no_current_path(path, **options) # @return [Boolean] # def has_current_path?(path, **options) - assert_current_path(path, options) - rescue Capybara::ExpectationNotMet - false + make_predicate(options) { assert_current_path(path, options) } end ## @@ -62,9 +60,7 @@ def has_current_path?(path, **options) # @return [Boolean] # def has_no_current_path?(path, **options) - assert_no_current_path(path, options) - rescue Capybara::ExpectationNotMet - false + make_predicate(options) { assert_no_current_path(path, options) } end private @@ -76,5 +72,12 @@ def _verify_current_path(path, options) end true end + + def make_predicate(options) + options[:wait] = 0 unless options.key?(:wait) || config.predicates_wait + yield + rescue Capybara::ExpectationNotMet + false + end end end diff --git a/lib/capybara/spec/session/has_css_spec.rb b/lib/capybara/spec/session/has_css_spec.rb index 47c06c514..c1989319d 100644 --- a/lib/capybara/spec/session/has_css_spec.rb +++ b/lib/capybara/spec/session/has_css_spec.rb @@ -30,6 +30,26 @@ expect(@session).to have_css("input[type='submit'][value='New Here']") end + context 'with predicates_wait == true' do + it 'should wait for content to appear', requires: [:js] do + Capybara.predicates_wait = true + Capybara.default_max_wait_time = 2 + @session.visit('/with_js') + @session.click_link('Click me') + expect(@session.has_css?("input[type='submit'][value='New Here']")).to be true + end + end + + context 'with predicates_wait == false' do + it 'should not wait for content to appear', requires: [:js] do + Capybara.predicates_wait = false + Capybara.default_max_wait_time = 2 + @session.visit('/with_js') + @session.click_link('Click me') + expect(@session.has_css?("input[type='submit'][value='New Here']")).to be false + end + end + context 'with between' do it 'should be true if the content occurs within the range given' do expect(@session).to have_css('p', between: 1..4) diff --git a/lib/capybara/spec/session/has_title_spec.rb b/lib/capybara/spec/session/has_title_spec.rb index 9a31cbfb2..3240019c0 100644 --- a/lib/capybara/spec/session/has_title_spec.rb +++ b/lib/capybara/spec/session/has_title_spec.rb @@ -7,6 +7,7 @@ it 'should be true if the page has the given title' do expect(@session).to have_title('with_js') + expect(@session.has_title?('with_js')).to be true end it 'should allow regexp matches' do @@ -21,6 +22,7 @@ it 'should be false if the page has not the given title' do expect(@session).not_to have_title('monkey') + expect(@session.has_title?('monkey')).to be false end it 'should default to exact: false matching' do @@ -31,6 +33,8 @@ it 'should match exactly if exact: true option passed' do expect(@session).to have_title('with_js', exact: true) expect(@session).not_to have_title('with_', exact: true) + expect(@session.has_title?('with_js', exact: true)).to be true + expect(@session.has_title?('with_', exact: true)).to be false end it 'should match partial if exact: false option passed' do @@ -62,5 +66,6 @@ it 'should be true if the page has not the given title' do expect(@session).to have_no_title('monkey') + expect(@session.has_no_title?('monkey')).to be true end end diff --git a/lib/capybara/spec/spec_helper.rb b/lib/capybara/spec/spec_helper.rb index b2f7c9dee..3806cb581 100644 --- a/lib/capybara/spec/spec_helper.rb +++ b/lib/capybara/spec/spec_helper.rb @@ -34,6 +34,7 @@ def reset! Capybara.default_set_options = {} Capybara.disable_animation = false Capybara.test_id = nil + Capybara.predicates_wait = true reset_threadsafe end