watir 6.16.2 → 6.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +5 -5
  2. data/.github/actions/enable-safari/action.yml +11 -0
  3. data/.github/actions/install-chrome/action.yml +11 -0
  4. data/.github/workflows/linux.yml +61 -0
  5. data/.github/workflows/mac.yml +55 -0
  6. data/.github/workflows/unit.yml +31 -0
  7. data/.github/workflows/windows.yml +39 -0
  8. data/.rubocop.yml +32 -107
  9. data/.rubocop_todo.yml +36 -0
  10. data/CHANGES.md +42 -0
  11. data/Gemfile +3 -1
  12. data/LICENSE +2 -2
  13. data/README.md +9 -10
  14. data/Rakefile +4 -4
  15. data/lib/watir-webdriver.rb +1 -1
  16. data/lib/watir.rb +1 -1
  17. data/lib/watir/adjacent.rb +8 -10
  18. data/lib/watir/after_hooks.rb +4 -4
  19. data/lib/watir/alert.rb +1 -0
  20. data/lib/watir/attribute_helper.rb +2 -0
  21. data/lib/watir/browser.rb +7 -3
  22. data/lib/watir/capabilities.rb +9 -6
  23. data/lib/watir/cookies.rb +3 -1
  24. data/lib/watir/element_collection.rb +21 -6
  25. data/lib/watir/elements/element.rb +66 -53
  26. data/lib/watir/elements/file_field.rb +1 -0
  27. data/lib/watir/elements/html_elements.rb +0 -1
  28. data/lib/watir/elements/iframe.rb +4 -3
  29. data/lib/watir/elements/link.rb +0 -9
  30. data/lib/watir/elements/radio.rb +1 -1
  31. data/lib/watir/elements/select.rb +22 -7
  32. data/lib/watir/generator/base/spec_extractor.rb +4 -4
  33. data/lib/watir/generator/html/generator.rb +1 -1
  34. data/lib/watir/has_window.rb +17 -15
  35. data/lib/watir/js_execution.rb +3 -3
  36. data/lib/watir/js_snippets.rb +2 -2
  37. data/lib/watir/legacy_wait.rb +1 -1
  38. data/lib/watir/locators.rb +1 -3
  39. data/lib/watir/locators/element/locator.rb +22 -12
  40. data/lib/watir/locators/element/selector_builder.rb +12 -13
  41. data/lib/watir/locators/element/selector_builder/xpath.rb +40 -13
  42. data/lib/watir/locators/text_field/matcher.rb +1 -1
  43. data/lib/watir/locators/text_field/selector_builder/xpath.rb +3 -1
  44. data/lib/watir/logger.rb +7 -20
  45. data/lib/watir/radio_set.rb +2 -2
  46. data/lib/watir/user_editable.rb +6 -2
  47. data/lib/watir/version.rb +1 -1
  48. data/lib/watir/wait.rb +2 -0
  49. data/lib/watir/wait/timer.rb +1 -1
  50. data/lib/watir/window.rb +8 -4
  51. data/lib/watir/window_collection.rb +105 -0
  52. data/lib/watirspec.rb +2 -1
  53. data/lib/watirspec/guards.rb +1 -1
  54. data/lib/watirspec/implementation.rb +3 -5
  55. data/lib/watirspec/rake_tasks.rb +2 -0
  56. data/lib/watirspec/runner.rb +5 -1
  57. data/lib/watirspec/server.rb +1 -1
  58. data/spec/spec_helper.rb +2 -7
  59. data/spec/unit/container_spec.rb +1 -1
  60. data/spec/unit/logger_spec.rb +5 -7
  61. data/spec/unit/match_elements/element_spec.rb +17 -15
  62. data/spec/unit/selector_builder/button_spec.rb +16 -15
  63. data/spec/unit/selector_builder/element_spec.rb +58 -9
  64. data/spec/unit/selector_builder/text_field_spec.rb +14 -14
  65. data/spec/unit/unit_helper.rb +2 -4
  66. data/spec/watirspec/after_hooks_spec.rb +58 -68
  67. data/spec/watirspec/alert_spec.rb +69 -79
  68. data/spec/watirspec/browser_spec.rb +51 -48
  69. data/spec/watirspec/cookies_spec.rb +52 -37
  70. data/spec/watirspec/drag_and_drop_spec.rb +14 -38
  71. data/spec/watirspec/elements/button_spec.rb +2 -0
  72. data/spec/watirspec/elements/buttons_spec.rb +1 -1
  73. data/spec/watirspec/elements/checkbox_spec.rb +8 -4
  74. data/spec/watirspec/elements/date_field_spec.rb +18 -9
  75. data/spec/watirspec/elements/date_time_field_spec.rb +3 -4
  76. data/spec/watirspec/elements/div_spec.rb +62 -54
  77. data/spec/watirspec/elements/element_spec.rb +73 -88
  78. data/spec/watirspec/elements/elements_spec.rb +12 -3
  79. data/spec/watirspec/elements/filefield_spec.rb +25 -50
  80. data/spec/watirspec/elements/form_spec.rb +6 -8
  81. data/spec/watirspec/elements/frame_spec.rb +10 -13
  82. data/spec/watirspec/elements/iframe_spec.rb +12 -9
  83. data/spec/watirspec/elements/iframes_spec.rb +2 -2
  84. data/spec/watirspec/elements/link_spec.rb +23 -12
  85. data/spec/watirspec/elements/links_spec.rb +11 -3
  86. data/spec/watirspec/elements/option_spec.rb +15 -17
  87. data/spec/watirspec/elements/select_list_spec.rb +222 -117
  88. data/spec/watirspec/elements/text_field_spec.rb +8 -4
  89. data/spec/watirspec/elements/tr_spec.rb +0 -9
  90. data/spec/watirspec/html/forms_with_input_elements.html +1 -0
  91. data/spec/watirspec/html/iframes.html +3 -0
  92. data/spec/watirspec/html/non_control_elements.html +4 -4
  93. data/spec/watirspec/html/right_click.html +12 -0
  94. data/spec/watirspec/html/wait.html +6 -6
  95. data/spec/watirspec/html/window_switching.html +10 -0
  96. data/spec/watirspec/legacy_wait_spec.rb +216 -0
  97. data/spec/watirspec/support/rspec_matchers.rb +17 -13
  98. data/spec/watirspec/user_editable_spec.rb +1 -1
  99. data/spec/watirspec/wait_spec.rb +257 -305
  100. data/spec/watirspec/window_switching_spec.rb +332 -211
  101. data/spec/watirspec_helper.rb +16 -19
  102. data/support/doctest_helper.rb +0 -2
  103. data/watir.gemspec +6 -7
  104. metadata +36 -26
  105. data/.travis.yml +0 -84
  106. data/appveyor.yml +0 -12
  107. data/lib/watir/elements/area.rb +0 -10
  108. data/spec/watirspec/relaxed_locate_spec.rb +0 -113
