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
@@ -12,6 +12,7 @@ module Watir
12
12
 
13
13
  self.value = path
14
14
  end
15
+ alias upload set
15
16
 
16
17
  #
17
18
  # Sets the file field to the given path
@@ -272,7 +272,6 @@ module Watir
272
272
  attribute(String, :form, :form)
273
273
  attribute(String, :label, :label)
274
274
  attribute("Boolean", :defaultselected?, :defaultSelected)
275
- attribute("Boolean", :selected?, :selected)
276
275
  attribute(String, :value, :value)
277
276
  attribute(Integer, :index, :index)
278
277
  end
@@ -44,8 +44,9 @@ module Watir
44
44
  # @see Watir::Browser#execute_script
45
45
  #
46
46
 
47
- def execute_script(script, *args)
47
+ def execute_script(script, *args, function_name: nil)
48
48
  args.map! do |e|
49
+ Watir.logger.info "Executing Script on Frame: #{function_name}" if function_name
49
50
  e.is_a?(Element) ? e.wait_until(&:exists?).wd : e
50
51
  end
51
52
  returned = driver.execute_script(script, *args)
@@ -134,8 +135,8 @@ module Watir
134
135
  @element
135
136
  end
136
137
 
137
- def respond_to_missing?(meth, _include_private = false)
138
- @driver.respond_to?(meth) || @element.respond_to?(meth) || super
138
+ def respond_to_missing?(meth, _include_private)
139
+ @driver.respond_to?(meth) || @element.respond_to?(meth) || super(meth, false)
139
140
  end
140
141
 
141
142
  def method_missing(meth, *args, &blk)
@@ -3,13 +3,4 @@ module Watir
3
3
  alias link a
4
4
  alias links as
5
5
  end # Container
6
-
7
- class Anchor < HTMLElement
8
- #
9
- # @todo temporarily add href attribute
10
- #
11
- # @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23192
12
- #
13
- attribute String, :href, :href
14
- end # Anchor
15
6
  end # Watir
@@ -33,7 +33,7 @@ module Watir
33
33
  #
34
34
 
35
35
  def text
36
- l = label()
36
+ l = label
37
37
  l.exist? ? l.text : ''
38
38
  end
39
39
  end # Radio
@@ -30,8 +30,11 @@ module Watir
30
30
  #
31
31
 
32
32
  def select(*str_or_rx)
33
- results = str_or_rx.flatten.map { |v| select_by v }
34
- results.first
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
35
+ else
36
+ str_or_rx.flatten.map { |v| select_by v }.first
37
+ end
35
38
  end
36
39
 
37
40
  #
@@ -43,6 +46,10 @@ module Watir
43
46
  #
44
47
 
45
48
  def select_all(*str_or_rx)
49
+ Watir.logger.deprecate('#select_all',
50
+ '#select with an Array instance',
51
+ ids: [:select_all])
52
+
46
53
  results = str_or_rx.flatten.map { |v| select_all_by v }
47
54
  results.first
48
55
  end
@@ -55,8 +62,11 @@ module Watir
55
62
  #
56
63
 
57
64
  def select!(*str_or_rx)
58
- results = str_or_rx.flatten.map { |v| select_by!(v, :single) }
59
- results.first
65
+ if str_or_rx.size > 1 || str_or_rx.first.is_a?(Array)
66
+ str_or_rx.flatten.map { |v| select_by! v, :multiple }.first
67
+ else
68
+ str_or_rx.flatten.map { |v| select_by! v, :single }.first
69
+ end
60
70
  end
61
71
 
62
72
  #
@@ -67,6 +77,10 @@ module Watir
67
77
  #
68
78
 
69
79
  def select_all!(*str_or_rx)
80
+ Watir.logger.deprecate('#select_all!',
81
+ '#select! with an Array instance',
82
+ ids: [:select_all])
83
+
70
84
  results = str_or_rx.flatten.map { |v| select_by!(v, :multiple) }
71
85
  results.first
72
86
  end
