capybara 3.12.0 → 3.13.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +13 -0
  3. data/README.md +13 -3
  4. data/lib/capybara.rb +8 -4
  5. data/lib/capybara/config.rb +3 -1
  6. data/lib/capybara/driver/base.rb +2 -2
  7. data/lib/capybara/driver/node.rb +11 -2
  8. data/lib/capybara/minitest.rb +3 -3
  9. data/lib/capybara/minitest/spec.rb +10 -3
  10. data/lib/capybara/node/actions.rb +4 -0
  11. data/lib/capybara/node/base.rb +13 -5
  12. data/lib/capybara/node/document.rb +12 -0
  13. data/lib/capybara/node/element.rb +37 -0
  14. data/lib/capybara/node/finders.rb +1 -0
  15. data/lib/capybara/node/matchers.rb +19 -4
  16. data/lib/capybara/node/simple.rb +7 -2
  17. data/lib/capybara/queries/selector_query.rb +93 -9
  18. data/lib/capybara/rspec/matchers.rb +11 -3
  19. data/lib/capybara/rspec/matchers/become_closed.rb +1 -1
  20. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  21. data/lib/capybara/selector.rb +30 -30
  22. data/lib/capybara/selector/selector.rb +47 -3
  23. data/lib/capybara/selenium/driver.rb +12 -11
  24. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +3 -3
  25. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  26. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +13 -0
  27. data/lib/capybara/selenium/extensions/find.rb +81 -0
  28. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  29. data/lib/capybara/selenium/node.rb +52 -28
  30. data/lib/capybara/session.rb +13 -1
  31. data/lib/capybara/spec/public/test.js +1 -0
  32. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  33. data/lib/capybara/spec/session/attach_file_spec.rb +6 -6
  34. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  35. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -0
  36. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  37. data/lib/capybara/spec/session/fill_in_spec.rb +7 -1
  38. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  39. data/lib/capybara/spec/session/find_spec.rb +11 -0
  40. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +0 -1
  41. data/lib/capybara/spec/session/has_css_spec.rb +32 -0
  42. data/lib/capybara/spec/session/has_select_spec.rb +2 -2
  43. data/lib/capybara/spec/session/has_selector_spec.rb +7 -0
  44. data/lib/capybara/spec/session/has_text_spec.rb +1 -1
  45. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  46. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  47. data/lib/capybara/spec/session/select_spec.rb +5 -0
  48. data/lib/capybara/spec/spec_helper.rb +1 -0
  49. data/lib/capybara/spec/views/obscured.erb +1 -1
  50. data/lib/capybara/spec/views/scroll.erb +20 -0
  51. data/lib/capybara/spec/views/with_html.erb +1 -1
  52. data/lib/capybara/version.rb +1 -1
  53. data/lib/capybara/window.rb +1 -1
  54. data/spec/basic_node_spec.rb +11 -0
  55. data/spec/dsl_spec.rb +1 -1
  56. data/spec/minitest_spec.rb +2 -2
  57. data/spec/minitest_spec_spec.rb +1 -1
  58. data/spec/rack_test_spec.rb +1 -0
  59. data/spec/result_spec.rb +2 -2
  60. data/spec/selector_spec.rb +11 -1
  61. data/spec/selenium_spec_firefox.rb +1 -1
  62. data/spec/selenium_spec_ie.rb +70 -9
  63. data/spec/session_spec.rb +9 -0
  64. data/spec/shared_selenium_session.rb +4 -3
  65. data/spec/spec_helper.rb +2 -0
  66. metadata +38 -6
  67. data/lib/capybara/rspec/matchers/have_style.rb +0 -23
  68. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
@@ -35,6 +35,7 @@ module Capybara
35
35
  # * :unchecked (Boolean) — Match unchecked fields?
36
36
  # * :disabled (Boolean) — Match disabled field?
37
37
  # * :multiple (Boolean) — Match fields that accept multiple values
