watir 7.0.0.beta1 → 7.0.0.beta5

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +7 -3
  3. data/.rubocop.yml +2 -7
  4. data/CHANGES.md +32 -0
  5. data/lib/watir/browser.rb +21 -7
  6. data/lib/watir/capabilities.rb +52 -7
  7. data/lib/watir/elements/date_field.rb +4 -1
  8. data/lib/watir/elements/date_time_field.rb +4 -1
  9. data/lib/watir/elements/element.rb +32 -3
  10. data/lib/watir/elements/font.rb +1 -0
  11. data/lib/watir/elements/iframe.rb +0 -1
  12. data/lib/watir/elements/radio.rb +2 -2
  13. data/lib/watir/elements/select.rb +63 -40
  14. data/lib/watir/has_window.rb +2 -0
  15. data/lib/watir/locators.rb +4 -0
  16. data/lib/watir/locators/element/matcher.rb +1 -1
  17. data/lib/watir/locators/element/selector_builder.rb +0 -3
  18. data/lib/watir/locators/element/selector_builder/xpath.rb +2 -1
  19. data/lib/watir/locators/option/matcher.rb +24 -0
  20. data/lib/watir/locators/option/selector_builder.rb +8 -0
  21. data/lib/watir/locators/option/selector_builder/xpath.rb +37 -0
  22. data/lib/watir/logger.rb +3 -74
  23. data/lib/watir/radio_set.rb +1 -0
  24. data/lib/watir/screenshot.rb +2 -8
  25. data/lib/watir/user_editable.rb +10 -3
  26. data/lib/watir/version.rb +1 -1
  27. data/lib/watir/window.rb +15 -4
  28. data/lib/watir/window_collection.rb +9 -0
  29. data/lib/watirspec.rb +4 -2
  30. data/lib/watirspec/guards.rb +1 -1
  31. data/lib/watirspec/remote_server.rb +2 -6
  32. data/lib/watirspec/server.rb +1 -1
  33. data/spec/spec_helper.rb +0 -10
  34. data/spec/unit/capabilities_spec.rb +198 -48
  35. data/spec/unit/match_elements/element_spec.rb +11 -0
  36. data/spec/watirspec/after_hooks_spec.rb +22 -45
  37. data/spec/watirspec/browser_spec.rb +185 -206
  38. data/spec/watirspec/cookies_spec.rb +47 -52
  39. data/spec/watirspec/drag_and_drop_spec.rb +5 -7
  40. data/spec/watirspec/elements/area_spec.rb +1 -5
  41. data/spec/watirspec/elements/button_spec.rb +4 -8
  42. data/spec/watirspec/elements/checkbox_spec.rb +2 -4
  43. data/spec/watirspec/elements/date_field_spec.rb +13 -16
  44. data/spec/watirspec/elements/date_time_field_spec.rb +14 -13
  45. data/spec/watirspec/elements/dd_spec.rb +3 -4
  46. data/spec/watirspec/elements/del_spec.rb +10 -12
  47. data/spec/watirspec/elements/div_spec.rb +41 -50
  48. data/spec/watirspec/elements/dl_spec.rb +4 -12
  49. data/spec/watirspec/elements/element_spec.rb +155 -89
  50. data/spec/watirspec/elements/elements_spec.rb +8 -9
  51. data/spec/watirspec/elements/filefield_spec.rb +5 -7
  52. data/spec/watirspec/elements/form_spec.rb +1 -1
  53. data/spec/watirspec/elements/forms_spec.rb +3 -5
  54. data/spec/watirspec/elements/frame_spec.rb +17 -22
  55. data/spec/watirspec/elements/iframe_spec.rb +21 -27
  56. data/spec/watirspec/elements/ins_spec.rb +10 -12
  57. data/spec/watirspec/elements/link_spec.rb +24 -26
  58. data/spec/watirspec/elements/links_spec.rb +8 -9
  59. data/spec/watirspec/elements/radio_spec.rb +11 -14
  60. data/spec/watirspec/elements/select_list_spec.rb +248 -117
  61. data/spec/watirspec/elements/span_spec.rb +10 -12
  62. data/spec/watirspec/elements/table_nesting_spec.rb +31 -34
  63. data/spec/watirspec/elements/table_spec.rb +11 -13
  64. data/spec/watirspec/elements/tbody_spec.rb +10 -12
  65. data/spec/watirspec/elements/td_spec.rb +4 -6
  66. data/spec/watirspec/elements/text_field_spec.rb +10 -12
  67. data/spec/watirspec/elements/tr_spec.rb +5 -7
  68. data/spec/watirspec/support/rspec_matchers.rb +1 -1
  69. data/spec/watirspec/user_editable_spec.rb +26 -28
  70. data/spec/watirspec/wait_spec.rb +255 -258
  71. data/spec/watirspec/window_switching_spec.rb +199 -200
  72. data/spec/watirspec_helper.rb +34 -31
  73. data/watir.gemspec +1 -1
  74. metadata +7 -8
  75. data/spec/implementation_spec.rb +0 -24
  76. data/spec/unit/logger_spec.rb +0 -81
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 994057876b1f2ad97972dfe3a43f139cf761aefabcacbc7ab1d2bc659693e70e
4
- data.tar.gz: d0bcac18f0cfe6d9d2a0258bb57a8e2e9f0804feda5eeaf8e5d840fd859de3c4
3
+ metadata.gz: af4906be1ae9d2713b91037006f964eddcac08ccb7a3cc341752132dea109b0f
4
+ data.tar.gz: 52ece209164c1ff75eeaec71efe8c70f03dd27f4fb1c791761e77c9ac600b333
5
5
  SHA512:
