Skip to content

Commit

Permalink
Introduce Node#has_element? (#2700)
Browse files Browse the repository at this point in the history
`Node#has_element?` and `Node#has_no_element?`
methods to power `have_element`, `assert_element`, etc.
  • Loading branch information
seanpdoyle authored Dec 11, 2023
1 parent 6267ff6 commit fafcb58
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 2 deletions.
15 changes: 14 additions & 1 deletion lib/capybara/minitest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions lib/capybara/minitest/spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down
25 changes: 25 additions & 0 deletions lib/capybara/node/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions lib/capybara/rspec/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down
1 change: 1 addition & 0 deletions lib/capybara/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
47 changes: 47 additions & 0 deletions lib/capybara/spec/session/has_element_spec.rb
Original file line number Diff line number Diff line change
@@ -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
9 changes: 8 additions & 1 deletion spec/minitest_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
24 changes: 24 additions & 0 deletions spec/rspec/shared_spec_matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,30 @@
end
end

describe 'have_element matcher' do
let(:html) { '<img src="/img.jpg" alt="a JPEG"><img src="/img.png" alt="a PNG">' }

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) { '<a href="#">Just a link</a><a href="#">Another link</a>' }

Expand Down

0 comments on commit fafcb58

Please sign in to comment.