38
+ # * :style (String, Regexp, Hash)
38
39
  #
39
40
  # * **:fieldset** - Select fieldset elements
40
41
  # * Locator: Matches id or contents of wrapped legend
@@ -42,6 +43,7 @@ module Capybara
42
43
  # * :id (String, Regexp, XPath::Expression) — Matches id attribute
43
44
  # * :legend (String) — Matches contents of wrapped legend
44
45
  # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
46
+ # * :style (String, Regexp, Hash)
45
47
  #
46
48
  # * **:link** - Find links ( <a> elements with an href attribute )
47
49
  # * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
@@ -51,6 +53,7 @@ module Capybara
51
53
  # * :alt (String) — Matches the alt attribute of a contained img element
52
54
  # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
53
55
  # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
56
+ # * :style (String, Regexp, Hash)
54
57
  #
55
58
  # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
56
59
  # * Locator: Matches the id, Capybara.test_id attribute, 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
@@ -60,6 +63,7 @@ module Capybara
60
63
  # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
61
64
  # * :value (String) — Matches the value of an input button
62
65
  # * :type
66
+ # * :style (String, Regexp, Hash)
63
67
  #
64
68
  # * **:link_or_button** - Find links or buttons
65
69
  # * Locator: See :link and :button selectors
@@ -75,6 +79,7 @@ module Capybara
75
79
  # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
76
80
  # * :disabled (Boolean) — Match disabled field?
77
81
  # * :multiple (Boolean) — Match fields that accept multiple values
82
+ # * :style (String, Regexp, Hash)
78
83
  #
79
84
  # * **:radio_button** - Find radio buttons
80
85
  # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
@@ -86,6 +91,7 @@ module Capybara
86
91
  # * :unchecked (Boolean) — Match unchecked fields?
87
92
  # * :disabled (Boolean) — Match disabled field?
88
93
  # * :option (String) — Match the value
94
+ # * :style (String, Regexp, Hash)
89
95
  #
90
96
  # * **:checkbox** - Find checkboxes
91
97
  # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
@@ -97,6 +103,7 @@ module Capybara
97
103
  # * *:unchecked (Boolean) — Match unchecked fields?
98
104
  # * *:disabled (Boolean) — Match disabled field?
99
105
  # * *:option (String) — Match the value
106
+ # * :style (String, Regexp, Hash)
100
107
  #
101
108
  # * **:select** - Find select elements
102
109
  # * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
@@ -111,6 +118,7 @@ module Capybara
111
118
  # * :with_options (Array<String>) — Partial match options
112
119
  # * :selected (String, Array<String>) — Match the selection(s)
113
120
  # * :with_selected (String, Array<String>) — Partial match the selection(s)
121
+ # * :style (String, Regexp, Hash)
114
122
  #
115
123
  # * **:option** - Find option elements
116
124
  # * Locator: Match text of option
@@ -136,6 +144,7 @@ module Capybara
136
144
  # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
137
145
  # * :disabled (Boolean) — Match disabled field?
138
146
  # * :multiple (Boolean) — Match field that accepts multiple values
147
+ # * :style (String, Regexp, Hash)
139
148
  #
140
149
  # * **:label** - Find label elements
141
150
  # * Locator: Match id or text contents
@@ -148,6 +157,7 @@ module Capybara
148
157
  # * :id (String, Regexp, XPath::Expression) — Match id attribute of table
149
158
  # * :caption (String) — Match text of associated caption
150
159
  # * :class ((String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
160
+ # * :style (String, Regexp, Hash)
151
161
  #
152
162
  # * **:frame** - Find frame/iframe elements
153
163
  # * Locator: Match id or name
@@ -155,6 +165,7 @@ module Capybara
155
165
  # * :id (String, Regexp, XPath::Expression) — Match id attribute
156
166
  # * :name (String) — Match name attribute
157
167
  # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
168
+ # * :style (String, Regexp, Hash)
158
169
  #