6
- metadata.gz: b652878ab0a6b4365e3226a664945a86c96f8f94c6b0cd4744c0809a66d8fb49ff37951726699df217856d413d4104778164218ca365a1481ec6ab1212c2f12e
7
- data.tar.gz: f6f5f093a6072f1456055853d2b93ce994b978897668eb3348ee6fe70fa872eea681c97481d254830f5d028cce0029bb9e45a5dd25d50d785a6e144592d429a2
6
+ metadata.gz: 5623e2ac22fd67b0c7f147333ed0c0e8ce3b87cf66ecb8d40a3f85672ce647d480bfe0114986a83d1a0bdc59510ad2d164c046769eea5c44a12252b3578a4d23
7
+ data.tar.gz: 9e95f5db40c379040dabb329c64dc4032c9c290193c3804e401cbfaabecec9550fa524c50e6483a9b597240a99f542549c1b823d91c99128dbab16b44f1c4d39
@@ -61,13 +61,17 @@ jobs:
61
61
  task: [ chrome, firefox, edge ]
62
62
  include:
63
63
  - os: 'macos-latest'
64
- task: 'safari'
65
64
  ruby: 2.6
65
+ task: 'safari'
66
66
  - os: 'macos-latest'
67
+ ruby: 3.0
67
68
  task: 'safari'
69
+ - os: 'windows-latest'
70
+ ruby: 2.6
71
+ task: 'ie'
72
+ - os: 'windows-latest'
68
73
  ruby: 3.0
69
- # - os: 'windows-latest'
70
- # task: 'ie'
74
+ task: 'ie'
71
75
  exclude:
72
76
  - os: 'ubuntu-latest'
73
77
  task: 'edge'
data/.rubocop.yml CHANGED
@@ -19,17 +19,12 @@ Lint/UnifiedInteger:
19
19
 
20
20
  # Default: 17
21
21
  Metrics/AbcSize:
22
- Max: 20
22
+ Max: 22
23
23
  Exclude:
24
- - 'lib/watir/capabilities.rb'
25
24
  - 'lib/watir/locators/element/selector_builder.rb'
26
- - 'lib/watir/locators/element/selector_builder/regexp_disassembler.rb'
27
- - 'lib/watir/locators/element/selector_builder/xpath.rb'
28
- - 'lib/watir/locators/element/locator.rb'
25
+ - 'lib/watir/locators/element/selector_builder/*.rb'
29
26
  - 'lib/watir/generator/base/generator.rb'
30
- - 'lib/watir/generator/base/visitor.rb'
31
27
  - 'spec/locator_spec_helper.rb'
32
- - 'spec/watirspec_helper.rb'
33
28
 
34
29
  Metrics/BlockLength:
35
30
  Exclude:
