watir 7.0.0.beta1 → 7.0.0.beta5

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