capybara 3.12.0 → 3.13.0

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