@@ -145,7 +159,8 @@ module Watir
145
159
  found = find_options(:value, str_or_rx)
146
160
 
147
161
  if found.size > 1
148
- Watir.logger.deprecate 'Selecting Multiple Options with #select', '#select_all',
162
+ Watir.logger.deprecate 'Selecting multiple options with #select using a String or Regexp value',
163
+ '#select with the desired values in an Array instance',
149
164
  ids: [:select_by]
150
165
  end
151
166
  select_matching(found)
@@ -170,8 +185,8 @@ module Watir
170
185
  str_or_rx.inspect.sub('\\A', '^')
171
186
  .sub('\\Z', '$')
172
187
  .sub('\\z', '$')
173
- .sub(%r{^\/}, '')
174
- .sub(%r{\/[a-z]*$}, '')
188
+ .sub(%r{^/}, '')
189
+ .sub(%r{/[a-z]*$}, '')
175
190
  .gsub(/\(\?#.+\)/, '')
176
191
  .gsub(/\(\?-\w+:/, '(')
177
192
  else
@@ -109,8 +109,8 @@ module Watir
109
109
  begin
110
110
  intf = fetch_interface(implementor_name).first
111
111
  intf.implements << fetch_interface(implementee_name).first
112
- rescue InterfaceNotFound => ex
113
- puts ex.message
112
+ rescue InterfaceNotFound => e
113
+ puts e.message
114
114
  end
115
115
  end
116
116
  end
@@ -123,8 +123,8 @@ module Watir
123
123
  begin
124
124
  intf = fetch_interface(includer_name).first
125
125
  intf.includes << fetch_interface(includee_name).first
126
- rescue InterfaceNotFound => ex
127
- puts ex.message
126
+ rescue InterfaceNotFound => e
127
+ puts e.message
128
128
  end
129
129
  end
130
130
  end
@@ -14,7 +14,7 @@ module Watir
14
14
  end
15
15
 
16
16
  def ignored_attributes
17
- %w[cells elements hash rows span text size selected? style width height tHead tFoot link options]
17
+ %w[cells elements hash rows span text size selected? style width height tHead tFoot link options selected]
18
18
  end
19
19
 
20
20
  def generator_implementation
@@ -10,13 +10,7 @@ module Watir
10
10
  #
11
11
 
12
12
  def windows(*args)
13
- all = @driver.window_handles.map { |handle| Window.new(self, handle: handle) }
14
-
15
- if args.empty?
16
- all
17
- else
18
- filter_windows extract_selector(args), all
19
- end
13
+ WindowCollection.new self, extract_selector(args)
20
14
  end
21
15
 
22
16
  #
@@ -51,16 +45,24 @@ module Watir
51
45
  @original_window ||= window
52
46
  end
53
47
 
54
- private
48
+ #
49
+ # Waits for and returns second window if present
50
+ # See Window#use
51
+ #
52
+ # @example
53
+ # browser.switch_window
54
+ #
55
+ # @return [Window]
56
+ #
55
57
 
56
- def filter_windows(selector, windows)
57
- unless selector.keys.all? { |k| %i[title url].include? k }
58
- raise ArgumentError, "invalid window selector: #{selector.inspect}"
59
- end
58
+ def switch_window
59
+ current_window = window
60
+ wins = windows
61
+ wait_until { (wins = windows) && wins.size > 1 } if wins.size == 1
62
+ raise StandardError, 'Unable to determine which window to switch to' if wins.size > 2
60
63
 
61
- windows.select do |win|
62
- selector.all? { |key, value| win.send(key) =~ /#{value}/ }
63
- end
64
+ wins.find { |w| w != current_window }.use
65
+ window
64
66
  end
65
67
  end # HasWindow
66
68
  end # Watir
@@ -3,8 +3,8 @@ module Watir
3
3
  #
4
4
  # Delegates script execution to Browser or IFrame.
5
5
  #
6
- def execute_script(script, *args)
7
- @query_scope.execute_script(script, *args)
6
+ def execute_script(script, *args, function_name: nil)
7
+ @query_scope.execute_script(script, *args, function_name: function_name)
8
8
  end
9
9
 
10
10
  #
@@ -54,7 +54,7 @@ module Watir
54
54
  long: {flashes: 5, delay: 0.5},
55
55
  rainbow: {flashes: 5, color: %w[red orange yellow green blue indigo violet]}
56
56
  }
57
- return flash(presets[preset]) unless presets[preset].nil?
57
+ return flash(**presets[preset]) unless presets[preset].nil?
58
58
 
59
59
  background_color = original_color = style('background-color')
60
60
  background_color = 'white' if background_color.empty?
@@ -6,11 +6,11 @@ module Watir
6
6
 
7
7
  def execute_js(function_name, *arguments)
8
8
  file = File.expand_path("../js_snippets/#{function_name}.js", __FILE__)
9
- raise Exception::Error, "Can not excute script as #{function_name}.js does not exist" unless File.exist?(file)
9
+ raise Exception::Error, "Can not execute script as #{function_name}.js does not exist" unless File.exist?(file)
10
10
 
11
11
  js = File.read(file)
12
12
  script = "return (#{js}).apply(null, arguments)"
13
- @query_scope.execute_script(script, *arguments)
13
+ @query_scope.execute_script(script, *arguments, function_name: function_name)
14
14
  end
15
15
  end # JSSnippets
16
16
  end # Watir
@@ -10,7 +10,7 @@ module Watir
10
10
  end
11
11
 
12
12
  def respond_to_missing?(*args)
13
- @element.respond_to?(*args)
13
+ @element.respond_to?(*args) || super
14
14
  end
15
15
 
16
16
  def method_missing(method, *args, &block)
@@ -55,9 +55,7 @@ module Watir
55
55
  end
56
56
 
57
57
  def class_from_string(string)
58
- Kernel.const_get(string)
59
- rescue NameError
60
- nil
58
+ Watir.const_get(string) if Watir.const_defined?(string)
61
59
  end
62
60
 
63
61
  def element_class_name
@@ -15,7 +15,8 @@ module Watir
15
15
  def locate(built)
16
16
  @built = built.dup
17
17
  @driver_scope = locator_scope.wd
18
- matching_elements(@built, :first)
18
+ @filter = :first
19
+ matching_elements
19
20
  rescue Selenium::WebDriver::Error::NoSuchElementError
20
21
  nil
21
22
  end
@@ -23,37 +24,46 @@ module Watir
23
24
  def locate_all(built)
24
25
  @built = built.dup
25
26
  @driver_scope = locator_scope.wd
26
- raise ArgumentError, "can't locate all elements by :index" if built.key?(:index)
27
+ @filter = :all
27
28
 
28
- [matching_elements(@built, :all)].flatten
29
+ return [matching_elements].flatten unless @built.key?(:index)
30
+
31
+ raise ArgumentError, "can't locate all elements by :index"
29
32
  end
30
33
 
31
34
  private
32
35
 
33
- def matching_elements(built, filter)
34
- return locate_element(*built.to_a.flatten) if built.size == 1 && filter == :first
35
-
36
- wd_locator_key = (Watir::Locators::W3C_FINDERS & built.keys).first
37
- wd_locator = built.select { |k, _v| wd_locator_key == k }
38
- match_values = built.reject { |k, _v| wd_locator_key == k }
36
+ def matching_elements
37
+ return locate_element(*@built.to_a.flatten) if @built.size == 1 && @filter == :first
39
38
 
40
39
  # TODO: Wrap this to continue trying until default timeout
41
40
  retries = 0
42
41
  begin
43
42
  elements = locate_elements(*wd_locator.to_a.flatten)
44
43
 
45
- element_matcher.match(elements, match_values, filter)
44
+ element_matcher.match(elements, match_values, @filter)
46
45
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
47
46
  retries += 1
48
47
  sleep 0.5
49
48
  retry unless retries > 2
50
- target = filter == :all ? 'element collection' : 'element'
49
+ target = @filter == :all ? 'element collection' : 'element'
51
50
  raise LocatorException, "Unable to locate #{target} from #{@selector} due to changing page"
52
51
  end
53
52
  end
54
53
 
54
+ def wd_locator
55
+ # SelectorBuilder only allows one of these
56
+ wd_locator_key = (Watir::Locators::W3C_FINDERS & @built.keys).first
57
+ @wd_locator ||= @built.select { |k, _v| wd_locator_key == k }
58
+ end
59
+
60
+ def match_values
61
+ @match_values ||= @built.reject { |k, _v| wd_locator.keys.first == k }
62
+ end
63
+
55
64
  def locator_scope
56
- @built.delete(:scope) || @query_scope.browser
65
+ scope = @built.delete(:scope)
66
+ @locator_scope ||= scope || @query_scope.browser
57
67
  end
58
68
 
59
69
  def locate_element(how, what, scope = driver_scope)
@@ -6,11 +6,10 @@ module Watir
6
6
  attr_reader :custom_attributes, :built
7
7
 
8
8
  WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/.freeze
9
- INTEGER_CLASS = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4') ? Fixnum : Integer
10
9
  VALID_WHATS = Hash.new([String, Regexp, TrueClass, FalseClass]).merge(adjacent: [::Symbol],
11
10
  xpath: [String],
12
11
  css: [String],
13
- index: [INTEGER_CLASS],
12
+ index: [Integer],
14
13
  visible: [TrueClass, FalseClass],
15
14
  tag_name: [String, Regexp, ::Symbol],
16
15
  visible_text: [String, Regexp],
@@ -53,11 +52,7 @@ module Watir
53
52
  if @selector.key?(:class) || @selector.key?(:class_name)
54
53
  classes = ([@selector[:class]].flatten + [@selector.delete(:class_name)].flatten).compact
55
54
 
56
- classes.each do |class_name|
57
- next unless class_name.is_a?(String) && class_name.strip.include?(' ')
58
-
59
- deprecate_class_array(class_name)
60
- end
55
+ deprecate_class_array(classes)
61
56
 
62
57
  @selector[:class] = classes
63
58
  end
@@ -83,11 +78,15 @@ module Watir
83
78
  scope_invalid_locators.empty?
84
79
  end
85
80
 
86
- def deprecate_class_array(class_name)
87
- dep = "Using the :class locator to locate multiple classes with a String value (i.e. \"#{class_name}\")"
88
- Watir.logger.deprecate dep,
89
- "Array (e.g. #{class_name.split})",
90
- ids: [:class_array]
81
+ def deprecate_class_array(class_array)
82
+ class_array.each do |class_name|
83
+ next unless class_name.is_a?(String) && class_name.strip.include?(' ')
84
+
85
+ dep = "Using the :class locator to locate multiple classes with a String value (i.e. \"#{class_name}\")"
86
+ Watir.logger.deprecate dep,
87
+ "Array (e.g. #{class_name.split})",
88
+ ids: [:class_array]
89
+ end
91
90
  end
92
91
 
93
92
  def check_type(how, what)
@@ -143,7 +142,7 @@ module Watir
143
142
 
144
143
  # Extensions implement this method when creating a different selector builder
145
144
  def implementation_class
146
- Kernel.const_get("#{self.class.name}::XPath")
145
+ Watir.const_get("#{self.class.name}::XPath")
147
146
  end
148
147
 
149
148
  def build_wd_selector(selector)
@@ -12,6 +12,7 @@ module Watir
12
12
 
13
13
  def build(selector)
14
14
  @selector = selector
15
+ @valid_attributes = build_valid_attributes
15
16
 
16
17
  @built = (@selector.keys & CAN_NOT_BUILD).each_with_object({}) do |key, hash|
17
18
  hash[key] = @selector.delete(key)
@@ -45,9 +46,10 @@ module Watir
45
46
  end
46
47
 
47
48
  def predicate_expression(key, val)
48
- if val.eql? true
49
+ case val
50
+ when true
49
51
  attribute_presence(key)
50
- elsif val.eql? false
52
+ when false
51
53
  attribute_absence(key)
52
54
  else
53
55
  equal_pair(key, val)
@@ -55,11 +57,9 @@ module Watir
55
57
  end
56
58
 
57
59
  def predicate_conversion(key, regexp)
58
- # type attributes can be upper case - downcase them
59
- # https://github.com/watir/watir/issues/72
60
- downcase = key == :type || regexp.casefold?
60
+ downcase = case_insensitive_attribute?(key) || regexp.casefold?
61
61
 
62
- lhs = lhs_for(key, downcase)
62
+ lhs = lhs_for(key, downcase: downcase)
63
63
 
64
64
  results = RegexpDisassembler.new(regexp).substrings
65
65
 
@@ -72,8 +72,10 @@ module Watir
72
72
 
73
73
  add_to_matching(key, regexp, results)
74
74
 
75
- results.map { |substring|
76
- "contains(#{lhs}, '#{substring}')"
75
+ results.map { |rhs|
76
+ rhs = "'#{rhs}'"
77
+ rhs = XpathSupport.downcase(rhs) if downcase
78
+ "contains(#{lhs}, #{rhs})"
77
79
  }.flatten.compact.join(' and ')
78
80
  end
79
81
 
@@ -200,7 +202,7 @@ module Watir
200
202
  regexp.casefold? ? !results.first.casecmp(regexp.source).zero? : results.first != regexp.source
201
203
  end
202
204
 
203
- def lhs_for(key, downcase = false)
205
+ def lhs_for(key, downcase: false)
204
206
  case key
205
207
  when String
206
208
  "@#{key}"
@@ -211,7 +213,7 @@ module Watir
211
213
  when :text
212
214
  'normalize-space()'
213
215
  when :contains_text
214
- 'text()'
216
+ 'normalize-space()'
215
217
  when ::Symbol
216
218
  lhs = "@#{key.to_s.tr('_', '-')}"
217
219
  downcase ? XpathSupport.downcase(lhs) : lhs
@@ -221,11 +223,11 @@ module Watir
221
223
  end
222
224
 
223
225
  def attribute_presence(attribute)
224
- lhs_for(attribute, false)
226
+ lhs_for(attribute)
225
227
  end
226
228
 
227
229
  def attribute_absence(attribute)
228
- lhs = lhs_for(attribute, false)
230
+ lhs = lhs_for(attribute)
229
231
  "not(#{lhs})"
230
232
  end
231
233
 
@@ -236,9 +238,34 @@ module Watir
236
238
 
237
239
  negate_xpath ? "not(#{expression})" : expression
238
240
  else
239
- "#{lhs_for(key, key == :type)}=#{XpathSupport.escape value}"
241
+ downcase = case_insensitive_attribute?(key)
242
+
243
+ lhs = lhs_for(key, downcase: downcase)
244
+ rhs = XpathSupport.escape(value)
245
+ rhs = XpathSupport.downcase(rhs) if downcase
246
+
247
+ "#{lhs}=#{rhs}"
248
+ end
249
+ end
250
+
251
+ def build_valid_attributes
252
+ tag_name = @selector[:tag_name]
253
+ if tag_name.is_a?(String) && Watir.tag_to_class[tag_name.to_sym]
254
+ Watir.tag_to_class[tag_name.to_sym].attribute_list
255
+ else
256
+ Watir::HTMLElement.attribute_list
240
257
  end
241
258
  end
259
+
260
+ def case_insensitive_attribute?(attribute)
261
+ # type attributes can be upper case - downcase them
262
+ # https://github.com/watir/watir/issues/72
263
+ return true if attribute == :type
264
+ return true if Watir::Element::CASE_INSENSITIVE_ATTRIBUTES.include?(attribute) &&
265
+ @valid_attributes.include?(attribute)
266
+
267
+ false
268
+ end
242
269
  end
243
270
  end
244
271
  end