capybara 2.5.0 → 2.18.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.
Files changed (205) hide show
  1. checksums.yaml +5 -5
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +413 -10
  7. data/License.txt +1 -1
  8. data/README.md +237 -130
  9. data/lib/capybara/config.rb +132 -0
  10. data/lib/capybara/cucumber.rb +3 -1
  11. data/lib/capybara/driver/base.rb +27 -6
  12. data/lib/capybara/driver/node.rb +14 -5
  13. data/lib/capybara/dsl.rb +2 -3
  14. data/lib/capybara/helpers.rb +13 -65
  15. data/lib/capybara/minitest/spec.rb +177 -0
  16. data/lib/capybara/minitest.rb +278 -0
  17. data/lib/capybara/node/actions.rb +180 -24
  18. data/lib/capybara/node/base.rb +17 -5
  19. data/lib/capybara/node/document.rb +5 -0
  20. data/lib/capybara/node/document_matchers.rb +15 -14
  21. data/lib/capybara/node/element.rb +55 -7
  22. data/lib/capybara/node/finders.rb +179 -67
  23. data/lib/capybara/node/matchers.rb +301 -105
  24. data/lib/capybara/node/simple.rb +15 -4
  25. data/lib/capybara/queries/ancestor_query.rb +25 -0
  26. data/lib/capybara/queries/base_query.rb +69 -3
  27. data/lib/capybara/queries/current_path_query.rb +17 -8
  28. data/lib/capybara/queries/match_query.rb +19 -0
  29. data/lib/capybara/queries/selector_query.rb +251 -0
  30. data/lib/capybara/queries/sibling_query.rb +25 -0
  31. data/lib/capybara/queries/text_query.rb +67 -16
  32. data/lib/capybara/queries/title_query.rb +4 -2
  33. data/lib/capybara/query.rb +3 -131
  34. data/lib/capybara/rack_test/browser.rb +14 -5
  35. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  36. data/lib/capybara/rack_test/driver.rb +15 -8
  37. data/lib/capybara/rack_test/form.rb +34 -12
  38. data/lib/capybara/rack_test/node.rb +29 -12
  39. data/lib/capybara/rails.rb +3 -3
  40. data/lib/capybara/result.rb +104 -9
  41. data/lib/capybara/rspec/compound.rb +95 -0
  42. data/lib/capybara/rspec/features.rb +17 -6
  43. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  44. data/lib/capybara/rspec/matchers.rb +199 -80
  45. data/lib/capybara/rspec.rb +4 -2
  46. data/lib/capybara/selector/css.rb +30 -0
  47. data/lib/capybara/selector/filter.rb +20 -0
  48. data/lib/capybara/selector/filter_set.rb +74 -0
  49. data/lib/capybara/selector/filters/base.rb +33 -0
  50. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  51. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  52. data/lib/capybara/selector/selector.rb +276 -0
  53. data/lib/capybara/selector.rb +452 -157
  54. data/lib/capybara/selenium/driver.rb +282 -81
  55. data/lib/capybara/selenium/node.rb +144 -46
  56. data/lib/capybara/server.rb +59 -16
  57. data/lib/capybara/session/config.rb +114 -0
  58. data/lib/capybara/session/matchers.rb +29 -19
  59. data/lib/capybara/session.rb +378 -143
  60. data/lib/capybara/spec/fixtures/no_extension +1 -0
  61. data/lib/capybara/spec/public/jquery-ui.js +13 -791
  62. data/lib/capybara/spec/public/jquery.js +4 -9045
  63. data/lib/capybara/spec/public/test.js +45 -11
  64. data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
  65. data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
  66. data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
  67. data/lib/capybara/spec/session/all_spec.rb +45 -32
  68. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  69. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
  70. data/lib/capybara/spec/session/assert_current_path.rb +15 -2
  71. data/lib/capybara/spec/session/assert_selector.rb +29 -28
  72. data/lib/capybara/spec/session/assert_text.rb +59 -20
  73. data/lib/capybara/spec/session/assert_title.rb +25 -11
  74. data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
  75. data/lib/capybara/spec/session/body_spec.rb +1 -0
  76. data/lib/capybara/spec/session/check_spec.rb +90 -14
  77. data/lib/capybara/spec/session/choose_spec.rb +31 -5
  78. data/lib/capybara/spec/session/click_button_spec.rb +20 -9
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
  80. data/lib/capybara/spec/session/click_link_spec.rb +39 -15
  81. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  82. data/lib/capybara/spec/session/current_url_spec.rb +12 -3
  83. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
  84. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
  85. data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
  86. data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
  87. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  88. data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
  89. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  90. data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
  91. data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
  92. data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
  93. data/lib/capybara/spec/session/find_button_spec.rb +43 -2
  94. data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
  95. data/lib/capybara/spec/session/find_field_spec.rb +42 -6
  96. data/lib/capybara/spec/session/find_link_spec.rb +22 -3
  97. data/lib/capybara/spec/session/find_spec.rb +103 -57
  98. data/lib/capybara/spec/session/first_spec.rb +34 -18
  99. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  100. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
  101. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  102. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  103. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  104. data/lib/capybara/spec/session/has_button_spec.rb +17 -8
  105. data/lib/capybara/spec/session/has_css_spec.rb +85 -73
  106. data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
  107. data/lib/capybara/spec/session/has_field_spec.rb +93 -58
  108. data/lib/capybara/spec/session/has_link_spec.rb +9 -8
  109. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  110. data/lib/capybara/spec/session/has_select_spec.rb +159 -59
  111. data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
  112. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  113. data/lib/capybara/spec/session/has_text_spec.rb +27 -12
  114. data/lib/capybara/spec/session/has_title_spec.rb +22 -4
  115. data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
  116. data/lib/capybara/spec/session/headers.rb +2 -1
  117. data/lib/capybara/spec/session/html_spec.rb +4 -3
  118. data/lib/capybara/spec/session/node_spec.rb +198 -38
  119. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  120. data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
  121. data/lib/capybara/spec/session/response_code.rb +2 -1
  122. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  123. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
  124. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  125. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  126. data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
  127. data/lib/capybara/spec/session/select_spec.rb +34 -32
  128. data/lib/capybara/spec/session/selectors_spec.rb +65 -0
  129. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  130. data/lib/capybara/spec/session/text_spec.rb +4 -4
  131. data/lib/capybara/spec/session/title_spec.rb +2 -1
  132. data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
  133. data/lib/capybara/spec/session/unselect_spec.rb +17 -16
  134. data/lib/capybara/spec/session/visit_spec.rb +77 -2
  135. data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
  136. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  137. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  138. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
  139. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
  140. data/lib/capybara/spec/session/window/window_spec.rb +36 -29
  141. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  142. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
  143. data/lib/capybara/spec/session/within_spec.rb +14 -6
  144. data/lib/capybara/spec/spec_helper.rb +37 -4
  145. data/lib/capybara/spec/test_app.rb +15 -3
  146. data/lib/capybara/spec/views/buttons.erb +1 -0
  147. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  148. data/lib/capybara/spec/views/form.erb +169 -9
  149. data/lib/capybara/spec/views/frame_child.erb +10 -2
  150. data/lib/capybara/spec/views/frame_one.erb +2 -1
  151. data/lib/capybara/spec/views/frame_parent.erb +3 -2
  152. data/lib/capybara/spec/views/frame_two.erb +2 -1
  153. data/lib/capybara/spec/views/header_links.erb +1 -0
  154. data/lib/capybara/spec/views/host_links.erb +1 -0
  155. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  156. data/lib/capybara/spec/views/path.erb +1 -0
  157. data/lib/capybara/spec/views/popup_one.erb +1 -0
  158. data/lib/capybara/spec/views/popup_two.erb +1 -0
  159. data/lib/capybara/spec/views/postback.erb +2 -1
  160. data/lib/capybara/spec/views/tables.erb +1 -0
  161. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  162. data/lib/capybara/spec/views/with_count.erb +2 -1
  163. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  164. data/lib/capybara/spec/views/with_hover.erb +7 -1
  165. data/lib/capybara/spec/views/with_html.erb +40 -2
  166. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  167. data/lib/capybara/spec/views/with_js.erb +32 -1
  168. data/lib/capybara/spec/views/with_scope.erb +1 -0
  169. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  170. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  171. data/lib/capybara/spec/views/with_title.erb +2 -1
  172. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  173. data/lib/capybara/spec/views/with_windows.erb +7 -0
  174. data/lib/capybara/spec/views/within_frames.erb +3 -2
  175. data/lib/capybara/version.rb +2 -1
  176. data/lib/capybara/window.rb +20 -3
  177. data/lib/capybara.rb +189 -93
  178. data/spec/basic_node_spec.rb +7 -6
  179. data/spec/capybara_spec.rb +90 -4
  180. data/spec/dsl_spec.rb +3 -1
  181. data/spec/filter_set_spec.rb +28 -0
  182. data/spec/fixtures/capybara.csv +1 -0
  183. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
  184. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
  185. data/spec/minitest_spec.rb +130 -0
  186. data/spec/minitest_spec_spec.rb +135 -0
  187. data/spec/per_session_config_spec.rb +67 -0
  188. data/spec/rack_test_spec.rb +50 -7
  189. data/spec/result_spec.rb +76 -0
  190. data/spec/rspec/features_spec.rb +21 -8
  191. data/spec/rspec/scenarios_spec.rb +21 -0
  192. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
  193. data/spec/rspec/views_spec.rb +5 -0
  194. data/spec/rspec_matchers_spec.rb +46 -0
  195. data/spec/rspec_spec.rb +79 -1
  196. data/spec/selector_spec.rb +199 -0
  197. data/spec/selenium_spec_chrome.rb +54 -9
  198. data/spec/selenium_spec_firefox.rb +68 -0
  199. data/spec/selenium_spec_marionette.rb +127 -0
  200. data/spec/server_spec.rb +102 -14
  201. data/spec/session_spec.rb +54 -0
  202. data/spec/shared_selenium_session.rb +215 -0
  203. data/spec/spec_helper.rb +7 -0
  204. metadata +140 -15
  205. data/spec/selenium_spec.rb +0 -128
