How to reuse code in Capybara

爱⌒轻易说出口 提交于 2020-01-02 11:29:09

问题


I have a bunch of codes with repeating structures in a feature test in Rails. I would like to dry up my spec by reusing the structure. Any suggestions?

An example is:

feature "Search page"
  subject { page }

  it "should display results"
    # do something

    within "#A" do
      should have_content("James")
    end
    within "#B" do
      should have_content("October 2014")
    end

    # do something else

    # code with similar structure
    within "#A" do
      should have_content("Amy")
    end
    within "#B" do
      should have_content("May 2011")
    end
  end

At first, I tried to define a custom matcher in RSpec, but when I add within block, it did not seem to work. My guess is within is specific to Capybara, and cannot be used in custom matcher in RSpec.


回答1:


Why not factor the common code into helper methods in a module. Then you can include that module in your spec_helper.rb file

I usually put common code like user_login in such a module in a file in the spec/support folder

spec_helper.rb

#Load all files in spec/support
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

RSpec.configure do |config|
  #some config

  config.include LoginHelper

  #more config
end

spec/support/login_helper.rb

module LoginHelper
  def do_login(username, password)
   visit root_path
     within("#LoginForm") do
       fill_in('username', :with => username)
       fill_in('password', :with => password)
      click_button('submit')
     end
   end
end



回答2:


I don't think you're using within as a matcher, since a matcher would be used after a should, should_not, etc. You can load custom, non-matcher methods into your specs by writing a module and including it in your spec_helper.rb config block, e.g.:

spec/support/my_macros.rb

module MyMacros
  def within(tag, &block)
    # your code here
  end
end

spec/spec_helper.rb

require 'spec/support/my_macros'
...
RSpec.configure do |config|
  config.include MyMacros
  ...
end



回答3:


I'm using Capybara + Cucumber for end-to-end testing. In the end, I think I've pretty much done what both @hraynaud and @eirikir suggest (directionally speaking) - although the details are different since I'm in the Cucumber context. So, consider this not a whole different idea - but maybe a slightly more complete description and discussion. Also, note that my examples focus on testing results - not navigation and form filling. Since it looked like you were in a testing mindset (given your use of should have_content), I thought this might be of interest.

In general, my approach is:

  1. Wrap Capybara tests in validation helper methods within a module. The motivation for wrapping is (a) to save me from having to remember Capybara syntax and (b) to avoid having to type all those repetitive test statements. Also, it ends up making my tests cleaner and more readable (at least for me).
  2. Create a generic validate method that receives (i) a validation helper method name (as a symbol) and (ii) an array of items each of which is to be passed to the validation helper. The validate method simply iterates over the array of items and calls the validation helper method (using the send method), passing each item along with each call.
  3. Attach the helpers and generic validate method to World (read more about World here) so that they are available throughout my Cucumber tests.
  4. Enjoy testing happiness!

Steps 1-3 happen in a file called form_validation_helpers.rb.

features/support/form_validation_helpers.rb

module FormValidationHelpers

  ...more methods before

  # ============================================================================
  #   Tests that an element is on the page
  # ============================================================================
  def is_present(element)
    expect(find(element)).to be_truthy
  end

  # ============================================================================
  #   Tests for the number of times an element appears on a page
  # ============================================================================
  def number_of(options={})
    page.should have_css(options[:element], count: options[:count])
  end

  # ============================================================================
  #   Checks that a page has content
  # ============================================================================
  def page_has_content(content)
    page.should have_content(content)
  end

  ...more methods after

  # ============================================================================
  #   The generic validation method
  # ============================================================================
  def validate(validation, *items)
    items.each do |item|
      send(validation, item)
    end
  end

end
World(FormValidationHelpers)

Step 4 (from above) happens in my step files.

features/step_definitions/sample_steps.rb

Then(/^she sees the organization events content$/) do

  validate :number_of,
    {element: 'ul#organization-tabs li.active', is: 1}

  validate :is_present,
    "ul#organization-tabs li#events-tab.active"

  validate :page_has_content,
    "A Sample Organization that Does Something Good",
    "We do all sorts of amazing things that you're sure to love."

end

As you can see from the validate :page_has_content example, I can run the test multiple times by adding the appropriate arguments onto the validate call (since the validate method receives everything after the first argument into an array).

I like having very specific selectors in my tests - so I can be sure I'm testing the right element. But, when I start changing my view files, I start breaking my tests (bad) and I have to go back and fix all the selectors in my tests - wherever they may be. So, I made a bunch of selector helpers and attached them to World the same as above.

features/support/form_selectors_helpers.rb

module FormSelectorsHelper

  ...more _selector methods before

  def event_summary_selector
    return 'input#event_summary[type="text"]'
  end

  ...more _selector methods after

end
World(FormSelectorsHelper)

So now, I have only one place where I need to keep my selectors up to date and accurate. Usage is as follows (note that I can pass whatever the validation helper method needs - strings, methods, hashes, arrays, etc.)...

features/step_definitions/more_sample_steps.rb

Then(/^she sees new event form$/) do
  validate :is_present, 
    event_summary_selector,
    start_date_input_selector,
    start_time_input_selector,
    end_time_input_selector

  validate :is_absent, 
    end_date_input_selector

  validate :is_unchecked, 
    all_day_event_checkbox_selector,
    use_recur_rule_checkbox_selector

  validate :is_disabled, 
    submit_button_selector

  validate :has_text,
    { element: modal_title_bar_selector, text: "Okay, let's create a new event!" } 

end

Turning back to your question, I imagine you could end up with something like:

feature "Search page"
  subject { page }

  it "should display results"

    # do something
    validate :has_content_within,
      [a_selector, "James"],
      [b_selector, "October 2014"]

    # do something else
    validate :has_content_within,
      [a_selector, "Amy"],
      [b_selector, "May 2011"]

  end


来源:https://stackoverflow.com/questions/26352251/how-to-reuse-code-in-capybara

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!