capybara 2.5.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
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