@@ -1,247 +1,542 @@
1
- module Capybara
2
- class Selector
3
- class Filter
4
- def initialize(name, block, options={})
5
- @name = name
6
- @block = block
7
- @options = options
8
- @options[:valid_values] = [true,false] if options[:boolean]
9
- end
10
-
11
- def default?
12
- @options.has_key?(:default)
13
- end
14
-
15
- def default
16
- @options[:default]
17
- end
18
-
19
- def matches?(node, value)
20
- if @options.has_key?(:valid_values) && !Array(@options[:valid_values]).include?(value)
21
- warn "Invalid value #{value.inspect} passed to filter #{@name}"
22
- end
23
- @block.call(node, value)
24
- end
25
- end
26
-
27
- attr_reader :name, :custom_filters, :format
28
-
29
- class << self
30
- def all
31
- @selectors ||= {}
32
- end
33
-
34
- def add(name, &block)
35
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
36
- end
37
-
38
- def remove(name)
39
- all.delete(name.to_sym)
40
- end
41
- end
1
+ # frozen_string_literal: true
2
+ require 'capybara/selector/selector'
3
+ Capybara::Selector::FilterSet.add(:_field) do
4
+ filter(:checked, :boolean) { |node, value| not(value ^ node.checked?) }
5
+ filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
6
+ filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
7
+ filter(:multiple, :boolean) { |node, value| !(value ^ node.multiple?) }
42
8
 