data/CHANGES.md CHANGED
@@ -1,3 +1,35 @@
1
+ ### 7.0.0.beta5 (2021-08-02)
2
+
3
+ * Add support for passing in Proxy and proxy Hash to Capabilities (#933)
4
+ * Trigger change event when setting values on date and date-time fields (#938)
5
+ * Allow user to obtain Capabilities instance from Browser instance
6
+
7
+ ### 7.0.0.beta4 (2021-05-29)
8
+
9
+ * Fix Bug in using negative class names within a collection (#934)
10
+
11
+ ### 7.0.0.beta3 (2021-05-05)
12
+
13
+ * Fix Bug preventing proper use of vendor extension capabilities
14
+ * Changed how timeouts are supported in Watir Capabilities (#932)
15
+ * Changed the default Alert Behavior not to automatically get dismissed when an exception happens (#931)
16
+
17
+ ### 7.0.0.beta2 (2021-03-28)
18
+
19
+ * Replace Watir Logger implementation with Selenium Logger subclass
20
+ * Change Watir Guards to use Selenium's new Guards. Tests run as pending when guarded.
21
+ * Implement `#set` as standard interface for each Input Element (#405)
22
+ * Implement `Element#set` to take correct `#set` behavior based on evaluated element (#664)
23
+ * Optimize Performance for Select Lists (#846)
24
+ * Allow user to set values on Select List exclusively by `:label`, `:text`, or `:value` (#846)
25
+ * Allow user to check if option selected in Select List by `:label`, `:text`, or `:value` (#929)
26
+ * Implement `Window#restore!` to return to original Window and close all others (#923)
27
+ * Minor performance improvement for iterating over windows (#923)
28
+ * Implement `Browser#closed?`; same as `Browser#exists?` without the Windows checks (#923)
29
+ * Update methods that use Selenium's Actions class to scroll element into view before acting (#847)
30
+ * Fix bug for `:text` locator with `Regexp` value based on whitespace (#924)
31
+ * Remove executing after hooks when changing frames (#888)
32
+
1
33
  ### 7.0.0.beta1 (2021-03-18)
2
34
 
3
35
  * Requires Selenium 4
data/lib/watir/browser.rb CHANGED
@@ -12,7 +12,7 @@ module Watir
12
12
  include Scrolling
13
13
 
14
14
  attr_writer :default_context, :original_window, :locator_namespace, :timer
15
- attr_reader :driver, :after_hooks
15
+ attr_reader :driver, :after_hooks, :capabilities
16
16
  alias wd driver # ensures duck typing with Watir::Element
17
17
 
18
18
  class << self
@@ -42,8 +42,8 @@ module Watir
42
42
  def initialize(browser = :chrome, *args)
43
43
  case browser
44
44
  when ::Symbol, String
45
- selenium_args = Capabilities.new(browser, *args).to_args
46
- @driver = Selenium::WebDriver.for(*selenium_args)
45
+ @capabilities = Capabilities.new(browser, *args)
46
+ @driver = Selenium::WebDriver.for(*@capabilities.to_args)
47
47
  when Selenium::WebDriver::Driver
48
48
  @driver = browser
49
49
  else
@@ -55,16 +55,21 @@ module Watir
55
55
  @default_context = true
56
56
  end
57
57
 
58
+ # rubocop:disable Metrics/AbcSize
59
+ # TODO: w3c default behavior does not like checking if alert exists
58
60
  def inspect
59
61
  if alert.exists?
60
62
  format('#<%s:0x%x alert=true>', self.class, hash * 2)
61
63
  else
62
64
  format('#<%s:0x%x url=%s title=%s>', self.class, hash * 2, url.inspect, title.inspect)
63
65
  end
66
+ rescue Selenium::WebDriver::Error::NoSuchWindowError
67
+ format('#<%s:0x%x closed=%s>', self.class, hash * 2, closed?)
64
68
  rescue Errno::ECONNREFUSED
65
69
  format('#<%s:0x%x closed=true>', self.class, hash * 2)
66
70
  end
67
71
  alias selector_string inspect
72
+ # rubocop:enable Metrics/AbcSize
68
73
 
69
74
  #
70
75
  # Returns URL of current page.
@@ -101,13 +106,23 @@ module Watir
101
106
  #
102
107
 
103
108
  def close
104
- return if @closed
109
+ return if closed?
105
110
 
106
111
  @driver.quit
107
112
  @closed = true
108
113
  end
109
114
  alias quit close
110
115
 
116
+ #
117
+ # Returns true if browser is closed and false otherwise.
118
+ #
119
+ # @return [Boolean]
120
+ #
121
+
122
+ def closed?
123
+ @closed
124
+ end
125
+
111
126
  #
112
127
  # Handles cookies.
113
128
  #
@@ -252,12 +267,12 @@ module Watir
252
267
  #
253
268
 
254
269
  def exist?
255
- !@closed && window.present?
270
+ !closed? && window.present?
256
271
  end
257
272
  alias exists? exist?
258
273
 
259
274
  def locate
260
- raise Error, 'browser was closed' if @closed
275
+ raise Error, 'browser was closed' if closed?
261
276
 
262
277
  ensure_context
263
278
  end
@@ -267,7 +282,6 @@ module Watir
267
282
 
268
283
  driver.switch_to.default_content
269
284
  @default_context = true
270
- after_hooks.run
271
285
  end
272
286
 
273
287
  def browser
@@ -55,7 +55,7 @@ module Watir
55
55
  http_client
56
56
  when Selenium::WebDriver::Remote::Http::Common
57
57
  Watir.logger.warn 'Check out the new Watir::HttpClient and let us know if there are missing features you need',
58
- ids: [:watir_client]
58
+ id: [:watir_client]
59
59
  http_client
60
60
  else
61
61
  raise TypeError, ':http_client must be a Hash or a Selenium HTTP Client instance'
@@ -63,15 +63,60 @@ module Watir
63
63
  end
64
64
 
65
65
  def process_browser_options
66
- browser_options = @options.delete(:options) || {}
67
-
68
- options = browser_options if browser_options.is_a? Selenium::WebDriver::Options
69
- options ||= Selenium::WebDriver::Options.send(@browser, **browser_options)
70
-
66
+ browser_options = @options.delete(:options).dup || {}
67
+ vendor_caps = process_vendor_capabilities(browser_options)
68
+
69
+ options = if browser_options.is_a? Selenium::WebDriver::Options
70
+ browser_options
71
+ else
72
+ convert_timeouts(browser_options)
73
+ Selenium::WebDriver::Options.send(@browser, **browser_options)
74
+ end
75
+
76
+ options.unhandled_prompt_behavior ||= :ignore
77
+ process_proxy_options(options)
71
78
  browser_specific_options(options)
72
79
  raise ArgumentError, "#{@options} are unrecognized arguments for Browser constructor" unless @options.empty?
73
80
 
74
- options
81
+ vendor_caps << options
82
+ end
83
+
84
+ def process_proxy_options(options)
85
+ proxy = @options.delete(:proxy)
86
+ return if proxy.nil?
87
+
88
+ proxy &&= Selenium::WebDriver::Proxy.new(proxy) if proxy.is_a?(Hash)
89
+
90
+ unless proxy.is_a?(Selenium::WebDriver::Proxy)
91
+ raise TypeError, "#{proxy} needs to be Selenium Proxy or Hash instance"
92
+ end
93
+
94
+ options.proxy = proxy
95
+ end
96
+
97
+ def process_vendor_capabilities(opts)
98
+ return [] unless opts.is_a? Hash
99
+
100
+ vendor = opts.select { |key, _val| key.to_s.include?(':') && opts.delete(key) }
101
+ vendor.map { |k, v| Selenium::WebDriver::Remote::Capabilities.new(k => v) }
102
+ end
103
+
104
+ def convert_timeouts(browser_options)
105
+ browser_options[:timeouts] ||= {}
106
+ browser_options[:timeouts].keys.each do |key|
107
+ raise(ArgumentError, 'do not set implicit wait, Watir handles waiting automatically') if key.to_s == 'implicit'
108
+
109
+ Watir.logger.deprecate('using timeouts directly in options',
110
+ ":#{key}_timeout",
111
+ id: 'timeouts')
112
+ end
113
+ if browser_options.key?(:page_load_timeout)
114
+ browser_options[:timeouts][:page_load] = browser_options.delete(:page_load_timeout) * 1000
115
+ end
116
+
117
+ return unless browser_options.key?(:script_timeout)
118
+
119
+ browser_options[:timeouts][:script] = browser_options.delete(:script_timeout) * 1000
75
120
  end
76
121
 
77
122
  def browser_specific_options(options)
@@ -11,7 +11,10 @@ module Watir
11
11
  raise ArgumentError, message unless [Date, ::Time].include?(date.class)
12
12
 
13
13
  date_string = date.strftime('%Y-%m-%d')
14
- element_call(:wait_for_writable) { execute_js(:setValue, @element, date_string) }
14
+ element_call(:wait_for_writable) do
15
+ execute_js(:setValue, @element, date_string)
16
+ execute_js(:fireEvent, @element, :change)
17
+ end
15
18
  end
16
19
  alias set set!
17
20
  alias value= set
@@ -11,7 +11,10 @@ module Watir
11
11
  raise ArgumentError, message unless [DateTime, ::Time].include?(date.class)
12
12
 
13
13
  date_time_string = date.strftime('%Y-%m-%dT%H:%M')
14
- element_call(:wait_for_writable) { execute_js(:setValue, @element, date_time_string) }
14
+ element_call(:wait_for_writable) do
15
+ execute_js(:setValue, @element, date_time_string)
16
+ execute_js(:fireEvent, @element, :change)
17
+ end
15
18
  end
16
19
  alias set set!
17
20
  alias value= set
@@ -163,6 +163,26 @@ module Watir
163
163
  browser.after_hooks.run
164
164
  end
165
165
 
166
+ #
167
+ # Determines the correct action based on subtype and takes it.
168
+ # Default is to click element
169
+ #
170
+
171
+ def set(*args)
172
+ subtype = to_subtype
173
+ if subtype.is_a?(Radio) && [String, Regexp].include?(args.first.class)
174
+ RadioSet.new(@query_scope, selector).set(*args)
175
+ elsif subtype.class.included_modules.include?(UserEditable) || subtype.public_methods(false).include?(:set)
176
+ subtype.set(*args)
177
+ elsif @content_editable || content_editable?
178
+ @content_editable = true
179
+ extend UserEditable
180
+ set(*args)
181
+ elsif args.empty? || args.first
182
+ click(*args)
183
+ end
184
+ end
185
+
166
186
  #
167
187
  # Simulates JavaScript click event on element.
168
188
  #
@@ -184,7 +204,10 @@ module Watir
184
204
  #
185
205
 
186
206
  def double_click
187
- element_call(:wait_for_present) { driver.action.double_click(@element).perform }
207
+ element_call(:wait_for_present) do
208
+ scroll.to
209
+ driver.action.double_click(@element).perform
210
+ end
188
211
  browser.after_hooks.run
189
212
  end
190
213
 
@@ -219,6 +242,7 @@ module Watir
219
242
 
220
243
  def right_click(*modifiers)
221
244
  element_call(:wait_for_present) do
245
+ scroll.to
222
246
  action = driver.action
223
247
  if modifiers.any?
224
248
  modifiers.each { |mod| action.key_down mod }
@@ -242,7 +266,10 @@ module Watir
242
266
  #
243
267
 
244
268
  def hover
245
- element_call(:wait_for_present) { driver.action.move_to(@element).perform }
269
+ element_call(:wait_for_present) do
270
+ scroll.to
271
+ driver.action.move_to(@element).perform
272
+ end
246
273
  end
247
274
 
248
275
  #
@@ -259,6 +286,7 @@ module Watir
259
286
  assert_is_element other
260
287
 
261
288
  value = element_call(:wait_for_present) do
289
+ scroll.to
262
290
  driver.action
263
291
  .drag_and_drop(@element, other.wd)
264
292
  .perform
@@ -280,6 +308,7 @@ module Watir
280
308
 
281
309
  def drag_and_drop_by(right_by, down_by)
282
310
  element_call(:wait_for_present) do
311
+ scroll.to
283
312
  driver.action
284
313
  .drag_and_drop_by(@element, right_by, down_by)
285
314
  .perform
@@ -672,7 +701,7 @@ module Watir
672
701
 
673
702
  def wait_for_enabled
674
703
  wait_for_exists
675
- return unless [Input, Button, Select, Option].any? { |c| is_a? c } || content_editable
704
+ return unless [Input, Button, Select, Option].any? { |c| is_a? c } || @content_editable
676
705
  return if enabled?
677
706
 
678
707
  begin
@@ -2,6 +2,7 @@ module Watir
2
2
  class Font < HTMLElement
3
3
  #
4
4
  # size of font
5
+ # This is in the whatwg spec was not generated: https://html.spec.whatwg.org/#htmlfontelement
5
6
  #
6
7
  # @return [Integer]
7
8
  #
@@ -126,7 +126,6 @@ module Watir
126
126
  def switch!
127
127
  @driver.switch_to.frame @element
128
128
  @browser.default_context = false
129
- @browser.after_hooks.run
130
129
  rescue Selenium::WebDriver::Error::NoSuchFrameError => e
131
130
  raise UnknownFrameException, e.message
132
131
  end
@@ -9,8 +9,8 @@ module Watir
9
9
  # Selects this radio button.
10
10
  #
11
11
 
12
- def set
13
- click unless set?
12
+ def set(bool = true)
13
+ click if bool && !set?
14
14
  end
15
15
  alias select set
16
16
 
@@ -5,7 +5,7 @@ module Watir
5
5
  #
6
6
 
7
7
  def clear
8
- raise Exception::Error, 'you can only clear multi-selects' unless multiple?
8
+ raise Exception::Error, 'you can only clear multi-selects' unless multiple_select_list?
9
9
 
10
10
  selected_options.each(&:click)
11
11
  end
@@ -18,7 +18,7 @@ module Watir
18
18
  #
19
19
 
20
20
  def include?(str_or_rx)
21
- option(text: str_or_rx).exist? || option(label: str_or_rx).exist?
21
+ option(text: str_or_rx).exist? || option(label: str_or_rx).exist? || option(value: str_or_rx).exist?
22
22
  end
23
23
 
24
24
  #
@@ -29,14 +29,16 @@ module Watir
29
29
  # @return [String] The text of the option selected. If multiple options match, returns the first match.
30
30
  #
31
31
 
32
- def select(*str_or_rx)
33
- if str_or_rx.size > 1 || str_or_rx.first.is_a?(Array)
34
- str_or_rx.flatten.map { |v| select_all_by v }.first
32
+ def select(*str_or_rx, text: nil, value: nil, label: nil)
33
+ key, value = parse_select_args(str_or_rx, text, value, label)
34
+
35
+ if value.size > 1 || value.first.is_a?(Array)
36
+ value.flatten.map { |v| select_all_by key, v }.first
35
37
  else
36
- found = find_options(:value, str_or_rx.flatten.first).first
37
- select_matching([found])
38
+ select_matching([find_option(key, value.flatten.first)])
38
39
  end
39
40
  end
41
+ alias set select
40
42
 
41
43
  #
42
44
  # Uses JavaScript to select the option whose text matches the given string.
@@ -45,11 +47,13 @@ module Watir
45
47
  # @raise [Watir::Exception::NoValueFoundException] if the value does not exist.
46
48
  #
47
49
 
48
- def select!(*str_or_rx)
49
- if str_or_rx.size > 1 || str_or_rx.first.is_a?(Array)
50
- str_or_rx.flatten.map { |v| select_by! v, :multiple }.first
50
+ def select!(*str_or_rx, text: nil, value: nil, label: nil)
51
+ key, value = parse_select_args(str_or_rx, text, value, label)
52
+
53
+ if value.size > 1 || value.first.is_a?(Array)
54
+ value.flatten.map { |v| select_by! key, v, :multiple }.first
51
55
  else
52
- str_or_rx.flatten.map { |v| select_by! v, :single }.first
56
+ value.flatten.map { |v| select_by! key, v, :single }.first
53
57
  end
54
58
  end
55
59
 
@@ -61,16 +65,10 @@ module Watir
61
65
  # @return [Boolean]
62
66
  #
63
67
 
64
- def selected?(str_or_rx)
65
- by_text = options(text: str_or_rx)
66
- return true if by_text.find(&:selected?)
67
-
68
- by_label = options(label: str_or_rx)
69
- return true if by_label.find(&:selected?)
68
+ def selected?(*str_or_rx, text: nil, value: nil, label: nil)
69
+ key, value = parse_select_args(str_or_rx, text, value, label)
70
70
 
71
- return false unless (by_text.size + by_label.size).zero?
72
-
73
- raise(UnknownObjectException, "Unable to locate option matching #{str_or_rx.inspect}")
71
+ find_option(key, value.first).selected?
74
72
  end
75
73
 
76
74
  #
@@ -81,8 +79,7 @@ module Watir
81
79
  #
82
80
 
83
81
  def value
84
- option = selected_options.first
85
- option&.value
82
+ selected_options.first&.value
86
83
  end
87
84
 
88
85
  #
@@ -92,9 +89,9 @@ module Watir
92
89
  # @return [String, nil]
93
90
  #
94
91
 
92
+ # TODO: What is default behavior without #first ?
95
93
  def text
96
- option = selected_options.first
97
- option&.text
94
+ selected_options.first&.text
98
95
  end
99
96
 
100
97
  # Returns an array of currently selected options.
@@ -108,10 +105,29 @@ module Watir
108
105
 
109
106
  private
110
107
 
111
- def select_by!(str_or_rx, number)
108
+ def multiple_select_list?
109
+ @multiple_select = @multiple_select.nil? ? multiple? : @multiple_select
110
+ end
111
+
112
+ def parse_select_args(str_or_rx, text, value, label)
113
+ selectors = {}
114
+ selectors[:any] = str_or_rx unless str_or_rx.empty?
115
+ selectors[:text] = Array[text] if text
116
+ selectors[:value] = Array[value] if value
117
+ selectors[:label] = Array[label] if label
118
+
119
+ raise ArgumentError, "too many arguments used for Select#select: #{selectors}" if selectors.size > 1
120
+
121
+ selectors.first
122
+ end
123
+
124
+ def select_by!(key, str_or_rx, number)
125
+ str_or_rx = type_check(str_or_rx)
126
+
112
127
  js_rx = process_str_or_rx(str_or_rx)
128
+ approaches = key == :any ? %w[Text Label Value] : [key.to_s.capitalize]
113
129
 
114
- %w[Text Label Value].each do |approach|
130
+ approaches.each do |approach|
115
131
  element_call { execute_js("selectOptions#{approach}", self, js_rx, number.to_s) }
116
132
  return selected_options.first.text if matching_option?(approach.downcase, str_or_rx)
117
133
  end
@@ -147,34 +163,41 @@ module Watir
147
163
  false
148
164
  end
149
165
 
150
- def select_all_by(str_or_rx)
151
- raise Error, 'you can only use #select_all on multi-selects' unless multiple?
166
+ def select_all_by(key, str_or_rx)
167
+ raise Error, 'you can only use #select_all on multi-selects' unless multiple_select_list?
152
168
 
153
- select_matching(find_options(:text, str_or_rx))
169
+ select_matching(find_options(key, str_or_rx))
154
170
  end
155
171
 
156
- def find_options(how, str_or_rx)
157
- msg = "expected String, Numeric or Regexp, got #{str_or_rx.inspect}:#{str_or_rx.class}"
158
- raise TypeError, msg unless [String, Numeric, Regexp].any? { |k| str_or_rx.is_a?(k) }
172
+ def find_option(key, str_or_rx)
173
+ val = type_check(str_or_rx)
159
174
 
160
- wait_while do
161
- @found = how == :value ? options(value: str_or_rx) : []
162
- @found = options(text: str_or_rx) if @found.empty?
163
- @found = options(label: str_or_rx) if @found.empty?
164
- @found.empty?
165
- end
166
- @found
175
+ option(key => val).wait_until(&:exists?)
176
+ rescue Wait::TimeoutError
177
+ raise_no_value_found(str_or_rx)
178
+ end
179
+
180
+ def find_options(key, str_or_rx)
181
+ val = type_check(str_or_rx)
182
+
183
+ options(key => val).wait_until(&:exists?)
167
184
  rescue Wait::TimeoutError
168
185
  raise_no_value_found(str_or_rx)
169
186
  end
170
187
 
188
+ def type_check(str_or_rx)
189
+ str_or_rx = str_or_rx.to_s if str_or_rx.is_a?(Numeric)
190
+ return str_or_rx if [String, Regexp].any? { |k| str_or_rx.is_a?(k) }
191
+
192
+ raise TypeError, "expected String, Numeric or Regexp, got #{str_or_rx.inspect}:#{str_or_rx.class}"
193
+ end
194
+
171
195
  # TODO: Consider locating the Select List before throwing the exception
172
196
  def raise_no_value_found(str_or_rx)
173
197
  raise NoValueFoundException, "#{str_or_rx.inspect} not found in #{inspect}"
174
198
  end
175
199
 
176
200
  def select_matching(elements)
177
- elements = [elements.first] unless multiple?
178
201
  elements.each { |e| e.click unless e.selected? }
179
202
  elements.first.exists? ? elements.first.text : ''
180
203
  end