159
170
  # * **:element**
160
171
  # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
@@ -173,8 +184,8 @@ module Capybara
173
184
  all.fetch(name.to_sym) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
174
185
  end
175
186
 
176
- def add(name, &block)
177
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
187
+ def add(name, **options, &block)
188
+ all[name.to_sym] = Capybara::Selector.new(name.to_sym, **options, &block)
178
189
  end
179
190
 
180
191
  def update(name, &block)
@@ -190,7 +201,7 @@ module Capybara
190
201
  end
191
202
  end
192
203
 
193
- def initialize(name, &block)
204
+ def initialize(name, locator_type: nil, raw_locator: false, &block)
194
205
  @name = name
195
206
  @filter_set = FilterSet.add(name) {}
196
207
  @match = nil
@@ -201,6 +212,8 @@ module Capybara
201
212
  @expression_filters = {}
202
213
  @locator_filter = nil
203
214
  @default_visibility = nil
215
+ @locator_type = locator_type
216
+ @raw_locator = raw_locator
204
217
  @config = {
205
218
  enable_aria_label: false,
206
219
  test_id: nil
@@ -301,6 +314,8 @@ module Capybara
301
314
  else
302
315
  warn 'Selector has no format'
303
316
  end
317
+ ensure
318
+ warn "Locator #{locator.inspect} must #{locator_description}. This will raise an error in a future version of Capybara." unless locator_valid?(locator)
304
319
  end
305
320
 
306
321
  ##
@@ -428,8 +443,37 @@ module Capybara
428
443
  Thread.current["capybara_#{object_id}_errors"] = nil
429
444
  end
430
445
 
446
+ # @api private
447
+ def raw_locator?
448
+ !!@raw_locator
449
+ end
450
+
431
451
  private
432
452
 
453
+ def locator_types
454
+ return nil unless @locator_type
455
+
456
+ Array(@locator_type)
457
+ end
458
+
459
+ def locator_valid?(locator)
460
+ return true unless locator && locator_types
461
+
462
+ locator_types&.any? do |type_or_method|
463
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
464
+ end
465
+ end
466
+
467
+ def locator_description
468
+ locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
469
+ if symbol
470
+ "respond to #{types_or_methods.join(' or ')}"
471
+ else
472
+ "be an instance of #{types_or_methods.join(' or ')}"
473
+ end
474
+ end.join(' or ')
475
+ end
476
+
433
477
  def errors
434
478
  Thread.current["capybara_#{object_id}_errors"] || []
435
479
  end
@@ -4,6 +4,8 @@ require 'uri'
4
4
  require 'English'
5
5
 
6
6
  class Capybara::Selenium::Driver < Capybara::Driver::Base
7
+ include Capybara::Selenium::Find
8
+
7
9
  DEFAULT_OPTIONS = {
8
10
  browser: :firefox,
9
11
  clear_local_storage: nil,
@@ -73,14 +75,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
73
75
  browser.current_url
74
76
  end
75
77
 
76
- def find_xpath(selector)
77
- browser.find_elements(:xpath, selector).map(&method(:build_node))
78
- end
79
-
80
- def find_css(selector)
81
- browser.find_elements(:css, selector).map(&method(:build_node))
82
- end
83
-
84
78
  def wait?; true; end
85
79
  def needs_server?; true; end
86
80
 
@@ -348,8 +342,12 @@ private
348
342
  end
349
343
  end
350
344
 
351
- def build_node(native_node)
352
- ::Capybara::Selenium::Node.new(self, native_node)
345
+ def find_context
346
+ browser
347
+ end
348
+
349
+ def build_node(native_node, initial_cache = {})
350
+ ::Capybara::Selenium::Node.new(self, native_node, initial_cache)
353
351
  end
354
352
 
355
353
  def specialize_driver(sel_driver)
@@ -359,6 +357,8 @@ private
359
357
  when :firefox
360
358
  require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(sel_driver)
361
359
  extend FirefoxDriver if sel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
360
+ when :ie, :internet_explorer
361
+ extend InternetExplorerDriver
362
362
  end
363
363
  end
364
364
 
@@ -385,7 +385,7 @@ private
385
385
  until find_xpath('/html/body/*').empty?
386
386
  raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
387
387
 
388
- sleep 0.05
388
+ sleep 0.01
389
389
  end
390
390
  end
391
391
 
@@ -402,3 +402,4 @@ end
402
402
 
403
403
  require 'capybara/selenium/driver_specializations/chrome_driver'
404
404
  require 'capybara/selenium/driver_specializations/firefox_driver'
405
+ require 'capybara/selenium/driver_specializations/internet_explorer_driver'
@@ -23,7 +23,7 @@ module Capybara::Selenium::Driver::ChromeDriver
23
23
 
24
24
  # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
25
25
  # and raises unnecessary error. Wait a bit and try again.
26
- sleep 0.5
26
+ sleep 0.25
27
27
  super
28
28
  end
29
29
 
@@ -51,8 +51,8 @@ private
51
51
  result['value']
52
52
  end
53
53
 
54
- def build_node(native_node)
55
- ::Capybara::Selenium::ChromeNode.new(self, native_node)
54
+ def build_node(native_node, initial_cache = {})
55
+ ::Capybara::Selenium::ChromeNode.new(self, native_node, initial_cache)
56
56
  end
57
57
 
58
58
  def bridge
@@ -44,7 +44,7 @@ module Capybara::Selenium::Driver::FirefoxDriver
44
44
 
45
45
  private
46
46
 
47
- def build_node(native_node)
48
- ::Capybara::Selenium::FirefoxNode.new(self, native_node)
47
+ def build_node(native_node, initial_cache = {})
48
+ ::Capybara::Selenium::FirefoxNode.new(self, native_node, initial_cache)
49
49
  end
50
50
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara::Selenium::Driver::InternetExplorerDriver
4
+ def switch_to_frame(frame)
5
+ return super unless frame == :parent
6
+
7
+ # iedriverserver has an issue if the current frame is removed from within it
8
+ # so we have to move to the default_content and iterate back through the frames
9
+ handles = @frame_handles[current_window_handle]
10
+ browser.switch_to.default_content
11
+ handles.tap(&:pop).each { |fh| browser.switch_to.frame(fh) }
12
+ end
13
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module Find
6
+ def find_xpath(selector, uses_visibility: false, styles: nil, **_options)
7
+ find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles)
8
+ end
9
+
10
+ def find_css(selector, uses_visibility: false, texts: [], styles: nil, **_options)
11
+ find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles)
12
+ end
13
+
14
+ private
15
+
16
+ def find_by(format, selector, uses_visibility:, texts:, styles:)
17
+ els = find_context.find_elements(format, selector)
18
+ hints = []
19
+ if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
20
+ els = filter_by_text(els, texts) unless texts.empty?
21
+
22
+ hints_js = +''
23
+ functions = []
24
+ if uses_visibility
25
+ hints_js << <<~VISIBILITY_JS
26
+ var vis_func = #{is_displayed_atom};
27
+ VISIBILITY_JS
28
+ functions << 'vis_func'
29
+ end
30
+
31
+ if styles.is_a? Hash
32
+ hints_js << <<~STYLE_JS
33
+ var style_func = function(el){
34
+ var el_styles = window.getComputedStyle(el);
35
+ return #{styles.keys.map(&:to_s)}.reduce(function(res, style){
36
+ res[style] = el_styles[style];
37
+ return res;
38
+ }, {});
39
+ };
40
+ STYLE_JS
41
+ functions << 'style_func'
42
+ end
43
+
44
+ unless functions.empty?
45
+ hints_js << <<~EACH_JS
46
+ return arguments[0].map(function(el){
47
+ return [#{functions.join(',')}].map(function(fn){ return fn.call(null, el) }); });
48
+ EACH_JS
49
+
50
+ hints = es_context.execute_script hints_js, els
51
+ hints.map! do |results|
52
+ result = {}
53
+ result[:style] = results.pop if styles.is_a? Hash
54
+ result[:visible] = results.pop if uses_visibility
55
+ result
56
+ end
57
+ end
58
+ end
59
+ els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) }
60
+ end
61
+
62
+ def filter_by_text(elements, texts)
63
+ es_context.execute_script <<~JS, elements, texts
64
+ var texts = arguments[1]
65
+ return arguments[0].filter(function(el){
66
+ var content = el.textContent.toLowerCase();
67
+ return texts.every(function(txt){ return content.indexOf(txt.toLowerCase()) != -1 });
68
+ })
69
+ JS
70
+ end
71
+
72
+ def es_context
73
+ respond_to?(:execute_script) ? self : driver
74
+ end
75
+
76
+ def is_displayed_atom # rubocop:disable Naming/PredicateName
77
+ @@is_displayed_atom ||= browser.send(:bridge).send(:read_atom, 'isDisplayed')
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module Scroll
6
+ def scroll_by(x, y)
7
+ driver.execute_script <<~JS, self, x, y
8
+ var el = arguments[0];
9
+ if (el.scrollBy){
10
+ el.scrollBy(arguments[1], arguments[2]);
11
+ } else {
12
+ el.scrollTop = el.scrollTop + arguments[2];
13
+ el.scrollLeft = el.scrollLeft + arguments[1];
14
+ }
15
+ JS
16
+ end
17
+
18
+ def scroll_to(element, location, position = nil)
19
+ # location, element = element, nil if element.is_a? Symbol
20
+ if element.is_a? Capybara::Selenium::Node
21
+ scroll_element_to_location(element, location)
22
+ elsif location.is_a? Symbol
23
+ scroll_to_location(location)
24
+ else
25
+ scroll_to_coords(*position)
26
+ end
27
+ self
28
+ end
29
+
30
+ private
31
+
32
+ def scroll_element_to_location(element, location)
33
+ scroll_opts = case location
34
+ when :top
35
+ 'true'
36
+ when :bottom
37
+ 'false'
38
+ when :center
39
+ "{behavior: 'instant', block: 'center'}"
40
+ else
41
+ raise ArgumentError, "Invalid scroll_to location: #{location}"
42
+ end
43
+ driver.execute_script <<~JS, element
44
+ arguments[0].scrollIntoView(#{scroll_opts})
45
+ JS
46
+ end
47
+
48
+ def scroll_to_location(location)
49
+ scroll_y = case location
50
+ when :top
51
+ '0'
52
+ when :bottom
53
+ 'arguments[0].scrollHeight'
54
+ when :center
55
+ '(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
56
+ end
57
+ driver.execute_script <<~JS, self
58
+ if (arguments[0].scrollTo){
59
+ arguments[0].scrollTo(0, #{scroll_y});
60
+ } else {
61
+ arguments[0].scrollTop = #{scroll_y};
62
+ }
63
+ JS
64
+ end
65
+
66
+ def scroll_to_coords(x, y)
67
+ driver.execute_script <<~JS, self, x, y
68
+ if (arguments[0].scrollTo){
69
+ arguments[0].scrollTo(arguments[1], arguments[2]);
70
+ } else {
71
+ arguments[0].scrollTop = arguments[2];
72
+ arguments[0].scrollLeft = arguments[1];
73
+ }
74
+ JS
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,13 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Selenium specific implementation of the Capybara::Driver::Node API
4
+
5
+ require 'capybara/selenium/extensions/find'
6
+ require 'capybara/selenium/extensions/scroll'
7
+
4
8
  class Capybara::Selenium::Node < Capybara::Driver::Node
9
+ include Capybara::Selenium::Find
10
+ include Capybara::Selenium::Scroll
11
+
5
12
  def visible_text
6
13
  native.text
7
14
  end
8
15
 
9
16
  def all_text
10
- text = driver.execute_script('return arguments[0].textContent', self)
17
+ text = driver.evaluate_script('arguments[0].textContent', self)
11
18
  text.gsub(/[\u200b\u200e\u200f]/, '')
12
19
  .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
13
20
  .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
@@ -148,35 +155,12 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
148
155
  native.attribute('isContentEditable')
149
156
  end
150
157
 
151
- def find_xpath(locator)
152
- native.find_elements(:xpath, locator).map { |el| self.class.new(driver, el) }
153
- end
154
-
155
- def find_css(locator)
156
- native.find_elements(:css, locator).map { |el| self.class.new(driver, el) }
157
- end
158
-
159
158
  def ==(other)
160
159
  native == other.native
161
160
  end
162
161
 
163
162
  def path
164
- path = find_xpath(XPath.ancestor_or_self).reverse
165
-
166
- result = []
167
- default_ns = path.last[:namespaceURI]
168
- while (node = path.shift)
169
- parent = path.first
170
- selector = node[:tagName]
171
- if node[:namespaceURI] != default_ns
172
- selector = XPath.child.where((XPath.local_name == selector) & (XPath.namespace_uri == node[:namespaceURI])).to_s
173
- end
174
-
175
- selector += sibling_index(parent, node, selector) if parent
176
- result.push selector
177
- end
178
-
179
- '/' + result.reverse.join('/')
163
+ driver.evaluate_script GET_XPATH_SCRIPT, self
180
164
  end
181
165
 
182
166
  protected
@@ -305,7 +289,7 @@ private
305
289
  # Ensure we are focused on the element
306
290
  click
307
291
 
308
- script = <<-JS
292
+ driver.execute_script <<-JS, self
309
293
  var range = document.createRange();
310
294
  var sel = window.getSelection();
311
295
  arguments[0].focus();
@@ -313,7 +297,6 @@ private
313
297
  sel.removeAllRanges();
314
298
  sel.addRange(range);
315
299
  JS
316
- driver.execute_script script, self
317
300
 
318
301
  # The action api has a speed problem but both chrome and firefox 58 raise errors
319
302
  # if we use the faster direct send_keys. For now just send_keys to the element
@@ -341,8 +324,12 @@ private
341
324
  each_key(keys) { |key| actions.key_up(key) }
342
325
  end
343
326
 
327
+ def browser
328
+ driver.browser
329
+ end
330
+
344
331
  def browser_action
345
- driver.browser.action
332
+ browser.action
346
333
  end
347
334
 
348
335
  def each_key(keys)
@@ -357,6 +344,43 @@ private
357
344
  end
358
345
  end
359
346
 
347
+ def find_context
348
+ native
349
+ end
350
+
351
+ def build_node(native_node, initial_cache = {})
352
+ self.class.new(driver, native_node, initial_cache)
353
+ end
354
+
355
+ GET_XPATH_SCRIPT = <<~'JS'
356
+ (function(el, xml){
357
+ var xpath = '';
358
+ var pos, tempitem2;
359
+
360
+ while(el !== xml.documentElement) {
361
+ pos = 0;
362
+ tempitem2 = el;
363
+ while(tempitem2) {
364
+ if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
365
+ pos += 1;
366
+ }
367
+ tempitem2 = tempitem2.previousSibling;
368
+ }
369
+
370
+ if (el.namespaceURI != xml.documentElement.namespaceURI) {
371
+ xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
372
+ } else {
373
+ xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
374
+ }
375
+
376
+ el = el.parentNode;
377
+ }
378
+ xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
379
+ xpath = xpath.replace(/\/$/, '');
380
+ return xpath;
381
+ })(arguments[0], document)
382
+ JS
383
+
360
384
  # SettableValue encapsulates time/date field formatting
361
385
  class SettableValue
362
386
  attr_reader :value