43
- def initialize(name, &block)
44
- @name = name
45
- @custom_filters = {}
46
- @match = nil
47
- @label = nil
48
- @failure_message = nil
49
- @description = nil
50
- instance_eval(&block)
51
- end
9
+ expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name).equals(val)] }
10
+ expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder).equals(val)] }
52
11
 
53
- def xpath(&block)
54
- @format = :xpath
55
- @xpath = block if block
56
- @xpath
57
- end
58
-
59
- # Same as xpath, but wrap in XPath.css().
60
- def css(&block)
61
- @format = :css
62
- @css = block if block
63
- @css
64
- end
65
-
66
- def match(&block)
67
- @match = block if block
68
- @match
69
- end
70
-
71
- def label(label=nil)
72
- @label = label if label
73
- @label
74
- end
75
-
76
- def description(options={})
77
- (@description && @description.call(options)).to_s
78
- end
79
-
80
- def call(locator)
81
- if @format==:css
82
- @css.call(locator)
83
- else
84
- @xpath.call(locator)
85
- end
86
- end
87
-
88
- def match?(locator)
89
- @match and @match.call(locator)
90
- end
91
-
92
- def filter(name, options={}, &block)
93
- @custom_filters[name] = Filter.new(name, block, options)
94
- end
95
-
96
- def describe &block
97
- @description = block
98
- end
12
+ describe do |options|
13
+ desc, states = String.new, []
14
+ states << 'checked' if options[:checked] || (options[:unchecked] == false)
15
+ states << 'not checked' if options[:unchecked] || (options[:checked] == false)
16
+ states << 'disabled' if options[:disabled] == true
17
+ states << 'not disabled' if options[:disabled] == false
18
+ desc << " that is #{states.join(' and ')}" unless states.empty?
19
+ desc << " with the multiple attribute" if options[:multiple] == true
20
+ desc << " without the multiple attribute" if options[:multiple] == false
21
+ desc
99
22
  end