data/CHANGES.md CHANGED
@@ -1,3 +1,45 @@
1
+ ### 6.18.0 (2021-02-26)
2
+
3
+ * Implement `WindowCollection` to manage multiple `Window` objects
4
+ * Add support for locating `Window` by `:element`
5
+ * Deprecate locating `Window` by `:index`
6
+ * Deprecate `Select#select_all` in favor of `#select` and an `Array`
7
+ * Implement `Browser#switch_window` (#849)
8
+ * Add support for `Numeric` attribute values to `Waitable`
9
+ * Allow users to specify Selenium 4 in their projects
10
+ * Update stale element handling behavior to match webdriver spec (#905 #909)
11
+ * Implement `Waitable` for `ElementCollection` (#853 #857)
12
+ * Improve performance for nested elements (#843)
13
+ * Less strict version check for `regexp_parser` gem (thanks Pavel Lobashov)
14
+
15
+ ### 6.17.0 (2020-08-27)
16
+ * Require Ruby > 2.5
17
+ * Implement Logger#selenium= to set selenium level from Watir
18
+ * Implement FileField#upload
19
+ * Fix bug with staleness handling in #exist and #present? (#853 & #852)
20
+ * Fix bug when locating elements by text with RegExp (#866 #871)
21
+ * Implement modifiers for `Element#right_click` (thanks Lakshya Kapoor #861)
22
+ * Updated locator code to adhere to spec on what attributes are case sensitive (#507 #856)
23
+ * Fix locating bug when iframe is nested under another element (thanks Matthew Mazaika #885 #886)
24
+ * Deprecate Element#scroll_into_view in favor of the new Scroll methods (#884)
25
+ * Fix threading bugs by allowing each Browser instance its own Timer (#881)
26
+ * Allow adjacent locators to return Input subtype when applicable (#878)
27
+ * Removed unnecessary reference to rubyforge (thanks olleolleolle #874)
28
+ * Removed deprecated Selenium classes (thanks joesho112358 #867)
29
+ * Add support for :service parameter for initializing Browser
30
+
31
+ ### 6.16.5 (2018-12-25)
32
+
33
+ * Fix bug with nested elements using scopes (#842)
34
+
35
+ ### 6.16.4 (2018-12-24)
36
+
37
+ * Minor adjustments to support locator extensions
38
+
39
+ ### 6.16.3 (2018-12-24)
40
+
41
+ * Minor adjustments to support locator extensions
42
+
1
43
  ### 6.16.2 (2018-12-24)
2
44
 
3
45
  * Fix bug merging scope when locating nested elements with css locator (#841)
data/Gemfile CHANGED
@@ -1,8 +1,10 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gem 'webidl', path: File.expand_path('../webidl') if ENV['LOCAL_WEBIDL']
4
4
 
5
5
  gem 'selenium-webdriver', path: File.expand_path('../selenium/build/rb') if ENV['LOCAL_SELENIUM']
6
6
 
7
+ gem 'ffi' if Gem.win_platform? # For selenium-webdriver on Windows
8
+
7
9
  # Specify your gem's dependencies in watir.gemspec
8
10
  gemspec
data/LICENSE CHANGED
@@ -1,8 +1,8 @@
1
1
  (the MIT License)
2
2
 
3
3
  Copyright (c) 2009-2015 Jari Bakken
4
- Copyright (c) 2015-2018 Alex Rodionov, Titus Fortner
5
- Copyright (c) 2018 Justin Ko
4
+ Copyright (c) 2015-2021 Alex Rodionov, Titus Fortner
5
+ Copyright (c) 2018-2021 Justin Ko
6
6
 
7
7
  Permission is hereby granted, free of charge, to any person obtaining
8
8
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -3,10 +3,12 @@
3
3
  Watir Powered By Selenium!
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/watir.svg)](http://badge.fury.io/rb/watir)
6
- [![Travis Status](https://travis-ci.org/watir/watir.svg?branch=master)](https://travis-ci.org/watir/watir)
7
- [![AppVeyor status](https://ci.appveyor.com/api/projects/status/9vbb7pp5p4uyoott/branch/master?svg=true)](https://ci.appveyor.com/project/p0deje/watir)
6
+ [![Unit Tests](https://github.com/titusfortner/watir/workflows/Unit%20Tests/badge.svg)](https://github.com/watir/watir/actions?query=workflow%3A%22Unit+Tests%22)
7
+ [![Mac Tests](https://github.com/titusfortner/watir/workflows/Mac%20Tests/badge.svg)](https://github.com/watir/watir/actions?query=workflow%3A%22Mac+Tests%22)
8
+ [![Windows Tests](https://github.com/titusfortner/watir/workflows/Windows%20Tests/badge.svg)](https://github.com/watir/watir/actions?query=workflow%3A%22Windows+Tests%22)
9
+ [![Linux Tests](https://github.com/titusfortner/watir/workflows/Linux%20Tests/badge.svg)](https://github.com/watir/watir/actions?query=workflow%3A%22Linux+Tests%22)
8
10
  [![Code Climate](https://codeclimate.com/github/watir/watir.svg)](https://codeclimate.com/github/watir/watir)
9
- [![Coverage Status](https://coveralls.io/repos/github/watir/watir/badge.svg?branch=master)](https://coveralls.io/github/watir/watir?branch=master)
11
+ [![Coverage Status](https://coveralls.io/repos/github/watir/watir/badge.svg?branch=main)](https://coveralls.io/github/watir/watir?branch=master)
10
12
 
11
13
  ## Using Watir
12
14
 
@@ -69,11 +71,11 @@ $ bundle exec rake svg:update
69
71
 
70
72
  ## Specs
71
73
 
72
- #### Travis CI
74
+ #### Github Actions
73
75
 
74
- Watir specs are run on [Travis CI](https://travis-ci.org/watir/watir).
76
+ Watir specs are run with [Github Actions](https://github.com/watir/watir/workflows).
75
77
 
76
- Watir code is tested with 2.3, 2.4 and 2.5 versions in multiple browsers and with multiple configurations.
78
+ Watir code is tested on Linux with latest versions of supported browsers and all active Ruby versions.
77
79
 
78
80
  #### Doctests
79
81
 
@@ -95,7 +97,7 @@ to ensure all paths in their code have tests associated with them.
95
97
 
96
98
  Watir is using [Rubocop](https://github.com/rubocop-hq/rubocop) to ensure a consistent style across the
97
99
  code base. It is run with our minimum supported Ruby version (2.3)
98
- We have some [established exceptions](https://github.com/watir/watir/blob/master/.rubocop.yml)
100
+ We have some [established exceptions](https://github.com/watir/watir/blob/main/.rubocop.yml)
99
101
  that might need to be tweaked for new code submissions. This can be addressed in the PR as necessary.
100
102
 
101
103
  #### Statistics
@@ -107,7 +109,4 @@ on wire calls.
107
109
 
108
110
  ## Copyright
109
111
 
110
- Copyright (c) 2009-2015 Jari Bakken
111
- Copyright (c) 2015-2018 Alex Rodionov, Titus Fortner
112
- Copyright (c) 2018 Justin Ko
113
112
  See LICENSE for details
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ Bundler::GemHelper.install_tasks
5
5
 
6
6
  require 'rspec/core/rake_task'
7
7
  RSpec::Core::RakeTask.new(:spec) do |spec|
8
- spec.rspec_opts = %w[--color --require fuubar --format Fuubar]
8
+ spec.rspec_opts = %w[--color --format doc]
9
9
  spec.pattern = 'spec/**/*_spec.rb'
10
10
  spec.exclude_pattern = 'spec/unit/**/*_spec.rb'
11
11
  end
@@ -61,7 +61,7 @@ end
61
61
 
62
62
  if extractor.errors.any?
63
63
  puts "\n\n<======================= ERRORS =======================>\n\n"
64
- puts extractor.errors.join("\n" + '=' * 80 + "\n")
64
+ puts extractor.errors.join("\n#{'=' * 80}\n")
65
65
  end
66
66
  end
67
67
 
@@ -93,7 +93,7 @@ YARD::Rake::YardocTask.new do |task|
93
93
  task.options = %w[--debug] # this is pretty slow, so nice with some output
94
94
  end
95
95
 
96
- require 'yard-doctest'
96
+ require 'yard/doctest/rake'
97
97
  YARD::Doctest::RakeTask.new do |task|
98
98
  task.doctest_opts = ['-v']
99
99
  end
@@ -115,7 +115,7 @@ namespace :changes do
115
115
 
116
116
  desc 'Print latest diff'
117
117
  task print: :differ do
118
- VersionDiffer.new.print_latest(STDOUT)
118
+ VersionDiffer.new.print_latest($stdout)
119
119
  end
120
120
  end
121
121
 
@@ -1,2 +1,2 @@
1
1
  require 'watir'
2
- warn Kernel.caller.first + ': `require "watir-webdriver"` is deprecated. Please, use `require "watir"`.'
2
+ warn "#{Kernel.caller.first}: `require 'watir-webdriver'` is deprecated. Please, use `require 'watir'`."
data/lib/watir.rb CHANGED
@@ -6,6 +6,7 @@ require 'watir/legacy_wait'
6
6
  require 'watir/wait'
7
7
  require 'watir/exception'
8
8
  require 'watir/window'
9
+ require 'watir/window_collection'
9
10
  require 'watir/has_window'
10
11
  require 'watir/adjacent'
11
12
  require 'watir/js_execution'
@@ -114,7 +115,6 @@ require 'watir/elements/element'
114
115
  require 'watir/elements/html_elements'
115
116
  require 'watir/elements/svg_elements'
116
117
 
117
- require 'watir/elements/area'
118
118
  require 'watir/elements/button'
119
119
  require 'watir/elements/cell'
120
120
  require 'watir/elements/checkbox'
@@ -113,16 +113,14 @@ module Watir
113
113
  def xpath_adjacent(opt = {})
114
114
  plural = opt.delete(:plural)
115
115
  opt[:index] ||= 0 unless plural || opt.values.any? { |e| e.is_a? Regexp }
116
- klass = if !plural && opt[:tag_name]
117
- Watir.element_class_for(opt[:tag_name])
118
- elsif !plural
119
- HTMLElement
120
- elsif opt[:tag_name]
121
- Object.const_get("#{Watir.element_class_for(opt[:tag_name])}Collection")
122
- else
123
- HTMLElementCollection
124
- end
125
- klass.new(self, opt)
116
+ if !plural
117
+ el = Watir.element_class_for(opt[:tag_name] || '').new(self, opt)
118
+ el.is_a?(Input) ? el.to_subtype : el
119
+ elsif opt[:tag_name]
120
+ Watir.const_get("#{Watir.element_class_for(opt[:tag_name])}Collection").new(self, opt)
121
+ else
122
+ HTMLElementCollection.new(self, opt)
123
+ end
126
124
  end
127
125
  end # Adjacent
128
126
  end # Watir
@@ -69,8 +69,8 @@ module Watir
69
69
  return unless @after_hooks.any? && !@browser.alert.exists?
70
70
 
71
71
  each { |after_hook| after_hook.call(@browser) }
72
- rescue Selenium::WebDriver::Error::NoSuchWindowError => ex
73
- Watir.logger.info "Could not execute After Hooks because browser window was closed #{ex}"
72
+ rescue Selenium::WebDriver::Error::NoSuchWindowError => e
73
+ Watir.logger.info "Could not execute After Hooks because browser window was closed #{e}"
74
74
  end
75
75
 
76
76
  #
@@ -99,8 +99,8 @@ module Watir
99
99
  # @yieldparam [#call] after_hook Object responding to call
100
100
  #
101
101
 
102
- def each
103
- @after_hooks.each { |after_hook| yield after_hook }
102
+ def each(&blk)
103
+ @after_hooks.each { |after_hook| blk.call(after_hook) }
104
104
  end
105
105
 
106
106
  #
data/lib/watir/alert.rb CHANGED
@@ -84,6 +84,7 @@ module Watir
84
84
  false
85
85
  end
86
86
  alias present? exists?
87
+ alias exist? exists?
87
88
 
88
89
  #
89
90
  # @api private
@@ -46,6 +46,8 @@ module Watir
46
46
  # @return [$1] value of $3 property
47
47
  #
48
48
  def attribute(type, method, attr)
49
+ return if method_defined?(method)
50
+
49
51
  typed_attributes[type] << [method, attr]
50
52
  define_attribute(type, method, attr)
51
53
  end
data/lib/watir/browser.rb CHANGED
@@ -11,7 +11,7 @@ module Watir
11
11
  include Exception
12
12
  include Scrolling
13
13
 
14
- attr_writer :default_context, :original_window, :locator_namespace
14
+ attr_writer :default_context, :original_window, :locator_namespace, :timer
15
15
  attr_reader :driver, :after_hooks
16
16
  alias wd driver # ensures duck typing with Watir::Element
17
17
 
@@ -213,11 +213,11 @@ module Watir
213
213
  # @param args Arguments will be available in the given script in the 'arguments' pseudo-array
214
214
  #
215
215
 
216
- def execute_script(script, *args)
216
+ def execute_script(script, *args, function_name: nil)
217
217
  args.map! do |e|
218
218
  e.is_a?(Element) ? e.wait_until(&:exists?).wd : e
219
219
  end
220
-
220
+ Watir.logger.info "Executing Script on Browser: #{function_name}" if function_name
221
221
  wrap_elements_in(self, @driver.execute_script(script, *args))
222
222
  end
223
223
 
@@ -300,6 +300,10 @@ module Watir
300
300
  end
301
301
  end
302
302
 
303
+ def timer
304
+ @timer ||= Wait::Timer.new
305
+ end
306
+
303
307
  private
304
308
 
305
309
  def wrap_element(scope, element)
@@ -25,7 +25,11 @@ module Watir
25
25
 
26
26
  def process_arguments
27
27
  url = @options.delete(:url)
28
- @selenium_opts[:url] = url if url
28
+ if url
29
+ @selenium_opts[:url] = url
30
+ elsif @options.key?(:service)
31
+ @selenium_opts[:service] = options.delete(:service)
32
+ end
29
33
 
30
34
  create_http_client
31
35
 
@@ -63,7 +67,6 @@ module Watir
63
67
  end
64
68
 
65
69
  # TODO: - this will get addressed with Capabilities Update
66
- # rubocop:disable Metrics/AbcSize
67
70
  # rubocop:disable Metrics/MethodLength
68
71
  # rubocop:disable Metrics/PerceivedComplexity:
69
72
  # rubocop:disable Metrics/CyclomaticComplexity::
@@ -82,7 +85,7 @@ module Watir
82
85
  browser_options[:args] += ['--headless', '--disable-gpu']
83
86
  end
84
87
  @selenium_opts[:options] = browser_options if browser_options.is_a? Selenium::WebDriver::Chrome::Options
85
- @selenium_opts[:options] ||= Selenium::WebDriver::Chrome::Options.new(browser_options)
88
+ @selenium_opts[:options] ||= Selenium::WebDriver::Chrome::Options.new(**browser_options)
86
89
  when :firefox
87
90
  profile = @options.delete(:profile)
88
91
  if browser_options.is_a? Selenium::WebDriver::Firefox::Options
@@ -97,7 +100,7 @@ module Watir
97
100
  browser_options[:args] ||= []
98
101
  browser_options[:args] += ['--headless']
99
102
  end
100
- @selenium_opts[:options] ||= Selenium::WebDriver::Firefox::Options.new(browser_options)
103
+ @selenium_opts[:options] ||= Selenium::WebDriver::Firefox::Options.new(**browser_options)
101
104
  @selenium_opts[:options].profile = profile if profile
102
105
  when :safari
103
106
  Selenium::WebDriver::Safari.technology_preview! if @options.delete(:technology_preview)
@@ -120,13 +123,13 @@ module Watir
120
123
  end
121
124
  unless browser_options.is_a? Selenium::WebDriver::IE::Options
122
125
  ie_caps = browser_options.select { |k| Selenium::WebDriver::IE::Options::CAPABILITIES.include?(k) }
123
- browser_options = Selenium::WebDriver::IE::Options.new(browser_options)
126
+ browser_options = Selenium::WebDriver::IE::Options.new(**browser_options)
124
127
  ie_caps.each { |k, v| browser_options.add_option(k, v) }
125
128
  end
126
129
  @selenium_opts[:options] = browser_options
127
130
  end
128
131
  end
129
- # rubocop:enable Metrics/AbcSize
132
+
130
133
  # rubocop:enable Metrics/MethodLength
131
134
  # rubocop:enable Metrics/PerceivedComplexity:
132
135
  # rubocop:enable Metrics/CyclomaticComplexity::
data/lib/watir/cookies.rb CHANGED
@@ -61,7 +61,7 @@ module Watir
61
61
  cookie[:path] = opts[:path] if opts.key?(:path)
62
62
  expires = opts[:expires]
63
63
  if expires
64
- cookie[:expires] = ::Time.parse(expires) if expires.is_a?(String) else expires
64
+ cookie[:expires] = expires.is_a?(String) ? ::Time.parse(expires) : expires
65
65
  end
66
66
  cookie[:domain] = opts[:domain] if opts.key?(:domain)
67
67
 
@@ -105,6 +105,8 @@ module Watir
105
105
  IO.write(file, to_a.to_yaml)
106
106
  end
107
107
 
108
+ #
109
+ # TODO: Use :permitted_classes keyword when minimum supported Ruby is 2.6
108
110
  #
109
111
  # Load cookies from file
110
112
  #
@@ -7,17 +7,19 @@ module Watir
7
7
  include Enumerable
8
8
  include Exception
9
9
  include JSSnippets
10
+ include Waitable
10
11
  include Locators::ClassHelpers
11
12
 
12
13
  def initialize(query_scope, selector)
13
14
  @query_scope = query_scope
14
15
  @selector = selector
16
+ @to_a = nil
15
17
 
16
18
  build unless @selector.key?(:element)
17
19
  end
18
20
 
19
21
  #
20
- # Yields each element in collection.
22
+ # Relocates elements then yields each element in resulting collection.
21
23
  #
22
24
  # @example
23
25
  # divs = browser.divs(class: 'kls')
@@ -29,6 +31,7 @@ module Watir
29
31
  #
30
32
 
31
33
  def each(&blk)
34
+ reset!
32
35
  to_a.each(&blk)
33
36
  end
34
37
 
@@ -37,6 +40,9 @@ module Watir
37
40
 
38
41
  alias empty? none?
39
42
 
43
+ alias exist? any?
44
+ alias exists? any?
45
+
40
46
  def build
41
47
  selector_builder.build(@selector.dup)
42
48
  end
@@ -145,14 +151,19 @@ module Watir
145
151
  alias eql? ==
146
152
 
147
153
  #
148
- # Creates a Collection containing elements of two collections.
154
+ # Removes cache of previously located elements in the collection.
149
155
  #
150
156
  # @example
151
157
  # options = browser.select_list(name: "new_user_languages").options
152
- # (options + browser.select_list(id: "new_user_role").options).size
153
- # #=> 8
158
+ # options.reset!
159
+ # options[0]
160
+ # #=> nil
154
161
  #
155
162
 
163
+ def reset!
164
+ @to_a = nil
165
+ end
166
+
156
167
  private
157
168
 
158
169
  def elements
@@ -182,7 +193,11 @@ module Watir
182
193
  end
183
194
 
184
195
  def ensure_context
185
- @query_scope.locate if @query_scope.is_a?(Browser) || @query_scope.located? && @query_scope.stale?
196
+ if @query_scope.is_a?(Browser) || !@query_scope.located? && @query_scope.is_a?(IFrame)
197
+ @query_scope.browser.locate
198
+ elsif @query_scope.located? && @query_scope.stale?
199
+ @query_scope.locate
200
+ end
186
201
  @query_scope.switch_to! if @query_scope.is_a?(IFrame)
187
202
  end
188
203
 
@@ -191,7 +206,7 @@ module Watir
191
206
  end
192
207
 
193
208
  def element_class
194
- Kernel.const_get(self.class.name.sub(/Collection$/, ''))
209
+ Watir.const_get(self.class.name.sub(/Collection$/, ''))
195
210
  end
196
211
 
197
212
  def construct_subtype(element, hash, tag_name)
@@ -18,6 +18,17 @@ module Watir
18
18
  attr_accessor :keyword
19
19
  attr_reader :selector
20
20
 
21
+ # https://www.w3.org/TR/html52/single-page.html#casesensitivity
22
+ CASE_INSENSITIVE_ATTRIBUTES = %i[accept accept_charset align alink axis
23
+ bgcolor charset checked clear codetype
24
+ color compact declare defer dir direction
25
+ disabled enctype face frame hreflang
26
+ http_equiv lang language link media
27
+ method multiple nohref noresize noshade
28
+ nowrap readonly rel rev rules scope
29
+ scrolling selected shape target text
30
+ type valign valuetype vlink].freeze
31
+
21
32
  #
22
33
  # temporarily add :id and :class_name manually since they're no longer specified in the HTML spec.
23
34
  #
@@ -54,12 +65,9 @@ module Watir
54
65
 
55
66
  def exists?
56
67
  if located? && stale?
57
- Watir.logger.deprecate 'Checking `#exists? == false` to determine a stale element',
58
- '`#stale? == true`',
59
- reference: 'http://watir.com/staleness-changes',
60
- ids: [:stale_exists]
61
- # TODO: Change this to `reset!` after removing deprecation
62
- return false
68
+ reset!
69
+ elsif located?
70
+ return true
63
71
  end
64
72
 
65
73
  assert_exists
@@ -141,7 +149,6 @@ module Watir
141
149
  #
142
150
 
143
151
  def click(*modifiers)
144
- # TODO: Should wait_for_enabled be default, or `Button` specific behavior?
145
152
  element_call(:wait_for_enabled) do
146
153
  if modifiers.any?
147
154
  action = driver.action
@@ -196,15 +203,35 @@ module Watir
196
203
  end
197
204
 
198
205
  #
199
- # Right clicks the element.
200
- # Note that browser support may vary.
206
+ # Right clicks the element, optionally while pressing the given modifier keys.
207
+ # Note that support for holding a modifier key is currently experimental,
208
+ # and may not work at all. Also, the browser support may vary.
201
209
  #
202
210
  # @example
203
211
  # browser.element(name: "new_user_button").right_click
204
212
  #
213
+ # @example Right click an element with shift key pressed
214
+ # browser.element(name: "new_user_button").right_click(:shift)
215
+ #
216
+ # @example Click an element with several modifier keys pressed
217
+ # browser.element(name: "new_user_button").right_click(:shift, :alt)
218
+ #
219
+ # @param [:shift, :alt, :control, :command, :meta] modifiers to press while right clicking.
220
+ #
221
+
222
+ def right_click(*modifiers)
223
+ element_call(:wait_for_present) do
224
+ action = driver.action
225
+ if modifiers.any?
226
+ modifiers.each { |mod| action.key_down mod }
227
+ action.context_click(@element)
228
+ modifiers.each { |mod| action.key_up mod }
229
+ action.perform
230
+ else
231
+ action.context_click(@element).perform
232
+ end
233
+ end
205
234
 
206
- def right_click
207
- element_call(:wait_for_present) { driver.action.context_click(@element).perform }
208
235
  browser.after_hooks.run
209
236
  end
210
237
 
@@ -247,7 +274,7 @@ module Watir
247
274
  # Note that browser support may vary.
248
275
  #
249
276
  # @example
250
- # browser.div(id: "draggable").drag_and_drop_by 100, -200
277
+ # browser.div(id: "draggable").drag_and_drop_by 100, 25
251
278
  #
252
279
  # @param [Integer] right_by
253
280
  # @param [Integer] down_by
@@ -358,6 +385,8 @@ module Watir
358
385
  #
359
386
 
360
387
  def scroll_into_view
388
+ Watir.logger.deprecate 'Element#scroll_into_view', 'Element#scroll methods', ids: [:scroll_into_view]
389
+
361
390
  element_call { @element.location_once_scrolled_into_view }
362
391
  end
363
392
 
@@ -460,16 +489,7 @@ module Watir
460
489
  msg = '#visible? behavior will be changing slightly, consider switching to #present? ' \
461
490
  '(more details: http://watir.com/element-existentialism/)'
462
491
  Watir.logger.warn msg, ids: [:visible_element]
463
- displayed = display_check
464
- if displayed.nil? && display_check
465
- Watir.logger.deprecate 'Checking `#visible? == false` to determine a stale element',
466
- '`#stale? == true`',
467
- reference: 'http://watir.com/staleness-changes',
468
- ids: [:stale_visible]
469
- end
470
- raise unknown_exception if displayed.nil?
471
-
472
- displayed
492
+ display_check
473
493
  end
474
494
 
475
495
  #
@@ -492,14 +512,7 @@ module Watir
492
512
  #
493
513
 
494
514
  def present?
495
- displayed = display_check
496
- if displayed.nil? && display_check
497
- Watir.logger.deprecate 'Checking `#present? == false` to determine a stale element',
498
- '`#stale? == true`',
499
- reference: 'http://watir.com/staleness-changes',
500
- ids: [:stale_present]
501
- end
502
- displayed
515
+ display_check
503
516
  rescue UnknownObjectException, UnknownFrameException
504
517
  false
505
518
  end
@@ -547,7 +560,7 @@ module Watir
547
560
  #
548
561
 
549
562
  def to_subtype
550
- tag = tag_name()
563
+ tag = tag_name
551
564
  klass = if tag == 'input'
552
565
  case attribute_value(:type)
553
566
  when 'checkbox'
@@ -595,7 +608,7 @@ module Watir
595
608
  def stale_in_context?
596
609
  @element.css_value('staleness_check') # any wire call will check for staleness
597
610
  false
598
- rescue Selenium::WebDriver::Error::ObsoleteElementError
611
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError
599
612
  true
600
613
  end
601
614
 
@@ -686,7 +699,7 @@ module Watir
686
699
  return assert_enabled unless Watir.relaxed_locate?
687
700
 
688
701
  wait_for_exists
689
- return unless [Input, Button, Select, Option].any? { |c| is_a? c } || @content_editable
702
+ return unless [Input, Button, Select, Option].any? { |c| is_a? c } || content_editable
690
703
  return if enabled?
691
704
 
692
705
  begin
@@ -698,9 +711,7 @@ module Watir
698
711
 
699
712
  def wait_for_writable
700
713
  wait_for_enabled
701
- unless Watir.relaxed_locate?
702
- raise_writable unless !respond_to?(:readonly?) || !readonly?
703
- end
714
+ raise_writable unless Watir.relaxed_locate? || (!respond_to?(:readonly?) || !readonly?)
704
715
 
705
716
  return if !respond_to?(:readonly?) || !readonly?
706
717
 
@@ -720,7 +731,11 @@ module Watir
720
731
  end
721
732
 
722
733
  def ensure_context
723
- @query_scope.locate if @query_scope.is_a?(Browser) || @query_scope.located? && @query_scope.stale?
734
+ if @query_scope.is_a?(Browser) || !@query_scope.located? && @query_scope.is_a?(IFrame)
735
+ @query_scope.browser.locate
736
+ elsif @query_scope.located? && @query_scope.stale?
737
+ @query_scope.locate
738
+ end
724
739
  @query_scope.switch_to! if @query_scope.is_a?(IFrame)
725
740
  end
726
741
 
@@ -770,54 +785,52 @@ module Watir
770
785
  @element.displayed?
771
786
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
772
787
  reset!
773
- nil
788
+ retry
774
789
  end
775
790
 
776
791
  # TODO: - this will get addressed with Watir::Executor implementation
777
792
  # rubocop:disable Metrics/AbcSize
778
793
  # rubocop:disable Metrics/MethodLength
779
- # rubocop:disable Metrics/PerceivedComplexity
780
794
  # rubocop:disable Metrics/CyclomaticComplexity:
795
+ # rubocop:disable Metrics/PerceivedComplexity::
781
796
  def element_call(precondition = nil, &block)
782
797
  caller = caller_locations(1, 1)[0].label
783
- already_locked = Wait.timer.locked?
784
- Wait.timer = Wait::Timer.new(timeout: Watir.default_timeout) unless already_locked
798
+ already_locked = browser.timer.locked?
799
+ browser.timer = Wait::Timer.new(timeout: Watir.default_timeout) unless already_locked
785
800
 
786
801
  begin
787
802
  check_condition(precondition, caller)
788
803
  Watir.logger.debug "-> `Executing #{inspect}##{caller}`"
789
804
  yield
790
- rescue unknown_exception => ex
805
+ rescue unknown_exception => e
791
806
  element_call(:wait_for_exists, &block) if precondition.nil?
792
- msg = ex.message
807
+ msg = e.message
793
808
  msg += '; Maybe look in an iframe?' if @query_scope.iframe.exists?
794
- custom_attributes = @locator.nil? ? [] : selector_builder.custom_attributes
809
+ custom_attributes = !defined?(@locator) || @locator.nil? ? [] : selector_builder.custom_attributes
795
810
  unless custom_attributes.empty?
796
811
  msg += "; Watir treated #{custom_attributes} as a non-HTML compliant attribute, ensure that was intended"
797
812
  end
798
813
  raise unknown_exception, msg
799
- rescue Selenium::WebDriver::Error::StaleElementReferenceError
814
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::NoSuchElementError
800
815
  reset!
801
816
  retry
802
- rescue Selenium::WebDriver::Error::ElementNotVisibleError, Selenium::WebDriver::Error::ElementNotInteractableError
803
- raise_present unless Wait.timer.remaining_time.positive?
817
+ # TODO: - InvalidElementStateError is deprecated, so no longer calling `raise_disabled`
818
+ # need a better way to handle this
819
+ rescue Selenium::WebDriver::Error::ElementNotInteractableError
820
+ raise_present unless browser.timer.remaining_time.positive?
804
821
  raise_present unless %i[wait_for_present wait_for_enabled wait_for_writable].include?(precondition)
805
822
  retry
806
- rescue Selenium::WebDriver::Error::InvalidElementStateError
807
- raise_disabled unless Wait.timer.remaining_time.positive?
808
- raise_disabled unless %i[wait_for_present wait_for_enabled wait_for_writable].include?(precondition)
809
- retry
810
823
  rescue Selenium::WebDriver::Error::NoSuchWindowError
811
824
  raise NoMatchingWindowFoundException, 'browser window was closed'
812
825
  ensure
813
826
  Watir.logger.debug "<- `Completed #{inspect}##{caller}`"
814
- Wait.timer.reset! unless already_locked
827
+ browser.timer.reset! unless already_locked
815
828
  end
816
829
  end
817
830
  # rubocop:enable Metrics/AbcSize
818
831
  # rubocop:enable Metrics/MethodLength
832
+ # rubocop:enable Metrics/CyclomaticComplexity
819
833
  # rubocop:enable Metrics/PerceivedComplexity
820
- # rubocop:enable Metrics/CyclomaticComplexity:
821
834
 
822
835
  def check_condition(condition, caller)
823
836
  Watir.logger.debug "<- `Verifying precondition #{inspect}##{condition} for #{caller}`"