diff --git a/README.md b/README.md index dec5576c..4a458730 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ You can read more about the missing features [here](https://github.com/teamcapyb - [Using Capybara with Cucumber](#using-capybara-with-cucumber) - [Using Capybara with RSpec](#using-capybara-with-rspec) - [Using Capybara with Test::Unit](#using-capybara-with-testunit) +- [Using Capybara with MiniTest](#using-capybara-with-minitest) - [Using Capybara with MiniTest::Spec](#using-capybara-with-minitestspec) - [Drivers](#drivers) - [Selecting the Driver](#selecting-the-driver) @@ -291,14 +292,15 @@ class BlogTest < ActionDispatch::IntegrationTest end ``` +## Using Capybara with MiniTest + +Set up your base class as with Test::Unit and additionally require capybara/minitest and include ::Capybara::Minitest::Assertions into +your base class + + ## Using Capybara with MiniTest::Spec -Set up your base class as with Test::Unit. (On Rails, the right base class -could be something other than ActionDispatch::IntegrationTest.) - -The capybara_minitest_spec gem ([GitHub](https://github.com/ordinaryzelig/capybara_minitest_spec), -[rubygems.org](https://rubygems.org/gems/capybara_minitest_spec)) provides MiniTest::Spec -expectations for Capybara. For example: +Follow the above instructions for MiniTest and additionally require capybara/minitest/spec ```ruby page.must_have_content('Important!') diff --git a/capybara.gemspec b/capybara.gemspec index 903a6ae4..20e5e542 100644 --- a/capybara.gemspec +++ b/capybara.gemspec @@ -35,6 +35,7 @@ Gem::Specification.new do |s| s.add_development_dependency("yard", [">= 0.5.8"]) s.add_development_dependency("fuubar", [">= 0.0.1"]) s.add_development_dependency("cucumber", [">= 0.10.5"]) + s.add_development_dependency("minitest") s.add_development_dependency("rake") s.add_development_dependency("pry") s.add_development_dependency("erubi") # dependency specification needed by rbx diff --git a/lib/capybara/minitest.rb b/lib/capybara/minitest.rb new file mode 100644 index 00000000..9e8ebbe4 --- /dev/null +++ b/lib/capybara/minitest.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true +require 'minitest' +require 'capybara/dsl' + +module Capybara + module Minitest + module Assertions + def assert_text(*args) + self.assertions += 1 + subject, *args = determine_subject(args) + subject.assert_text(*args) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + alias_method :assert_content, :assert_text + + def assert_no_text(*args) + self.assertions += 1 + subject, *args = determine_subject(args) + subject.assert_no_text(*args) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + alias_method :refute_text, :assert_no_text + alias_method :refute_content, :refute_text + alias_method :assert_no_content, :refute_text + + def assert_selector(*args, &optional_filter_block) + self.assertions +=1 + subject, *args = determine_subject(args) + subject.assert_selector(*args, &optional_filter_block) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + + def assert_no_selector(*args, &optional_filter_block) + self.assertions +=1 + subject, *args = determine_subject(args) + subject.assert_no_selector(*args, &optional_filter_block) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + alias_method :refute_selector, :assert_no_selector + + def assert_matches_selector(*args) + self.assertions += 1 + subject, *args = determine_subject(args) + subject.assert_matches_selector(*args) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + + def assert_not_matches_selector(*args) + self.assertions += 1 + subject, *args = determine_subject(args) + subject.assert_not_matches_selector(*args) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + alias_method :refute_matches_selector, :assert_not_matches_selector + + %w(title current_path).each do |selector_type| + define_method "assert_#{selector_type}" do |*args| + begin + self.assertions += 1 + subject, *args = determine_subject(args) + subject.public_send("assert_#{selector_type}",*args) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + end + + define_method "assert_no_#{selector_type}" do |*args| + begin + self.assertions += 1 + subject, *args = determine_subject(args) + subject.public_send("assert_no_#{selector_type}",*args) + rescue Capybara::ExpectationNotMet => e + raise ::Minitest::Assertion, e.message + end + end + alias_method "refute_#{selector_type}", "assert_no_#{selector_type}" + end + + %w(xpath css 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 = *args, {} + locator, options = nil, locator if locator.is_a? Hash + assert_selector(subject, selector_type.to_sym, locator, options, &optional_filter_block) + end + + define_method "assert_no_#{selector_type}" do |*args, &optional_filter_block| + subject, *args = determine_subject(args) + locator, options = *args, {} + locator, options = nil, locator if locator.is_a? Hash + assert_no_selector(subject, selector_type.to_sym, locator, options, &optional_filter_block) + end + alias_method "refute_#{selector_type}", "assert_no_#{selector_type}" + end + + %w(xpath css).each do |selector_type| + define_method "assert_matches_#{selector_type}" do |*args, &optional_filter_block| + subject, *args = determine_subject(args) + assert_matches_selector(subject, selector_type.to_sym, *args, &optional_filter_block) + end + + define_method "assert_not_matches_#{selector_type}" do |*args, &optional_filter_block| + subject, *args = determine_subject(args) + assert_not_matches_selector(subject, selector_type.to_sym, *args, &optional_filter_block) + end + alias_method "refute_matches_#{selector_type}", "assert_not_matches_#{selector_type}" + end + + %w(checked unchecked).each do |field_type| + define_method "assert_#{field_type}_field" do |*args, &optional_filter_block| + subject, *args = determine_subject(args) + locator, options = *args, {} + locator, options = nil, locator if locator.is_a? Hash + assert_selector(subject, :field, locator, options.merge(field_type.to_sym => true), &optional_filter_block) + end + + define_method "assert_no_#{field_type}_field" do |*args, &optional_filter_block| + subject, *args = determine_subject(args) + locator, options = *args, {} + locator, options = nil, locator if locator.is_a? Hash + assert_no_selector(subject, :field, locator, options.merge(field_type.to_sym => true), &optional_filter_block) + end + alias_method "refute_#{field_type}_field", "assert_no_#{field_type}_field" + end + + private + + def determine_subject(args) + case args.first + when Capybara::Session, Capybara::Node::Base, Capybara::Node::Simple + args + else + [page, *args] + end + end + end + end +end diff --git a/lib/capybara/minitest/spec.rb b/lib/capybara/minitest/spec.rb new file mode 100644 index 00000000..c58f7081 --- /dev/null +++ b/lib/capybara/minitest/spec.rb @@ -0,0 +1,69 @@ +require 'minitest/spec' + +module Capybara + module Minitest + module Expectations + %w(text content title current_path).each do |assertion| + infect_an_assertion "assert_#{assertion}", "must_have_#{assertion}", :reverse + infect_an_assertion "refute_#{assertion}", "wont_have_#{assertion}", :reverse + end + + # Unfortunately infect_an_assertion doesn't pass through the optional filter block so we can't use it for these + %w(selector xpath css link button field select table checked_field unchecked_field).each do |assertion| + self.class_eval <<-EOM + def must_have_#{assertion} *args, &optional_filter_block + ::Minitest::Expectation.new(self, ::Minitest::Spec.current).must_have_#{assertion}(*args, &optional_filter_block) + end + + def wont_have_#{assertion} *args, &optional_filter_block + ::Minitest::Expectation.new(self, ::Minitest::Spec.current).wont_have_#{assertion}(*args, &optional_filter_block) + end + EOM + + ::Minitest::Expectation.class_eval <<-EOM, __FILE__, __LINE__ + 1 + def must_have_#{assertion} *args, &optional_filter_block + ctx.assert_#{assertion}(target, *args, &optional_filter_block) + end + + def wont_have_#{assertion} *args, &optional_filter_block + ctx.refute_#{assertion}(target, *args, &optional_filter_block) + end + EOM + end + + %w(selector xpath css).each do |assertion| + self.class_eval <<-EOM + def must_match_#{assertion} *args, &optional_filter_block + ::Minitest::Expectation.new(self, ::Minitest::Spec.current).must_match_#{assertion}(*args, &optional_filter_block) + end + + def wont_match_#{assertion} *args, &optional_filter_block + ::Minitest::Expectation.new(self, ::Minitest::Spec.current).wont_match_#{assertion}(*args, &optional_filter_block) + end + EOM + + ::Minitest::Expectation.class_eval <<-EOM, __FILE__, __LINE__ + 1 + def must_match_#{assertion} *args, &optional_filter_block + ctx.assert_matches_#{assertion}(target, *args, &optional_filter_block) + end + + def wont_match_#{assertion} *args, &optional_filter_block + ctx.refute_matches_#{assertion}(target, *args, &optional_filter_block) + end + EOM + end + end + end +end + +class Capybara::Session + include Capybara::Minitest::Expectations unless ENV["MT_NO_EXPECTATIONS"] +end + +class Capybara::Node::Base + include Capybara::Minitest::Expectations unless ENV["MT_NO_EXPECTATIONS"] +end + +class Capybara::Node::Simple + include Capybara::Minitest::Expectations unless ENV["MT_NO_EXPECTATIONS"] +end diff --git a/spec/minitest_spec.rb b/spec/minitest_spec.rb new file mode 100644 index 00000000..3d77534b --- /dev/null +++ b/spec/minitest_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true +require 'spec_helper' +require 'capybara/minitest' + +class MinitestTest < Minitest::Test + include Capybara::DSL + include Capybara::Minitest::Assertions + + def setup + visit('/form') + end + + def teardown + Capybara.reset_sessions! + end + + def test_assert_text + assert_text('Form') + assert_no_text('Not on the page') + refute_text('Also Not on the page') + end + + def test_assert_title + visit('/with_title') + assert_title('Test Title') + assert_no_title('Not the title') + refute_title('Not the title') + end + + def test_assert_current_path + assert_current_path('/form') + assert_no_current_path('/not_form') + refute_current_path('/not_form') + end + + def test_assert_xpath + assert_xpath('.//select[@id="form_title"]') + assert_xpath('.//select', count: 1) { |el| el[:id] == "form_title" } + assert_no_xpath('.//select[@id="not_form_title"]') + assert_no_xpath('.//select') { |el| el[:id] == "not_form_title"} + refute_xpath('.//select[@id="not_form_title"]') + end + + def test_assert_css + assert_css('select#form_title') + assert_no_css('select#not_form_title') + end + + def test_assert_link + visit('/with_html') + assert_link('A link') + assert_link(count: 1){ |el| el.text == 'A link'} + assert_no_link('Not on page') + end + + def test_assert_button + assert_button('fresh_btn') + assert_button(count: 1){ |el| el[:id] == 'fresh_btn' } + assert_no_button('not_btn') + end + + def test_assert_field + assert_field('customer_email') + assert_no_field('not_on_the_form') + end + + def test_assert_select + assert_select('form_title') + assert_no_select('not_form_title') + end + + def test_assert_checked_field + assert_checked_field('form_pets_dog') + assert_no_checked_field('form_pets_cat') + refute_checked_field('form_pets_snake') + end + + def test_assert_unchecked_field + assert_unchecked_field('form_pets_cat') + assert_no_unchecked_field('form_pets_dog') + refute_unchecked_field('form_pets_snake') + end + + def test_assert_table + visit('/tables') + assert_table('agent_table') + assert_no_table('not_on_form') + refute_table('not_on_form') + end + + def test_assert_matches_selector + assert_matches_selector(find(:field, 'customer_email'), :field, 'customer_email') + assert_not_matches_selector(find(:select, 'form_title'), :field, 'customer_email') + refute_matches_selector(find(:select, 'form_title'), :field, 'customer_email') + end + + def test_assert_matches_css + assert_matches_css(find(:select, 'form_title'), 'select#form_title') + refute_matches_css(find(:select, 'form_title'), 'select#form_other_title') + end + + def test_assert_matches_xpath + assert_matches_xpath(find(:select, 'form_title'), './/select[@id="form_title"]') + refute_matches_xpath(find(:select, 'form_title'), './/select[@id="form_other_title"]') + end +end + +RSpec.describe 'capybara/minitest' do + before do + Capybara.current_driver = :rack_test + Capybara.app = TestApp + end + + it "should support minitest" do + output = StringIO.new + reporter = Minitest::SummaryReporter.new(output) + reporter.start + MinitestTest.run reporter, {} + reporter.report + expect(output.string).to include("15 runs, 42 assertions, 0 failures, 0 errors, 0 skips") + end +end diff --git a/spec/minitest_spec_spec.rb b/spec/minitest_spec_spec.rb new file mode 100644 index 00000000..a19b8090 --- /dev/null +++ b/spec/minitest_spec_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true +require 'spec_helper' +require 'capybara/minitest' +require 'capybara/minitest/spec' + +class MinitestSpecTest < Minitest::Spec + include ::Capybara::DSL + include ::Capybara::Minitest::Assertions + + before do + visit('/form') + end + after do + Capybara.reset_sessions! + end + + it "supports text expectations" do + page.must_have_text('Form', minimum: 1) + page.wont_have_text('Not a form') + form = find(:css, 'form', text: 'Title') + form.must_have_text('Customer Email') + form.wont_have_text('Some other email') + end + + it "supports current_path expectations" do + page.must_have_current_path('/form') + page.wont_have_current_path('/form2') + end + + it "supports title expectations" do + visit('/with_title') + page.must_have_title('Test Title') + page.wont_have_title('Not the title') + end + + it "supports xpath expectations" do + page.must_have_xpath('.//input[@id="customer_email"]') + page.wont_have_xpath('.//select[@id="not_form_title"]') + page.wont_have_xpath('.//input[@id="customer_email"]') { |el| el[:id] == "not_customer_email" } + el = find(:select, 'form_title') + el.must_have_xpath('.//option[@class="title"]') + el.must_have_xpath('.//option', count: 1) { |el| el[:class] != 'title' && !el.disabled?} + el.wont_have_xpath('.//input[@id="customer_email"]') + end + + it "support css expectations" do + page.must_have_css('input#customer_email') + page.wont_have_css('select#not_form_title') + el = find(:select, 'form_title') + el.must_have_css('option.title') + el.wont_have_css('input#customer_email') + end + + it "supports link expectations" do + visit('/with_html') + page.must_have_link('A link') + page.wont_have_link('Not on page') + end + + it "supports button expectations" do + page.must_have_button('fresh_btn') + page.wont_have_button('not_btn') + end + + it "supports field expectations" do + page.must_have_field('customer_email') + page.wont_have_field('not_on_the_form') + end + + it "supports select expectations" do + page.must_have_select('form_title') + page.wont_have_select('not_form_title') + end + + it "supports checked_field expectations" do + page.must_have_checked_field('form_pets_dog') + page.wont_have_checked_field('form_pets_cat') + end + + it "supports unchecked_field expectations" do + page.must_have_unchecked_field('form_pets_cat') + page.wont_have_unchecked_field('form_pets_dog') + end + + it "supports table expectations" do + visit('/tables') + page.must_have_table('agent_table') + page.wont_have_table('not_on_form') + end + + it "supports match_selector expectations" do + find(:field, 'customer_email').must_match_selector(:field, 'customer_email') + find(:select, 'form_title').wont_match_selector(:field, 'customer_email') + end + + it "supports match_css expectations" do + find(:select, 'form_title').must_match_css('select#form_title') + find(:select, 'form_title').wont_match_css('select#form_other_title') + end + + it "supports match_xpath expectations" do + find(:select, 'form_title').must_match_xpath('.//select[@id="form_title"]') + find(:select, 'form_title').wont_match_xpath('.//select[@id="not_on_page"]') + end +end + +RSpec.describe 'capybara/minitest/spec' do + before do + Capybara.current_driver = :rack_test + Capybara.app = TestApp + end + + it "should support minitest spec" do + output = StringIO.new + reporter = Minitest::SummaryReporter.new(output) + reporter.start + MinitestSpecTest.run reporter, {} + reporter.report + expect(output.string).to include("15 runs, 38 assertions, 0 failures, 0 errors, 0 skips") + end +end