100
23
  end
101
24
 
25
+ ##
26
+ #
27
+ # Select elements by XPath expression
28
+ #
29
+ # @locator An XPath expression
30
+ #
102
31
  Capybara.add_selector(:xpath) do
103
32
  xpath { |xpath| xpath }
104
33
  end
105
34
 
35
+ ##
36
+ #
37
+ # Select elements by CSS selector
38
+ #
39
+ # @locator A CSS selector
40
+ #
106
41
  Capybara.add_selector(:css) do
107
42
  css { |css| css }
108
43
  end
109
44
 
45
+ ##
46
+ #
47
+ # Select element by id
48
+ #
49
+ # @locator The id of the element to match
50
+ #
110
51
  Capybara.add_selector(:id) do
111
52
  xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
112
53
  end
113
54
 
55
+ ##
56
+ #
57
+ # Select field elements (input [not of type submit, image, or hidden], textarea, select)
58
+ #
59
+ # @locator Matches against the id, name, or placeholder
60
+ # @filter [String] :id Matches the id attribute
61
+ # @filter [String] :name Matches the name attribute
62
+ # @filter [String] :placeholder Matches the placeholder attribute
63
+ # @filter [String] :type Matches the type attribute of the field or element type for 'textarea' and 'select'
64
+ # @filter [Boolean] :readonly
65
+ # @filter [String] :with Matches the current value of the field
66
+ # @filter [String, Array<String>] :class Matches the class(es) provided
67
+ # @filter [Boolean] :checked Match checked fields?
68
+ # @filter [Boolean] :unchecked Match unchecked fields?
69
+ # @filter [Boolean] :disabled Match disabled field?
70
+ # @filter [Boolean] :multiple Match fields that accept multiple values
114
71
  Capybara.add_selector(:field) do
115
- xpath { |locator| XPath::HTML.field(locator) }
116
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
117
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
118
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
119
- filter(:readonly, boolean: true) { |node, value| not(value ^ node[:readonly]) }
120
- filter(:with) { |node, with| node.value == with.to_s }
121
- filter(:type) do |node, type|
72
+ xpath do |locator, options|
73
+ xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
74
+ locate_field(xpath, locator, options)
75
+ end
76
+
77
+ expression_filter(:type) do |expr, type|
78
+ type = type.to_s
122
79
  if ['textarea', 'select'].include?(type)
123
- node.tag_name == type
80
+ expr.axis(:self, type.to_sym)
124
81
  else
125
- node[:type] == type
82
+ expr[XPath.attr(:type).equals(type)]
126
83
  end
127
84
  end
85
+
86
+ filter_set(:_field) # checked/unchecked/disabled/multiple/name/placeholder
87
+
88
+ filter(:readonly, :boolean) { |node, value| not(value ^ node.readonly?) }
89
+ filter(:with) do |node, with|
90
+ with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
91
+ end
128
92
  describe do |options|
129
- desc, states = "", []
93
+ desc = String.new
94
+ (expression_filters.keys - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.has_key?(ef) }
130
95
  desc << " of type #{options[:type].inspect}" if options[:type]
131
96
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
132
- states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
133
- states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
134
- states << 'disabled' if options[:disabled]
135
- desc << " that is #{states.join(' and ')}" unless states.empty?
136
97
  desc
137
98
  end
138
99
  end
139
100
 
101
+ ##
102
+ #
103
+ # Select fieldset elements
104
+ #
105
+ # @locator Matches id or contents of wrapped legend
106
+ #
107
+ # @filter [String] :id Matches id attribute
108
+ # @filter [String] :legend Matches contents of wrapped legend
109
+ # @filter [String, Array<String>] :class Matches the class(es) provided
110
+ #
140
111
  Capybara.add_selector(:fieldset) do
