capybara_test_helpers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/README.md +401 -0
- data/lib/capybara_test_helpers.rb +6 -0
- data/lib/capybara_test_helpers/actions.rb +116 -0
- data/lib/capybara_test_helpers/assertions.rb +124 -0
- data/lib/capybara_test_helpers/benchmark_helpers.rb +94 -0
- data/lib/capybara_test_helpers/config.rb +56 -0
- data/lib/capybara_test_helpers/cucumber.rb +12 -0
- data/lib/capybara_test_helpers/dependency_injection.rb +44 -0
- data/lib/capybara_test_helpers/finders.rb +40 -0
- data/lib/capybara_test_helpers/matchers.rb +55 -0
- data/lib/capybara_test_helpers/rspec.rb +33 -0
- data/lib/capybara_test_helpers/selectors.rb +174 -0
- data/lib/capybara_test_helpers/synchronization.rb +41 -0
- data/lib/capybara_test_helpers/test_helper.rb +182 -0
- data/lib/capybara_test_helpers/to_or_expectation_handler.rb +27 -0
- data/lib/capybara_test_helpers/version.rb +6 -0
- data/lib/generators/test_helper/test_helper_generator.rb +26 -0
- metadata +92 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/rspec'
|
4
|
+
require 'active_support/core_ext/array/wrap'
|
5
|
+
|
6
|
+
# Internal: Avoid warnings in assert_valid_keys for passing the `test_helper` option.
|
7
|
+
Capybara::Queries::BaseQuery.prepend(Module.new {
|
8
|
+
attr_reader :test_helper
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@test_helper = options.delete(:test_helper)
|
12
|
+
end
|
13
|
+
})
|
14
|
+
|
15
|
+
# Internal: Handle locator aliases provided in the test helper to finders,
|
16
|
+
# matchers, assertions, and actions.
|
17
|
+
Capybara::Queries::SelectorQuery.prepend(Module.new {
|
18
|
+
def initialize(*args, **options, &filter_block)
|
19
|
+
# Resolve any locator aliases defined in the test helper where this query
|
20
|
+
# originated from (through a finder, assertion, or matcher).
|
21
|
+
if test_helper = options[:test_helper]
|
22
|
+
args, options, filter_block = test_helper.resolve_alias_for_selector_query(args, options, filter_block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Unwrap any test helpers that were provided to the :label selector, since
|
26
|
+
# it's making an explicit check by class.
|
27
|
+
options[:for] = options[:for].to_capybara_node if options[:for].is_a?(CapybaraTestHelpers::TestHelper)
|
28
|
+
|
29
|
+
super(*args, **options, &filter_block)
|
30
|
+
end
|
31
|
+
})
|
32
|
+
|
33
|
+
# Public: Adds aliasing for element selectors to make it easier to encapsulate
|
34
|
+
# how to find a particular kind of element in the UI.
|
35
|
+
module CapybaraTestHelpers::Selectors
|
36
|
+
SELECTOR_SEPARATOR = ','
|
37
|
+
|
38
|
+
def self.included(base)
|
39
|
+
base.extend(ClassMethods)
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
# Public: Returns the available selectors for the test helper, or an empty
|
44
|
+
# Hash if selectors are not defined.
|
45
|
+
def selectors
|
46
|
+
unless defined?(@selectors)
|
47
|
+
parent_selectors = superclass.respond_to?(:selectors) ? superclass.selectors : {}
|
48
|
+
child_selectors = (defined?(self::SELECTORS) && self::SELECTORS || {})
|
49
|
+
.tap { |new_selectors| validate_selectors(new_selectors) }
|
50
|
+
@selectors = parent_selectors.merge(child_selectors).transform_values(&:freeze).freeze
|
51
|
+
end
|
52
|
+
@selectors
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Allows to "call" selectors, as a shortcut for find.
|
56
|
+
#
|
57
|
+
# Example: table.header == table.find(:header)
|
58
|
+
def define_getters_for_selectors
|
59
|
+
selectors.each_key do |selector_name|
|
60
|
+
define_method(selector_name) { |*args, **kwargs, &block|
|
61
|
+
find(selector_name, *args, **kwargs, &block)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Validates that all the selectors defined in the class won't
|
67
|
+
# cause confusion or misbehavior.
|
68
|
+
def validate_selectors(selectors)
|
69
|
+
selectors.each_key do |name|
|
70
|
+
if Capybara::Selector.all.key?(name)
|
71
|
+
raise "A selector with the name #{ name.inspect } is already registered in Capybara," \
|
72
|
+
" consider renaming the #{ name.inspect } alias in #{ self.class.name } to avoid confusion."
|
73
|
+
end
|
74
|
+
if CapybaraTestHelpers::RESERVED_METHODS.include?(name)
|
75
|
+
raise "A method with the name #{ name.inspect } is part of the Capybara DSL," \
|
76
|
+
" consider renaming the #{ name.inspect } alias in #{ self.class.name } to avoid confusion."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: Returns the available selectors for the test helper, or an empty
|
83
|
+
# Hash if selectors are not defined.
|
84
|
+
def selectors
|
85
|
+
self.class.selectors
|
86
|
+
end
|
87
|
+
|
88
|
+
# Internal: Inspects the arguments for a SelectorQuery, and resolves a
|
89
|
+
# selector alias if provided.
|
90
|
+
#
|
91
|
+
# Returns a pair of arguments and keywords to initialize a SelectorQuery.
|
92
|
+
def resolve_alias_for_selector_query(args, kwargs, filter_block)
|
93
|
+
# Extract the explicitly provided selector, if any. Example: `find_button`.
|
94
|
+
explicit_type = args.shift if selectors.key?(args[1])
|
95
|
+
|
96
|
+
if selectors.key?(args[0])
|
97
|
+
args, kwargs = locator_for(*args, **kwargs)
|
98
|
+
|
99
|
+
# Remove the type provided by the alias, and add back the explicit one.
|
100
|
+
if explicit_type
|
101
|
+
args.shift if args[0].is_a?(Symbol)
|
102
|
+
args.unshift(explicit_type)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
[args, kwargs, wrap_filter(filter_block)]
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Internal: Checks if there's a Capybara selector defined by that name.
|
112
|
+
def registered_selector?(name)
|
113
|
+
Capybara::Selector.all.key?(name)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Internal: Returns the query locator defined under the specified alias.
|
117
|
+
#
|
118
|
+
# NOTE: In most cases, the query locator is a simple CSS selector, but it also
|
119
|
+
# supports any of the built-in Capybara selectors.
|
120
|
+
#
|
121
|
+
# Returns an Array with the Capybara locator arguments, and options if any.
|
122
|
+
def locator_for(*args, **kwargs)
|
123
|
+
if args.size == 1
|
124
|
+
args = [*Array.wrap(resolve_locator_alias(args.first))]
|
125
|
+
kwargs = args.pop.deep_merge(kwargs) if args.last.is_a?(Hash)
|
126
|
+
end
|
127
|
+
[args, kwargs]
|
128
|
+
rescue KeyError => error
|
129
|
+
raise NotImplementedError, "A selector in #{ self.class.name } is not defined, #{ error.message }"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Internal: Resolves one of the segments of a locator alias.
|
133
|
+
def resolve_locator_alias(fragment)
|
134
|
+
return fragment unless fragment.is_a?(Symbol) && (selectors.key?(fragment) || !registered_selector?(fragment))
|
135
|
+
|
136
|
+
locator = selectors.fetch(fragment)
|
137
|
+
|
138
|
+
locator.is_a?(Array) ? combine_locator_fragments(locator) : locator
|
139
|
+
end
|
140
|
+
|
141
|
+
# Internal: Resolves a complex locator alias, which might reference other
|
142
|
+
# locator aliases as well.
|
143
|
+
def combine_locator_fragments(fragments)
|
144
|
+
return fragments unless fragments.any? { |fragment| fragment.is_a?(Symbol) }
|
145
|
+
|
146
|
+
fragments = fragments.map { |fragment| resolve_locator_alias(fragment) }
|
147
|
+
flat_fragments = fragments.flatten(1)
|
148
|
+
type = flat_fragments.shift if flat_fragments.first.is_a?(Symbol)
|
149
|
+
|
150
|
+
# Only flatten fragments if it's CSS or XPath
|
151
|
+
if type.nil? || type == :css || type == :xpath
|
152
|
+
fragments = flat_fragments
|
153
|
+
else
|
154
|
+
type = nil
|
155
|
+
end
|
156
|
+
|
157
|
+
options = fragments.pop if fragments.last.is_a?(Hash)
|
158
|
+
|
159
|
+
[type, *combine_css_selectors(fragments), options].compact
|
160
|
+
end
|
161
|
+
|
162
|
+
# Internal: Combines parent and child classes to preserve the order.
|
163
|
+
def combine_css_selectors(selectors)
|
164
|
+
return selectors unless selectors.size > 1 && selectors.all? { |selector| selector.is_a?(String) }
|
165
|
+
|
166
|
+
selectors.reduce { |parent_selectors, children_selectors|
|
167
|
+
parent_selectors.split(SELECTOR_SEPARATOR).flat_map { |parent_selector|
|
168
|
+
children_selectors.split(SELECTOR_SEPARATOR).map { |children_selector|
|
169
|
+
"#{ parent_selector }#{ children_selector }"
|
170
|
+
}
|
171
|
+
}.join(SELECTOR_SEPARATOR)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Internal: Provides helper functions to perform asynchronous assertions.
|
4
|
+
module CapybaraTestHelpers::Synchronization
|
5
|
+
# Internal: Necessary because the RSpec exception is not a StandardError and
|
6
|
+
# thus capybara does not rescue it, so it wouldn't attempt to retry it.
|
7
|
+
class ExpectationError < StandardError; end
|
8
|
+
|
9
|
+
# Internal: Errors that will be retried in a `synchronize_expectation` block.
|
10
|
+
EXPECTATION_ERRORS = [
|
11
|
+
ExpectationError,
|
12
|
+
Capybara::ElementNotFound,
|
13
|
+
(Selenium::WebDriver::Error::StaleElementReferenceError if defined?(Selenium::WebDriver::Error::StaleElementReferenceError)),
|
14
|
+
].compact.freeze
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# Public: Can be used to make an asynchronous expectation, that will be
|
19
|
+
# retried until the max wait time configured in Capybara.
|
20
|
+
def synchronize_expectation(retry_on_errors: [], **options)
|
21
|
+
synchronize(errors: EXPECTATION_ERRORS + retry_on_errors, **options) {
|
22
|
+
begin
|
23
|
+
yield
|
24
|
+
rescue RSpec::Expectations::ExpectationNotMetError => error
|
25
|
+
# NOTE: Rethrow as ExpectationError because the RSpec exception is not
|
26
|
+
# a StandardError so capybara wouldn't rescue it inside synchronize.
|
27
|
+
raise ExpectationError, error
|
28
|
+
end
|
29
|
+
}
|
30
|
+
rescue ExpectationError => error
|
31
|
+
raise error.cause # Unwrap this internal error and raise the original error.
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Used to implement more specific synchronization helpers.
|
35
|
+
#
|
36
|
+
# By default Capybara's methods like `find` and `have_css` already use
|
37
|
+
# synchronize to achieve asynchronicity, so it's not necessary to use this.
|
38
|
+
def synchronize(wait: Capybara.default_max_wait_time == 0 ? Capybara.default_max_wait_time : 3, **options, &block)
|
39
|
+
(current_element? ? current_context : page.document).synchronize(wait, **options, &block)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/rspec'
|
4
|
+
|
5
|
+
require 'active_support/core_ext/module/delegation'
|
6
|
+
require 'active_support/core_ext/string/inflections'
|
7
|
+
require 'active_support/core_ext/object/blank'
|
8
|
+
|
9
|
+
require 'capybara_test_helpers/selectors'
|
10
|
+
require 'capybara_test_helpers/synchronization'
|
11
|
+
require 'capybara_test_helpers/finders'
|
12
|
+
require 'capybara_test_helpers/actions'
|
13
|
+
require 'capybara_test_helpers/assertions'
|
14
|
+
require 'capybara_test_helpers/matchers'
|
15
|
+
|
16
|
+
# Public: Base class to create test helpers that have full access to the
|
17
|
+
# Capybara DSL, while easily defining custom assertions, getters, and actions.
|
18
|
+
#
|
19
|
+
# It also supports locator aliases to prevent duplication and keep tests easier
|
20
|
+
# to understand and to maintain.
|
21
|
+
class CapybaraTestHelpers::TestHelper
|
22
|
+
include RSpec::Matchers
|
23
|
+
include RSpec::Mocks::ExampleMethods
|
24
|
+
include Capybara::DSL
|
25
|
+
include CapybaraTestHelpers::Selectors
|
26
|
+
include CapybaraTestHelpers::Synchronization
|
27
|
+
include CapybaraTestHelpers::Finders
|
28
|
+
include CapybaraTestHelpers::Actions
|
29
|
+
include CapybaraTestHelpers::Assertions
|
30
|
+
include CapybaraTestHelpers::Matchers
|
31
|
+
|
32
|
+
undef_method(*CapybaraTestHelpers::SKIPPED_DSL_METHODS)
|
33
|
+
|
34
|
+
attr_reader :query_context, :test_context
|
35
|
+
|
36
|
+
def initialize(query_context, test_context: query_context, negated: nil)
|
37
|
+
@query_context, @test_context, @negated = query_context, test_context, negated
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: To make the benchmark log less verbose.
|
41
|
+
def inspect
|
42
|
+
%(#<#{ self.class.name } #{ current_element? ? %(tag="#{ base.tag_name }") : object_id }>)
|
43
|
+
rescue *page.driver.invalid_element_errors
|
44
|
+
%(#<#{ self.class.name } #{ object_id }>)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Makes it easier to inspect the current element.
|
48
|
+
def inspect_node
|
49
|
+
to_capybara_node.inspect
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Casts the current context as a Capybara::Node::Element.
|
53
|
+
#
|
54
|
+
# NOTE: Uses the :el convention, which means actions can be performed directly
|
55
|
+
# on the test helper if an :el selector is defined.
|
56
|
+
def to_capybara_node
|
57
|
+
return current_context if current_element?
|
58
|
+
return find_element(:el) if selectors.key?(:el)
|
59
|
+
|
60
|
+
raise_missing_element_error
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Wraps a Capybara::Node::Element with a test helper object.
|
64
|
+
def wrap_element(capybara_node)
|
65
|
+
if capybara_node.is_a?(Enumerable)
|
66
|
+
capybara_node.map { |node| wrap_element(node) }
|
67
|
+
else
|
68
|
+
self.class.new(capybara_node, test_context: test_context)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Public: Wraps a CapybaraTestHelper with a different test helper object.
|
73
|
+
def wrap_test_helper(test_helper)
|
74
|
+
self.class.new(test_helper.query_context, test_context: test_context) if test_helper.query_context
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: Scopes the Capybara queries in the block to be inside the specified
|
78
|
+
# selector.
|
79
|
+
def within_element(*args, **kwargs, &block)
|
80
|
+
locator = args.empty? ? [self] : args
|
81
|
+
kwargs[:test_helper] = self
|
82
|
+
page.within(*locator, **kwargs, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Public: Scopes the Capybara queries in the block to be inside the specified
|
86
|
+
# selector.
|
87
|
+
def within(*args, **kwargs, &block)
|
88
|
+
return be_within(*args, **kwargs) unless block_given? # RSpec matcher.
|
89
|
+
|
90
|
+
within_element(*args, **kwargs, &block)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Public: Unscopes the inner block from any previous `within` calls.
|
94
|
+
def within_document
|
95
|
+
page.instance_exec { scopes << nil }
|
96
|
+
yield wrap_element(page.document)
|
97
|
+
ensure
|
98
|
+
page.instance_exec { scopes.pop }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Internal: Returns the name of the class without the suffix.
|
102
|
+
#
|
103
|
+
# Example: 'current_page' for CurrentPageTestHelper.
|
104
|
+
def friendly_name
|
105
|
+
self.class.name.chomp('TestHelper').underscore
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
# Internal: Used to perform assertions and others.
|
111
|
+
def current_context
|
112
|
+
query_context.respond_to?(:to_capybara_node) ? query_context : page
|
113
|
+
end
|
114
|
+
|
115
|
+
# Internal: Returns true if the current context is an element.
|
116
|
+
def current_element?
|
117
|
+
current_context.is_a?(Capybara::Node::Element)
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# Internal: Wraps the optional filter block to ensure we pass it a test helper
|
123
|
+
# instead of a raw Capybara::Node::Element.
|
124
|
+
def wrap_filter(filter)
|
125
|
+
proc { |capybara_node_element| filter.call(wrap_element(capybara_node_element)) } if filter
|
126
|
+
end
|
127
|
+
|
128
|
+
# Internal: Helper to provide more information on the error.
|
129
|
+
def raise_missing_element_error
|
130
|
+
method_caller = caller.select { |x| x['test_helpers'] }[1]
|
131
|
+
method_caller_name = method_caller&.match(/in `(\w+)'/)
|
132
|
+
method_caller_name = method_caller_name ? method_caller_name[1] : method_caller
|
133
|
+
raise ArgumentError, "You are calling the `#{ method_caller_name }' method on the test helper but :el is not defined nor there's a current element.\n"\
|
134
|
+
'Define :el, or find an element before performing the action.'
|
135
|
+
end
|
136
|
+
|
137
|
+
class << self
|
138
|
+
# Public: Make methods in the test context available in the helpers.
|
139
|
+
def delegate_to_test_context(*method_names)
|
140
|
+
delegate(*method_names, to: :test_context)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Public: Allows to define dependencies on other matchers.
|
144
|
+
#
|
145
|
+
# NOTE: When you call a helper the "negated" state is preserved for assertions.
|
146
|
+
#
|
147
|
+
# NOTE: You can also pass an element to a test helper to "wrap" a specified
|
148
|
+
# element with the specified test helper class.
|
149
|
+
#
|
150
|
+
# Example:
|
151
|
+
# dropdown(element).toggle_menu
|
152
|
+
def use_test_helpers(*helper_names)
|
153
|
+
helper_names.each do |helper_name|
|
154
|
+
private define_method(helper_name) { |element = nil|
|
155
|
+
test_helper = test_context.get_test_helper(helper_name)
|
156
|
+
if element
|
157
|
+
raise ArgumentError, "#{ element.inspect } must be a test helper or element." unless element.respond_to?(:to_capybara_node)
|
158
|
+
|
159
|
+
test_helper = test_helper.wrap_element(element.to_capybara_node)
|
160
|
+
end
|
161
|
+
@negated.nil? ? test_helper : test_helper.should(@negated)
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Internal: Allows to perform certain actions just before a test helper will
|
167
|
+
# be loaded for the first time.
|
168
|
+
def on_test_helper_load
|
169
|
+
define_getters_for_selectors
|
170
|
+
end
|
171
|
+
|
172
|
+
# Internal: Fail early if a reserved method is redefined.
|
173
|
+
def method_added(method_name)
|
174
|
+
return unless CapybaraTestHelpers::RESERVED_METHODS.include?(method_name)
|
175
|
+
|
176
|
+
raise "A method with the name #{ method_name.inspect } is part of the Capybara DSL," \
|
177
|
+
' overriding it could cause unexpected issues that could be very hard to debug.'
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
Capybara::TestHelper = CapybaraTestHelpers::TestHelper unless defined?(Capybara::TestHelper)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/rspec'
|
4
|
+
|
5
|
+
# Internal: Used heavily in the RSpec matchers, makes it very easy to create
|
6
|
+
# a dual assertion (can be used as positive or negative).
|
7
|
+
#
|
8
|
+
# See https://maximomussini.com/posts/cucumber-to_or_not_to/
|
9
|
+
module CapybaraTestHelpers::ToOrExpectationHandler
|
10
|
+
# Public: Allows a more convenient definition of should/should not Gherkin steps.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# Then(/^I should (not )?see "(.*)"$/) do |not_to, text|
|
15
|
+
# expect(page).to_or not_to, have_content(text)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
def to_or(not_to, matcher, message = nil, &block)
|
19
|
+
if not_to
|
20
|
+
not_to(matcher, message, &block)
|
21
|
+
else
|
22
|
+
to(matcher, message, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec::Expectations::ExpectationTarget.include(CapybaraTestHelpers::ToOrExpectationHandler)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'capybara_test_helpers'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
# Internal: Generates a new test helper file in the appropriate directory.
|
5
|
+
class TestHelperGenerator < Rails::Generators::NamedBase
|
6
|
+
def create_helper_file
|
7
|
+
create_file("#{ CapybaraTestHelpers.config.helpers_paths.first }/#{ file_name }_test_helper.rb") {
|
8
|
+
<<~CAPYBARA_TEST_HELPER
|
9
|
+
# frozen_string_literal: true
|
10
|
+
|
11
|
+
class #{ file_name.camelize }TestHelper < #{ file_name.to_s == 'base' ? 'Capybara::TestHelper' : 'BaseTestHelper' }
|
12
|
+
# Selectors: Semantic aliases for elements, a useful abstraction.
|
13
|
+
SELECTORS = {}
|
14
|
+
|
15
|
+
# Getters: A convenient way to get related data or nested elements.
|
16
|
+
|
17
|
+
# Actions: Encapsulate complex actions to provide a cleaner interface.
|
18
|
+
|
19
|
+
# Assertions: Check on element properties, used with `should` and `should_not`.
|
20
|
+
|
21
|
+
# Background: Helpers to add/modify/delete data in the database or session.
|
22
|
+
end
|
23
|
+
CAPYBARA_TEST_HELPER
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|