diff --git a/lib/capybara/minitest.rb b/lib/capybara/minitest.rb
index 2d7687395..5f39bad80 100644
--- a/lib/capybara/minitest.rb
+++ b/lib/capybara/minitest.rb
@@ -190,6 +190,19 @@ def assert_#{assertion_name} *args, &optional_filter_block
# @!method assert_no_css
# See {Capybara::Node::Matchers#has_no_css?}
+ ##
+ # Assert that provided element exists
+ #
+ # @!method assert_element
+ # See {Capybara::Node::Matchers#has_element?}
+
+ ##
+ # Assert that provided element does not exist
+ #
+ # @!method assert_no_element
+ # @!method refute_element
+ # See {Capybara::Node::Matchers#has_no_element?}
+
##
# Assert that provided link exists
#
@@ -281,7 +294,7 @@ def assert_#{assertion_name} *args, &optional_filter_block
# @!method assert_no_table
# See {Capybara::Node::Matchers#has_no_table?}
- %w[xpath css link button field select table].each do |selector_type|
+ %w[xpath css element link button field select table].each do |selector_type|
define_method "assert_#{selector_type}" do |*args, &optional_filter_block|
subject, args = determine_subject(args)
locator, options = extract_locator(args)
diff --git a/lib/capybara/minitest/spec.rb b/lib/capybara/minitest/spec.rb
index 6454a52b6..e60e91401 100644
--- a/lib/capybara/minitest/spec.rb
+++ b/lib/capybara/minitest/spec.rb
@@ -95,6 +95,18 @@ module Expectations
# @!method wont_have_field
# See {Capybara::Node::Matchers#has_no_field?}
+ ##
+ # Expectation that there is element
+ #
+ # @!method must_have_element
+ # See {Capybara::Node::Matchers#has_element?}
+
+ ##
+ # Expectation that there is no element
+ #
+ # @!method wont_have_element
+ # See {Capybara::Node::Matchers#has_no_element?}
+
##
# Expectation that there is link
#
diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb
index 8eeeb19e3..9d970fddb 100644
--- a/lib/capybara/node/matchers.rb
+++ b/lib/capybara/node/matchers.rb
@@ -322,6 +322,31 @@ def has_no_css?(path, **options, &optional_filter_block)
has_no_selector?(:css, path, **options, &optional_filter_block)
end
+ ##
+ #
+ # Checks if the page or current node has a element with the given
+ # local name.
+ #
+ # @param [String] locator The local name of a element to check for
+ # @option options [String, Regexp] The attributes values of matching elements
+ # @return [Boolean] Whether it exists
+ #
+ def has_element?(locator = nil, **options, &optional_filter_block)
+ has_selector?(:element, locator, **options, &optional_filter_block)
+ end
+
+ ##
+ #
+ # Checks if the page or current node has no element with the given
+ # local name.
+ #
+ # @param (see #has_element?)
+ # @return [Boolean] Whether it doesn't exist
+ #
+ def has_no_element?(locator = nil, **options, &optional_filter_block)
+ has_no_selector?(:element, locator, **options, &optional_filter_block)
+ end
+
##
#
# Checks if the page or current node has a link with the given
diff --git a/lib/capybara/rspec/matchers.rb b/lib/capybara/rspec/matchers.rb
index 2e1379914..8d9b10898 100644
--- a/lib/capybara/rspec/matchers.rb
+++ b/lib/capybara/rspec/matchers.rb
@@ -83,6 +83,11 @@ def match_selector(...)
end
end
+ # @!method have_element(locator = nil, **options, &optional_filter_block)
+ # RSpec matcher for elements.
+ #
+ # @see Capybara::Node::Matchers#has_element?
+
# @!method have_link(locator = nil, **options, &optional_filter_block)
# RSpec matcher for links.
#
diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb
index 14097bd77..1451a91af 100644
--- a/lib/capybara/session.rb
+++ b/lib/capybara/session.rb
@@ -45,6 +45,7 @@ class Session
fill_in find find_all find_button find_by_id find_field find_link
has_content? has_text? has_css? has_no_content? has_no_text?
has_no_css? has_no_xpath? has_xpath? select uncheck
+ has_element? has_no_element?
has_link? has_no_link? has_button? has_no_button? has_field?
has_no_field? has_checked_field? has_unchecked_field?
has_no_table? has_table? unselect has_select? has_no_select?
diff --git a/lib/capybara/spec/session/has_element_spec.rb b/lib/capybara/spec/session/has_element_spec.rb
new file mode 100644
index 000000000..68496843e
--- /dev/null
+++ b/lib/capybara/spec/session/has_element_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+Capybara::SpecHelper.spec '#has_element?' do
+ before do
+ @session.visit('/with_html')
+ end
+
+ it 'should be true if the given element is on the page' do
+ expect(@session).to have_element('a', id: 'foo')
+ expect(@session).to have_element('a', text: 'A link', href: '/with_simple_html')
+ expect(@session).to have_element('a', text: :'A link', href: :'/with_simple_html')
+ expect(@session).to have_element('a', text: 'A link', href: %r{/with_simple_html})
+ expect(@session).to have_element('a', text: 'labore', target: '_self')
+ end
+
+ it 'should be false if the given element is not on the page' do
+ expect(@session).not_to have_element('a', text: 'monkey')
+ expect(@session).not_to have_element('a', text: 'A link', href: '/nonexistent-href')
+ expect(@session).not_to have_element('a', text: 'A link', href: /nonexistent/)
+ expect(@session).not_to have_element('a', text: 'labore', target: '_blank')
+ end
+
+ it 'should notify if an invalid locator is specified' do
+ allow(Capybara::Helpers).to receive(:warn).and_return(nil)
+ @session.has_element?(@session)
+ expect(Capybara::Helpers).to have_received(:warn).with(/Called from: .+/)
+ end
+end
+
+Capybara::SpecHelper.spec '#has_no_element?' do
+ before do
+ @session.visit('/with_html')
+ end
+
+ it 'should be false if the given element is on the page' do
+ expect(@session).not_to have_no_element('a', id: 'foo')
+ expect(@session).not_to have_no_element('a', text: 'A link', href: '/with_simple_html')
+ expect(@session).not_to have_no_element('a', text: 'labore', target: '_self')
+ end
+
+ it 'should be true if the given element is not on the page' do
+ expect(@session).to have_no_element('a', text: 'monkey')
+ expect(@session).to have_no_element('a', text: 'A link', href: '/nonexistent-href')
+ expect(@session).to have_no_element('a', text: 'A link', href: %r{/nonexistent-href})
+ expect(@session).to have_no_element('a', text: 'labore', target: '_blank')
+ end
+end
diff --git a/spec/minitest_spec.rb b/spec/minitest_spec.rb
index 7a836ca9c..69dd2d77f 100644
--- a/spec/minitest_spec.rb
+++ b/spec/minitest_spec.rb
@@ -60,6 +60,13 @@ def test_assert_selector
refute_selector(:css, 'select#not_form_title')
end
+ def test_assert_element
+ visit('/with_html')
+ assert_element('a', text: 'A link')
+ assert_element(count: 1) { |el| el.text == 'A link' }
+ assert_no_element(text: 'Not on page')
+ end
+
def test_assert_link
visit('/with_html')
assert_link('A link')
@@ -163,6 +170,6 @@ def test_assert_sibling
reporter.start
MinitestTest.run reporter, {}
reporter.report
- expect(output.string).to include('22 runs, 53 assertions, 0 failures, 0 errors, 1 skips')
+ expect(output.string).to include('23 runs, 56 assertions, 0 failures, 0 errors, 1 skips')
end
end
diff --git a/spec/rspec/shared_spec_matchers.rb b/spec/rspec/shared_spec_matchers.rb
index c00b69ed0..f56895848 100644
--- a/spec/rspec/shared_spec_matchers.rb
+++ b/spec/rspec/shared_spec_matchers.rb
@@ -503,6 +503,30 @@
end
end
+ describe 'have_element matcher' do
+ let(:html) { '' }
+
+ it 'gives proper description' do
+ expect(have_element('img').description).to eq('have element "img"')
+ end
+
+ it 'passes if there is such a element' do
+ expect(html).to have_element('img', src: '/img.jpg')
+ end
+
+ it 'fails if there is no such element' do
+ expect do
+ expect(html).to have_element('photo')
+ end.to raise_error(/expected to find element "photo"/)
+ end
+
+ it 'supports compounding' do
+ expect(html).to have_element('img', alt: 'a JPEG').and have_element('img', src: '/img.png')
+ expect(html).to have_element('photo').or have_element('img', src: '/img.jpg')
+ expect(html).to have_no_element('photo').and have_element('img', alt: 'a PNG')
+ end
+ end
+
describe 'have_link matcher' do
let(:html) { 'Just a linkAnother link' }