141
- xpath { |locator| XPath::HTML.fieldset(locator) }
142
- end
143
-
144
- Capybara.add_selector(:link_or_button) do
145
- label "link or button"
146
- xpath { |locator| XPath::HTML.link_or_button(locator) }
147
- filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
148
- describe { |options| " that is disabled" if options[:disabled] }
112
+ xpath(:legend) do |locator, options|
113
+ xpath = XPath.descendant(:fieldset)
114
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
115
+ xpath = xpath[XPath.child(:legend)[XPath.string.n.is(options[:legend])]] if options[:legend]
116
+ xpath
117
+ end
149
118
  end
150
119
 
120
+ ##
121
+ #
122
+ # Find links ( <a> elements with an href attribute )
123
+ #
124
+ # @locator Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
125
+ #
126
+ # @filter [String] :id Matches the id attribute
127
+ # @filter [String] :title Matches the title attribute
128
+ # @filter [String] :alt Matches the alt attribute of a contained img element
129
+ # @filter [String] :class Matches the class(es) provided
130
+ # @filter [String, Regexp,nil] :href Matches the normalized href of the link, if nil will find <a> elements with no href attribute
131
+ #
151
132
  Capybara.add_selector(:link) do
152
- xpath { |locator| XPath::HTML.link(locator) }
133
+ xpath(:title, :alt) do |locator, options={}|
134
+ xpath = XPath.descendant(:a)
135
+ xpath = if options.fetch(:href, true).nil?
136
+ xpath[~XPath.attr(:href)]
137
+ else
138
+ xpath[XPath.attr(:href)]
139
+ end
140
+ unless locator.nil?
141
+ locator = locator.to_s
142
+ matchers = XPath.attr(:id).equals(locator).or(
143
+ XPath.string.n.is(locator)).or(
144
+ XPath.attr(:title).is(locator)).or(
145
+ XPath.descendant(:img)[XPath.attr(:alt).is(locator)])
146
+ matchers = matchers.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
147
+ xpath = xpath[matchers]
148
+ end
149
+ xpath = [:title].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
150
+ xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt).equals(options[:alt])]] if options[:alt]
151
+ xpath
152
+ end
153
+
153
154
  filter(:href) do |node, href|
154
- if href.is_a? Regexp
155
+ case href
156
+ when nil
157
+ true
158
+ when Regexp
155
159
  node[:href].match href
156
160
  else
157
161
  node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)], minimum: 0)
158
162
  end
159
163
  end
160
- describe { |options| " with href #{options[:href].inspect}" if options[:href] }
164
+
165
+ describe do |options|
166
+ desc = String.new()
167
+ desc << " with href #{options[:href].inspect}" if options[:href]
168
+ desc << " with no href attribute" if options.fetch(:href, true).nil?
169
+ end
161
170
  end
162
171
 
172
+ ##
173
+ #
174
+ # Find buttons ( input [of type submit, reset, image, button] or button elements )
175
+ #
176
+ # @locator Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
177
+ #
178
+ # @filter [String] :id Matches the id attribute
179
+ # @filter [String] :title Matches the title attribute
180
+ # @filter [String] :class Matches the class(es) provided
181
+ # @filter [String] :value Matches the value of an input button
182
+ #
163
183
  Capybara.add_selector(:button) do
164
- xpath { |locator| XPath::HTML.button(locator) }
165
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
184
+ xpath(:value, :title) do |locator, options={}|
185
+ input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
186
+ btn_xpath = XPath.descendant(:button)
187
+ image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).equals('image')]
188
+
189
+ unless locator.nil?
190
+ locator = locator.to_s
191
+ locator_matches = XPath.attr(:id).equals(locator).or XPath.attr(:value).is(locator).or XPath.attr(:title).is(locator)
192
+ locator_matches = locator_matches.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
193
+
194
+ input_btn_xpath = input_btn_xpath[locator_matches]
195
+
196
+ btn_xpath = btn_xpath[locator_matches.or XPath.string.n.is(locator).or XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
197
+
198
+ alt_matches = XPath.attr(:alt).is(locator)
199
+ alt_matches = alt_matches.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
200
+ image_btn_xpath = image_btn_xpath[alt_matches]
201
+ end
202
+
203
+ res_xpath = input_btn_xpath.union(btn_xpath).union(image_btn_xpath)
204
+
205
+ res_xpath = expression_filters.keys.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
206
+
207
+ res_xpath
208
+ end
209
+
210
+ filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
211
+
212
+ describe do |options|
213
+ desc = String.new
214
+ desc << " that is disabled" if options[:disabled] == true
215
+ desc << describe_all_expression_filters(options)
216
+ desc
217
+ end
218
+ end
219
+
220
+ ##
221
+ #
222
+ # Find links or buttons
223
+ #
224
+ Capybara.add_selector(:link_or_button) do
225
+ label "link or button"
226
+ xpath do |locator, options|
227
+ self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:union)
228
+ end
229
+
230
+ filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
231
+
166
232
  describe { |options| " that is disabled" if options[:disabled] }
167
233
  end
168
234
 
235
+ ##
236
+ #
237
+ # Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
238
+ #
239
+ # @locator Matches against the id, name, or placeholder
240
+ # @filter [String] :id Matches the id attribute
241
+ # @filter [String] :name Matches the name attribute
242
+ # @filter [String] :placeholder Matches the placeholder attribute
243
+ # @filter [String] :with Matches the current value of the field
244
+ # @filter [String] :type Matches the type attribute of the field or element type for 'textarea'
245
+ # @filter [String, Array<String>] :class Matches the class(es) provided
246
+ # @filter [Boolean] :disabled Match disabled field?
247
+ # @filter [Boolean] :multiple Match fields that accept multiple values
248
+ #
169
249
  Capybara.add_selector(:fillable_field) do
170
250
  label "field"
171
- xpath { |locator| XPath::HTML.fillable_field(locator) }
172
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
173
- describe { |options| " that is disabled" if options[:disabled] }
251
+ xpath do |locator, options|
252
+ xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
253
+ locate_field(xpath, locator, options)
254
+ end
255
+
256
+ expression_filter(:type) do |expr, type|
257
+ type = type.to_s
258
+ if ['textarea'].include?(type)
259
+ expr.axis(:self, type.to_sym)
260
+ else
261
+ expr[XPath.attr(:type).equals(type)]
262
+ end
263
+ end
264
+
265
+ filter_set(:_field, [:disabled, :multiple, :name, :placeholder])
266
+
267
+ filter(:with) do |node, with|
268
+ with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
269
+ end
270
+
271
+ describe do |options|
272
+ desc = String.new
273
+ desc << describe_all_expression_filters(options)
274
+ desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
275
+ desc
276
+ end
174
277
  end
175
278
 
279
+ ##
280
+ #
281
+ # Find radio buttons
282
+ #
283
+ # @locator Match id, name, or associated label text
284
+ # @filter [String] :id Matches the id attribute
285
+ # @filter [String] :name Matches the name attribute
286
+ # @filter [String, Array<String>] :class Matches the class(es) provided
287
+ # @filter [Boolean] :checked Match checked fields?
288
+ # @filter [Boolean] :unchecked Match unchecked fields?
289
+ # @filter [Boolean] :disabled Match disabled field?
290
+ # @filter [String] :option Match the value
291
+ #
176
292
  Capybara.add_selector(:radio_button) do
177
293
  label "radio button"
178
- xpath { |locator| XPath::HTML.radio_button(locator) }
179
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
180
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
294
+ xpath do |locator, options|
295
+ xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
296
+ locate_field(xpath, locator, options)
297
+ end
298
+
299
+ filter_set(:_field, [:checked, :unchecked, :disabled, :name])
300
+
181
301
  filter(:option) { |node, value| node.value == value.to_s }
182
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
302
+
183
303
  describe do |options|
184
- desc, states = "", []
304
+ desc = String.new
185
305
  desc << " with value #{options[:option].inspect}" if options[:option]
186
- states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
187
- states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
188
- states << 'disabled' if options[:disabled]
189
- desc << " that is #{states.join(' and ')}" unless states.empty?
306
+ desc << describe_all_expression_filters(options)
190
307
  desc
191
308
  end
192
309
  end
193
310
 
311
+ ##
312
+ #
313
+ # Find checkboxes
314
+ #
315
+ # @locator Match id, name, or associated label text
316
+ # @filter [String] :id Matches the id attribute
317
+ # @filter [String] :name Matches the name attribute
318
+ # @filter [String, Array<String>] :class Matches the class(es) provided
319
+ # @filter [Boolean] :checked Match checked fields?
320
+ # @filter [Boolean] :unchecked Match unchecked fields?
321
+ # @filter [Boolean] :disabled Match disabled field?
322
+ # @filter [String] :option Match the value
323
+ #
194
324
  Capybara.add_selector(:checkbox) do
195
- xpath { |locator| XPath::HTML.checkbox(locator) }
196
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
197
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
325
+ xpath do |locator, options|
326
+ xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
327
+ locate_field(xpath, locator, options)
328
+ end
329
+
330
+ filter_set(:_field, [:checked, :unchecked, :disabled, :name])
331
+
198
332
  filter(:option) { |node, value| node.value == value.to_s }
199
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
333
+
200
334
  describe do |options|
201
- desc, states = "", []
335
+ desc = String.new
202
336
  desc << " with value #{options[:option].inspect}" if options[:option]
203
- states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
204
- states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
205
- states << 'disabled' if options[:disabled]
206
- desc << " that is #{states.join(' and ')}" unless states.empty?
337
+ desc << describe_all_expression_filters(options)
207
338
  desc
208
339
  end
209
340
  end
210
341
 
342
+ ##
343
+ #
344
+ # Find select elements
345
+ #
346
+ # @locator Match id, name, placeholder, or associated label text
347
+ # @filter [String] :id Matches the id attribute
348
+ # @filter [String] :name Matches the name attribute
349
+ # @filter [String] :placeholder Matches the placeholder attribute
350
+ # @filter [String, Array<String>] :class Matches the class(es) provided
351
+ # @filter [Boolean] :disabled Match disabled field?
352
+ # @filter [Boolean] :multiple Match fields that accept multiple values
353
+ # @filter [Array<String>] :options Exact match options
354
+ # @filter [Array<String>] :with_options Partial match options
355
+ # @filter [String, Array<String>] :selected Match the selection(s)
356
+ # @filter [String, Array<String>] :with_selected Partial match the selection(s)
357
+ #
211
358
  Capybara.add_selector(:select) do
212
359
  label "select box"
213
- xpath { |locator| XPath::HTML.select(locator) }
360
+ xpath do |locator, options|
361
+ xpath = XPath.descendant(:select)
362
+ locate_field(xpath, locator, options)
363
+ end
364
+
365
+ filter_set(:_field, [:disabled, :multiple, :name, :placeholder])
366
+
214
367
  filter(:options) do |node, options|
215
- actual = node.all(:xpath, './/option').map { |option| option.text }
368
+ if node.visible?
369
+ actual = node.all(:xpath, './/option').map { |option| option.text }
370
+ else
371
+ actual = node.all(:xpath, './/option', visible: false).map { |option| option.text(:all) }
372
+ end
216
373
  options.sort == actual.sort
217
374
  end
218
- filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option, minimum: 0) } }
375
+
376
+ filter(:with_options) do |node, options|
377
+ finder_settings = { minimum: 0 }
378
+ if !node.visible?
379
+ finder_settings[:visible] = false
380
+ end
381
+ options.all? { |option| node.first(:option, option, finder_settings) }
382
+ end
383
+
219
384
  filter(:selected) do |node, selected|
220
- actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
221
- [selected].flatten.sort == actual.sort
385
+ actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
386
+ Array(selected).sort == actual.sort
222
387
  end
223
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
388
+
389
+ filter(:with_selected) do |node, selected|
390
+ actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
391
+ (Array(selected) - actual).empty?
392
+ end
393
+
224
394
  describe do |options|
225
- desc = ""
395
+ desc = String.new
226
396
  desc << " with options #{options[:options].inspect}" if options[:options]
227
397
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
228
398
  desc << " with #{options[:selected].inspect} selected" if options[:selected]
229
- desc << " that is disabled" if options[:disabled]
399
+ desc << " with at least #{options[:with_selected].inspect} selected" if options[:with_selected]
400
+ desc << describe_all_expression_filters(options)
230
401
  desc
231
402
  end
232
403
  end
233
404
 
405
+ ##
406
+ #
407
+ # Find option elements
408
+ #
409
+ # @locator Match text of option
410
+ # @filter [Boolean] :disabled Match disabled option
411
+ # @filter [Boolean] :selected Match selected option
412
+ #
234
413
  Capybara.add_selector(:option) do
235
- xpath { |locator| XPath::HTML.option(locator) }
414
+ xpath do |locator|
415
+ xpath = XPath.descendant(:option)
416
+ xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
417
+ xpath
418
+ end
419
+
420
+ filter(:disabled, :boolean) { |node, value| not(value ^ node.disabled?) }
421
+ filter(:selected, :boolean) { |node, value| not(value ^ node.selected?) }
422
+
423
+ describe do |options|
424
+ desc = String.new
425
+ desc << " that is#{' not' unless options[:disabled]} disabled" if options.has_key?(:disabled)
426
+ desc << " that is#{' not' unless options[:selected]} selected" if options.has_key?(:selected)
427
+ desc
428
+ end
236
429
  end
237
430
 
431
+ ##
432
+ #
433
+ # Find file input elements
434
+ #
435
+ # @locator Match id, name, or associated label text
436
+ # @filter [String] :id Matches the id attribute
437
+ # @filter [String] :name Matches the name attribute
438
+ # @filter [String, Array<String>] :class Matches the class(es) provided
439
+ # @filter [Boolean] :disabled Match disabled field?
440
+ # @filter [Boolean] :multiple Match field that accepts multiple values
441
+ #
238
442
  Capybara.add_selector(:file_field) do
239
443
  label "file field"
240
- xpath { |locator| XPath::HTML.file_field(locator) }
241
- filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
242
- describe { |options| " that is disabled" if options[:disabled] }
444
+ xpath do |locator, options|
445
+ xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
446
+ locate_field(xpath, locator, options)
447
+ end
448
+
449
+ filter_set(:_field, [:disabled, :multiple, :name])
450
+
451
+ describe do |options|
452
+ desc = String.new
453
+ desc << describe_all_expression_filters(options)
454
+ desc
455
+ end
456
+ end
457
+
458
+ ##
459
+ #
460
+ # Find label elements
461
+ #
462
+ # @locator Match id or text contents
463
+ # @filter [Element, String] :for The element or id of the element associated with the label
464
+ #
465
+ Capybara.add_selector(:label) do
466
+ label "label"
467
+ xpath(:for) do |locator, options|
468
+ xpath = XPath.descendant(:label)
469
+ xpath = xpath[XPath.string.n.is(locator.to_s).or XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
470
+ if options.has_key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
471
+ xpath = xpath[XPath.attr(:for).equals(options[:for].to_s).or((~XPath.attr(:for)).and(XPath.descendant()[XPath.attr(:id).equals(options[:for].to_s)]))]
472
+ end
473
+ xpath
474
+ end
475
+
476
+ filter(:for) do |node, field_or_value|
477
+ if field_or_value.is_a? Capybara::Node::Element
478
+ if node[:for]
479
+ field_or_value[:id] == node[:for]
480
+ else
481
+ field_or_value.find_xpath('./ancestor::label[1]').include? node.base
482
+ end
483
+ else
484
+ #Non element values were handled through the expression filter
485
+ true
486
+ end
487
+ end
488
+
489
+ describe do |options|
490
+ desc = String.new
491
+ desc << " for #{options[:for]}" if options[:for]
492
+ desc
493
+ end
243
494
  end
244
495
 
496
+ ##
497
+ #
498
+ # Find table elements
499
+ #
500
+ # @locator id or caption text of table
501
+ # @filter [String] :id Match id attribute of table
502
+ # @filter [String] :caption Match text of associated caption
503
+ # @filter [String, Array<String>] :class Matches the class(es) provided
504
+ #
245
505
  Capybara.add_selector(:table) do
246
- xpath { |locator| XPath::HTML.table(locator) }
506
+ xpath(:caption) do |locator, options|
507
+ xpath = XPath.descendant(:table)
508
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
509
+ xpath = xpath[XPath.descendant(:caption).equals(options[:caption])] if options[:caption]
510
+ xpath
511
+ end
512
+
513
+ describe do |options|
514
+ desc = String.new
515
+ desc << " with caption #{options[:caption]}" if options[:caption]
516
+ desc
517
+ end
518
+ end
519
+
520
+ ##
521
+ #
522
+ # Find frame/iframe elements
523
+ #
524
+ # @locator Match id or name
525
+ # @filter [String] :id Match id attribute
526
+ # @filter [String] :name Match name attribute
527
+ # @filter [String, Array<String>] :class Matches the class(es) provided
528
+ #
529
+ Capybara.add_selector(:frame) do
530
+ xpath(:name) do |locator, options|
531
+ xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
532
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.attr(:name).equals(locator)] unless locator.nil?
533
+ xpath = expression_filters.keys.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
534
+ xpath
535
+ end
536
+
537
+ describe do |options|
538
+ desc = String.new
539
+ desc << " with name #{options[:name]}" if options[:name]
540
+ desc
541
+ end
247
542
  end