watir 5.0.0 → 6.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.document +5 -0
  3. data/.gitignore +21 -0
  4. data/.gitmodules +3 -0
  5. data/.travis.yml +35 -0
  6. data/CHANGES.md +1756 -0
  7. data/Gemfile +12 -0
  8. data/LICENSE +23 -0
  9. data/README.md +92 -0
  10. data/Rakefile +161 -32
  11. data/lib/watir.rb +127 -1
  12. data/lib/watir/after_hooks.rb +132 -0
  13. data/lib/watir/alert.rb +104 -0
  14. data/lib/watir/aliases.rb +6 -0
  15. data/lib/watir/atoms.rb +24 -0
  16. data/lib/watir/atoms/README +3 -0
  17. data/lib/watir/atoms/fireEvent.js +29 -0
  18. data/lib/watir/atoms/getAttribute.js +18 -0
  19. data/lib/watir/atoms/getInnerHtml.js +18 -0
  20. data/lib/watir/atoms/getOuterHtml.js +18 -0
  21. data/lib/watir/atoms/getParentElement.js +17 -0
  22. data/lib/watir/atoms/selectText.js +61 -0
  23. data/lib/watir/attribute_helper.rb +98 -0
  24. data/lib/watir/browser.rb +346 -0
  25. data/lib/watir/cell_container.rb +25 -0
  26. data/lib/watir/container.rb +51 -0
  27. data/lib/watir/cookies.rb +132 -0
  28. data/lib/watir/element_collection.rb +126 -0
  29. data/lib/watir/elements/area.rb +12 -0
  30. data/lib/watir/elements/button.rb +37 -0
  31. data/lib/watir/elements/cell.rb +17 -0
  32. data/lib/watir/elements/checkbox.rb +54 -0
  33. data/lib/watir/elements/dlist.rb +12 -0
  34. data/lib/watir/elements/element.rb +646 -0
  35. data/lib/watir/elements/file_field.rb +41 -0
  36. data/lib/watir/elements/font.rb +11 -0
  37. data/lib/watir/elements/form.rb +17 -0
  38. data/lib/watir/elements/hidden.rb +20 -0
  39. data/lib/watir/elements/html_elements.rb +2063 -0
  40. data/lib/watir/elements/iframe.rb +163 -0
  41. data/lib/watir/elements/image.rb +62 -0
  42. data/lib/watir/elements/input.rb +7 -0
  43. data/lib/watir/elements/link.rb +18 -0
  44. data/lib/watir/elements/option.rb +74 -0
  45. data/lib/watir/elements/radio.rb +42 -0
  46. data/lib/watir/elements/row.rb +17 -0
  47. data/lib/watir/elements/select.rb +238 -0
  48. data/lib/watir/elements/svg_elements.rb +667 -0
  49. data/lib/watir/elements/table.rb +42 -0
  50. data/lib/watir/elements/table_cell.rb +6 -0
  51. data/lib/watir/elements/table_row.rb +15 -0
  52. data/lib/watir/elements/table_section.rb +15 -0
  53. data/lib/watir/elements/text_area.rb +5 -0
  54. data/lib/watir/elements/text_field.rb +37 -0
  55. data/lib/watir/exception.rb +17 -0
  56. data/lib/watir/extensions/nokogiri.rb +14 -0
  57. data/lib/watir/extensions/select_text.rb +10 -0
  58. data/lib/watir/generator.rb +3 -0
  59. data/lib/watir/generator/base.rb +11 -0
  60. data/lib/watir/generator/base/generator.rb +115 -0
  61. data/lib/watir/generator/base/idl_sorter.rb +47 -0
  62. data/lib/watir/generator/base/spec_extractor.rb +138 -0
  63. data/lib/watir/generator/base/util.rb +21 -0
  64. data/lib/watir/generator/base/visitor.rb +157 -0
  65. data/lib/watir/generator/html.rb +15 -0
  66. data/lib/watir/generator/html/generator.rb +36 -0
  67. data/lib/watir/generator/html/spec_extractor.rb +50 -0
  68. data/lib/watir/generator/html/visitor.rb +21 -0
  69. data/lib/watir/generator/svg.rb +7 -0
  70. data/lib/watir/generator/svg/generator.rb +38 -0
  71. data/lib/watir/generator/svg/spec_extractor.rb +46 -0
  72. data/lib/watir/generator/svg/visitor.rb +21 -0
  73. data/lib/watir/has_window.rb +53 -0
  74. data/lib/watir/locators.rb +22 -0
  75. data/lib/watir/locators/button/locator.rb +38 -0
  76. data/lib/watir/locators/button/selector_builder.rb +27 -0
  77. data/lib/watir/locators/button/selector_builder/xpath.rb +29 -0
  78. data/lib/watir/locators/button/validator.rb +15 -0
  79. data/lib/watir/locators/cell/locator.rb +17 -0
  80. data/lib/watir/locators/cell/selector_builder.rb +24 -0
  81. data/lib/watir/locators/element/locator.rb +249 -0
  82. data/lib/watir/locators/element/selector_builder.rb +147 -0
  83. data/lib/watir/locators/element/selector_builder/css.rb +65 -0
  84. data/lib/watir/locators/element/selector_builder/xpath.rb +72 -0
  85. data/lib/watir/locators/element/validator.rb +23 -0
  86. data/lib/watir/locators/row/locator.rb +17 -0
  87. data/lib/watir/locators/row/selector_builder.rb +29 -0
  88. data/lib/watir/locators/text_area/locator.rb +13 -0
  89. data/lib/watir/locators/text_area/selector_builder.rb +22 -0
  90. data/lib/watir/locators/text_field/locator.rb +44 -0
  91. data/lib/watir/locators/text_field/selector_builder.rb +34 -0
  92. data/lib/watir/locators/text_field/selector_builder/xpath.rb +19 -0
  93. data/lib/watir/locators/text_field/validator.rb +20 -0
  94. data/lib/watir/row_container.rb +36 -0
  95. data/lib/watir/screenshot.rb +50 -0
  96. data/lib/watir/user_editable.rb +38 -0
  97. data/lib/watir/version.rb +3 -3
  98. data/lib/watir/wait.rb +250 -0
  99. data/lib/watir/wait/timer.rb +19 -0
  100. data/lib/watir/window.rb +244 -0
  101. data/lib/watir/xpath_support.rb +20 -0
  102. data/spec/always_locate_spec.rb +43 -0
  103. data/spec/browser_spec.rb +130 -0
  104. data/spec/click_spec.rb +19 -0
  105. data/spec/container_spec.rb +34 -0
  106. data/spec/element_locator_spec.rb +532 -0
  107. data/spec/element_spec.rb +136 -0
  108. data/spec/implementation.rb +216 -0
  109. data/spec/input_spec.rb +14 -0
  110. data/spec/locator_spec_helper.rb +57 -0
  111. data/spec/spec_helper.rb +35 -0
  112. data/spec/special_chars_spec.rb +13 -0
  113. data/support/doctest_helper.rb +78 -0
  114. data/support/travis.sh +44 -0
  115. data/support/version_differ.rb +59 -0
  116. data/watir.gemspec +37 -25
  117. metadata +288 -23
  118. data/lib/watir/loader.rb +0 -64
@@ -0,0 +1,126 @@
1
+ module Watir
2
+
3
+ #
4
+ # Base class for element collections.
5
+ #
6
+
7
+ class ElementCollection
8
+ include Enumerable
9
+
10
+ def initialize(parent, selector)
11
+ @parent = parent
12
+ @selector = selector
13
+ end
14
+
15
+ #
16
+ # Yields each element in collection.
17
+ #
18
+ # @example
19
+ # divs = browser.divs(class: 'kls')
20
+ # divs.each do |div|
21
+ # puts div.text
22
+ # end
23
+ #
24
+ # @yieldparam [Watir::Element] element Iterate through the elements in this collection.
25
+ #
26
+
27
+ def each(&blk)
28
+ to_a.each(&blk)
29
+ end
30
+
31
+ #
32
+ # Returns number of elements in collection.
33
+ #
34
+ # @return [Fixnum]
35
+ #
36
+
37
+ def length
38
+ elements.length
39
+ end
40
+ alias_method :size, :length
41
+
42
+ #
43
+ # Get the element at the given index.
44
+ #
45
+ # Also note that because of Watir's lazy loading, this will return an Element
46
+ # instance even if the index is out of bounds.
47
+ #
48
+ # @param [Fixnum] idx Index of wanted element, 0-indexed
49
+ # @return [Watir::Element] Returns an instance of a Watir::Element subclass
50
+ #
51
+
52
+ def [](idx)
53
+ to_a[idx] || element_class.new(@parent, @selector.merge(index: idx))
54
+ end
55
+
56
+ #
57
+ # First element of this collection
58
+ #
59
+ # @return [Watir::Element] Returns an instance of a Watir::Element subclass
60
+ #
61
+
62
+ def first
63
+ self[0]
64
+ end
65
+
66
+ #
67
+ # Last element of the collection
68
+ #
69
+ # @return [Watir::Element] Returns an instance of a Watir::Element subclass
70
+ #
71
+
72
+ def last
73
+ self[-1]
74
+ end
75
+
76
+ #
77
+ # This collection as an Array.
78
+ #
79
+ # @return [Array<Watir::Element>]
80
+ #
81
+
82
+ def to_a
83
+ # TODO: optimize - lazy element_class instance?
84
+ @to_a ||= elements.map { |e| element_class.new(@parent, element: e) }
85
+ end
86
+
87
+ private
88
+
89
+ def elements
90
+ @parent.is_a?(IFrame) ? @parent.switch_to! : @parent.send(:assert_exists)
91
+
92
+ element_validator = element_validator_class.new
93
+ selector_builder = selector_builder_class.new(@parent, @selector, element_class.attribute_list)
94
+ locator = locator_class.new(@parent, @selector, selector_builder, element_validator)
95
+
96
+ @elements ||= locator.locate_all
97
+ end
98
+
99
+ def locator_class
100
+ Kernel.const_get("#{Watir.locator_namespace}::#{element_class_name}::Locator")
101
+ rescue NameError
102
+ Kernel.const_get("#{Watir.locator_namespace}::Element::Locator")
103
+ end
104
+
105
+ def element_validator_class
106
+ Kernel.const_get("#{Watir.locator_namespace}::#{element_class_name}::Validator")
107
+ rescue NameError
108
+ Kernel.const_get("#{Watir.locator_namespace}::Element::Validator")
109
+ end
110
+
111
+ def selector_builder_class
112
+ Kernel.const_get("#{Watir.locator_namespace}::#{element_class_name}::SelectorBuilder")
113
+ rescue NameError
114
+ Kernel.const_get("#{Watir.locator_namespace}::Element::SelectorBuilder")
115
+ end
116
+
117
+ def element_class_name
118
+ element_class.to_s.split('::').last
119
+ end
120
+
121
+ def element_class
122
+ Kernel.const_get(self.class.name.sub(/Collection$/, ''))
123
+ end
124
+
125
+ end # ElementCollection
126
+ end # Watir
@@ -0,0 +1,12 @@
1
+ module Watir
2
+ class Area < HTMLElement
3
+
4
+ #
5
+ # @todo temporarily add href attribute
6
+ #
7
+ # @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23192
8
+ #
9
+ attribute String, :href, :href
10
+
11
+ end # Area
12
+ end # Watir
@@ -0,0 +1,37 @@
1
+ module Watir
2
+
3
+ #
4
+ # Class representing button elements.
5
+ #
6
+ # This class covers both <button> and <input type="submit|reset|image|button" /> elements.
7
+ #
8
+
9
+ class Button < HTMLElement
10
+
11
+ inherit_attributes_from Watir::Input
12
+
13
+ VALID_TYPES = %w[button reset submit image]
14
+
15
+ #
16
+ # Returns the text of the button.
17
+ #
18
+ # For input elements, returns the "value" attribute.
19
+ # For button elements, returns the inner text.
20
+ #
21
+ # @return [String]
22
+ #
23
+
24
+ def text
25
+ tn = tag_name
26
+
27
+ case tn
28
+ when 'input'
29
+ value
30
+ when 'button'
31
+ super
32
+ else
33
+ raise Exception::Error, "unknown tag name for button: #{tn}"
34
+ end
35
+ end
36
+ end # Button
37
+ end # Watir
@@ -0,0 +1,17 @@
1
+ module Watir
2
+
3
+ #
4
+ # Custom class representing table cell (th or td).
5
+ #
6
+
7
+ class Cell < TableCell
8
+ end # Cell
9
+
10
+ class CellCollection < TableCellCollection
11
+ def elements
12
+ # we do this craziness since the xpath used will find direct child rows
13
+ # before any rows inside thead/tbody/tfoot...
14
+ super.sort_by { |e| e.attribute(:cellIndex).to_i }
15
+ end
16
+ end
17
+ end # Watir
@@ -0,0 +1,54 @@
1
+ module Watir
2
+ class CheckBox < Input
3
+
4
+ #
5
+ # Sets checkbox to the given value.
6
+ #
7
+ # @example
8
+ # checkbox = browser.checkbox(id: 'new_user_interests_cars')
9
+ # checkbox.set? #=> false
10
+ # checkbox.set
11
+ # checkbox.set? #=> true
12
+ # checkbox.set(false)
13
+ # checkbox.set? #=> false
14
+ #
15
+ # @param [Boolean] bool
16
+ #
17
+
18
+ def set(bool = true)
19
+ set? == bool ? assert_enabled : click
20
+ end
21
+
22
+ #
23
+ # Returns true if the element is checked
24
+ # @return [Boolean]
25
+ #
26
+
27
+ def set?
28
+ assert_exists
29
+ element_call { @element.selected? }
30
+ end
31
+
32
+ #
33
+ # Unsets checkbox.
34
+ #
35
+
36
+ def clear
37
+ set false
38
+ end
39
+
40
+ end # CheckBox
41
+
42
+ module Container
43
+ def checkbox(*args)
44
+ CheckBox.new(self, extract_selector(args).merge(tag_name: "input", type: "checkbox"))
45
+ end
46
+
47
+ def checkboxes(*args)
48
+ CheckBoxCollection.new(self, extract_selector(args).merge(tag_name: "input", type: "checkbox"))
49
+ end
50
+ end # Container
51
+
52
+ class CheckBoxCollection < InputCollection
53
+ end # CheckBoxCollection
54
+ end # Watir
@@ -0,0 +1,12 @@
1
+ module Watir
2
+ class DList < HTMLElement
3
+
4
+ def to_hash
5
+ keys = dts.map { |e| e.text }
6
+ values = dds.map { |e| e.text }
7
+
8
+ Hash[keys.zip(values)]
9
+ end
10
+
11
+ end # DList
12
+ end # Watir
@@ -0,0 +1,646 @@
1
+ module Watir
2
+
3
+ #
4
+ # Base class for HTML elements.
5
+ #
6
+
7
+ class Element
8
+ extend AttributeHelper
9
+
10
+ include Exception
11
+ include Container
12
+ include EventuallyPresent
13
+
14
+ #
15
+ # temporarily add :id and :class_name manually since they're no longer specified in the HTML spec.
16
+ #
17
+ # @see http://html5.org/r/6605
18
+ # @see http://html5.org/r/7174
19
+ #
20
+ # TODO: use IDL from DOM core - http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html
21
+ #
22
+ attribute String, :id, :id
23
+ attribute String, :class_name, :className
24
+
25
+ def initialize(parent, selector)
26
+ @parent = parent
27
+ @selector = selector
28
+ @element = nil
29
+
30
+ unless @selector.kind_of? Hash
31
+ raise ArgumentError, "invalid argument: #{selector.inspect}"
32
+ end
33
+ end
34
+
35
+ #
36
+ # Returns true if element exists.
37
+ #
38
+ # @return [Boolean]
39
+ #
40
+
41
+ def exists?
42
+ assert_exists
43
+ true
44
+ rescue UnknownObjectException, UnknownFrameException
45
+ false
46
+ end
47
+ alias_method :exist?, :exists?
48
+
49
+ def inspect
50
+ if @selector.key?(:element)
51
+ '#<%s:0x%x located=%s selector=%s>' % [self.class, hash*2, !!@element, '{element: (selenium element)}']
52
+ else
53
+ '#<%s:0x%x located=%s selector=%s>' % [self.class, hash*2, !!@element, selector_string]
54
+ end
55
+ end
56
+
57
+ #
58
+ # Returns true if two elements are equal.
59
+ #
60
+ # @example
61
+ # browser.text_field(name: "new_user_first_name") == browser.text_field(name: "new_user_first_name")
62
+ # #=> true
63
+ #
64
+
65
+ def ==(other)
66
+ other.is_a?(self.class) && wd == other.wd
67
+ end
68
+ alias_method :eql?, :==
69
+
70
+ def hash
71
+ @element ? @element.hash : super
72
+ end
73
+
74
+ #
75
+ # Returns the text of the element.
76
+ #
77
+ # @return [String]
78
+ #
79
+
80
+ def text
81
+ assert_exists
82
+ element_call { @element.text }
83
+ end
84
+
85
+ #
86
+ # Returns tag name of the element.
87
+ #
88
+ # @return [String]
89
+ #
90
+
91
+ def tag_name
92
+ assert_exists
93
+ element_call { @element.tag_name.downcase }
94
+ end
95
+
96
+ #
97
+ # Clicks the element, optionally while pressing the given modifier keys.
98
+ # Note that support for holding a modifier key is currently experimental,
99
+ # and may not work at all.
100
+ #
101
+ # @example Click an element
102
+ # browser.element(name: "new_user_button").click
103
+ #
104
+ # @example Click an element with shift key pressed
105
+ # browser.element(name: "new_user_button").click(:shift)
106
+ #
107
+ # @example Click an element with several modifier keys pressed
108
+ # browser.element(name: "new_user_button").click(:shift, :control)
109
+ #
110
+ # @param [:shift, :alt, :control, :command, :meta] Modifier key(s) to press while clicking.
111
+ #
112
+
113
+ def click(*modifiers)
114
+ assert_exists
115
+ assert_enabled
116
+
117
+ element_call do
118
+ if modifiers.any?
119
+ assert_has_input_devices_for "click(#{modifiers.join ', '})"
120
+
121
+ action = driver.action
122
+ modifiers.each { |mod| action.key_down mod }
123
+ action.click @element
124
+ modifiers.each { |mod| action.key_up mod }
125
+
126
+ action.perform
127
+ else
128
+ @element.click
129
+ end
130
+ end
131
+
132
+ browser.after_hooks.run
133
+ end
134
+
135
+ #
136
+ # Double clicks the element.
137
+ # Note that browser support may vary.
138
+ #
139
+ # @example
140
+ # browser.element(name: "new_user_button").double_click
141
+ #
142
+
143
+ def double_click
144
+ assert_exists
145
+ assert_has_input_devices_for :double_click
146
+
147
+ element_call { driver.action.double_click(@element).perform }
148
+ browser.after_hooks.run
149
+ end
150
+
151
+ #
152
+ # Right clicks the element.
153
+ # Note that browser support may vary.
154
+ #
155
+ # @example
156
+ # browser.element(name: "new_user_button").right_click
157
+ #
158
+
159
+ def right_click
160
+ assert_exists
161
+ assert_has_input_devices_for :right_click
162
+
163
+ element_call { driver.action.context_click(@element).perform }
164
+ browser.after_hooks.run
165
+ end
166
+
167
+ #
168
+ # Moves the mouse to the middle of this element.
169
+ # Note that browser support may vary.
170
+ #
171
+ # @example
172
+ # browser.element(name: "new_user_button").hover
173
+ #
174
+
175
+ def hover
176
+ assert_exists
177
+ assert_has_input_devices_for :hover
178
+
179
+ element_call { driver.action.move_to(@element).perform }
180
+ end
181
+
182
+ #
183
+ # Drag and drop this element on to another element instance.
184
+ # Note that browser support may vary.
185
+ #
186
+ # @example
187
+ # a = browser.div(id: "draggable")
188
+ # b = browser.div(id: "droppable")
189
+ # a.drag_and_drop_on b
190
+ #
191
+
192
+ def drag_and_drop_on(other)
193
+ assert_is_element other
194
+ assert_exists
195
+ assert_has_input_devices_for :drag_and_drop_on
196
+
197
+ element_call do
198
+ driver.action.
199
+ drag_and_drop(@element, other.wd).
200
+ perform
201
+ end
202
+ end
203
+
204
+ #
205
+ # Drag and drop this element by the given offsets.
206
+ # Note that browser support may vary.
207
+ #
208
+ # @example
209
+ # browser.div(id: "draggable").drag_and_drop_by 100, -200
210
+ #
211
+ # @param [Fixnum] right_by
212
+ # @param [Fixnum] down_by
213
+ #
214
+
215
+ def drag_and_drop_by(right_by, down_by)
216
+ assert_exists
217
+ assert_has_input_devices_for :drag_and_drop_by
218
+
219
+ element_call do
220
+ driver.action.
221
+ drag_and_drop_by(@element, right_by, down_by).
222
+ perform
223
+ end
224
+ end
225
+
226
+ #
227
+ # Flashes (change background color far a moment) element.
228
+ #
229
+ # @example
230
+ # browser.text_field(name: "new_user_first_name").flash
231
+ #
232
+
233
+ def flash
234
+ background_color = style("backgroundColor")
235
+ element_color = driver.execute_script("arguments[0].style.backgroundColor", @element)
236
+
237
+ 10.times do |n|
238
+ color = (n % 2 == 0) ? "red" : background_color
239
+ driver.execute_script("arguments[0].style.backgroundColor = '#{color}'", @element)
240
+ end
241
+
242
+ driver.execute_script("arguments[0].style.backgroundColor = arguments[1]", @element, element_color)
243
+
244
+ self
245
+ end
246
+
247
+ #
248
+ # Returns value of the element.
249
+ #
250
+ # @return [String]
251
+ #
252
+
253
+ def value
254
+ attribute_value('value') || ''
255
+ rescue Selenium::WebDriver::Error::InvalidElementStateError
256
+ ''
257
+ end
258
+
259
+ #
260
+ # Returns given attribute value of element.
261
+ #
262
+ # @example
263
+ # browser.a(id: "link_2").attribute_value "title"
264
+ # #=> "link_title_2"
265
+ #
266
+ # @param [String] attribute_name
267
+ # @return [String, nil]
268
+ #
269
+
270
+ def attribute_value(attribute_name)
271
+ assert_exists
272
+ element_call { @element.attribute attribute_name }
273
+ end
274
+
275
+ #
276
+ # Returns outer (inner + element itself) HTML code of element.
277
+ #
278
+ # @example
279
+ # browser.div(id: 'foo').outer_html
280
+ # #=> "<div id=\"foo\"><a href=\"#\">hello</a></div>"
281
+ #
282
+ # @return [String]
283
+ #
284
+
285
+ def outer_html
286
+ assert_exists
287
+ element_call { execute_atom(:getOuterHtml, @element) }.strip
288
+ end
289
+
290
+ alias_method :html, :outer_html
291
+
292
+ #
293
+ # Returns inner HTML code of element.
294
+ #
295
+ # @example
296
+ # browser.div(id: 'foo').inner_html
297
+ # #=> "<a href=\"#\">hello</a>"
298
+ #
299
+ # @return [String]
300
+ #
301
+
302
+ def inner_html
303
+ assert_exists
304
+ element_call { execute_atom(:getInnerHtml, @element) }.strip
305
+ end
306
+
307
+ #
308
+ # Sends sequence of keystrokes to element.
309
+ #
310
+ # @example
311
+ # browser.text_field(name: "new_user_first_name").send_keys "Watir", :return
312
+ #
313
+ # @param [String, Symbol] *args
314
+ #
315
+
316
+ def send_keys(*args)
317
+ assert_exists
318
+ assert_writable
319
+ element_call { @element.send_keys(*args) }
320
+ end
321
+
322
+ #
323
+ # Focuses element.
324
+ # Note that Firefox queues focus events until the window actually has focus.
325
+ #
326
+ # @see http://code.google.com/p/selenium/issues/detail?id=157
327
+ #
328
+
329
+ def focus
330
+ assert_exists
331
+ element_call { driver.execute_script "return arguments[0].focus()", @element }
332
+ end
333
+
334
+ #
335
+ # Returns true if this element is focused.
336
+ #
337
+ # @return [Boolean]
338
+ #
339
+
340
+ def focused?
341
+ assert_exists
342
+ element_call { @element == driver.switch_to.active_element }
343
+ end
344
+
345
+ #
346
+ # Simulates JavaScript events on element.
347
+ # Note that you may omit "on" from event name.
348
+ #
349
+ # @example
350
+ # browser.button(name: "new_user_button").fire_event :click
351
+ # browser.button(name: "new_user_button").fire_event "mousemove"
352
+ # browser.button(name: "new_user_button").fire_event "onmouseover"
353
+ #
354
+ # @param [String, Symbol] event_name
355
+ #
356
+
357
+ def fire_event(event_name)
358
+ assert_exists
359
+ event_name = event_name.to_s.sub(/^on/, '').downcase
360
+
361
+ element_call { execute_atom :fireEvent, @element, event_name }
362
+ end
363
+
364
+ #
365
+ # Returns parent element of current element.
366
+ #
367
+
368
+ def parent
369
+ assert_exists
370
+
371
+ e = element_call { execute_atom :getParentElement, @element }
372
+
373
+ if e.kind_of?(Selenium::WebDriver::Element)
374
+ Watir.element_class_for(e.tag_name.downcase).new(@parent, element: e)
375
+ end
376
+ end
377
+
378
+ #
379
+ # @api private
380
+ #
381
+
382
+ def driver
383
+ @parent.driver
384
+ end
385
+
386
+ #
387
+ # @api private
388
+ #
389
+
390
+ def wd
391
+ assert_exists
392
+ @element
393
+ end
394
+
395
+ #
396
+ # Returns true if this element is visible on the page.
397
+ #
398
+ # @return [Boolean]
399
+ #
400
+
401
+ def visible?
402
+ assert_exists
403
+ element_call { @element.displayed? }
404
+ end
405
+
406
+ #
407
+ # Returns true if this element is present and enabled on the page.
408
+ #
409
+ # @return [Boolean]
410
+ #
411
+
412
+ def enabled?
413
+ assert_exists
414
+ element_call { @element.enabled? }
415
+ end
416
+
417
+ #
418
+ # Returns true if the element exists and is visible on the page.
419
+ #
420
+ # @return [Boolean]
421
+ # @see Watir::Wait
422
+ #
423
+
424
+ def present?
425
+ exists? && visible?
426
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError, UnknownObjectException
427
+ # if the element disappears between the exists? and visible? calls,
428
+ # consider it not present.
429
+ false
430
+ end
431
+
432
+ #
433
+ # Returns given style property of this element.
434
+ #
435
+ # @example
436
+ # browser.button(value: "Delete").style #=> "border: 4px solid red;"
437
+ # browser.button(value: "Delete").style("border") #=> "4px solid rgb(255, 0, 0)"
438
+ #
439
+ # @param [String] property
440
+ # @return [String]
441
+ #
442
+
443
+ def style(property = nil)
444
+ if property
445
+ assert_exists
446
+ element_call { @element.style property }
447
+ else
448
+ attribute_value("style").to_s.strip
449
+ end
450
+ end
451
+
452
+ #
453
+ # Cast this Element instance to a more specific subtype.
454
+ #
455
+ # @example
456
+ # browser.element(xpath: "//input[@type='submit']").to_subtype
457
+ # #=> #<Watir::Button>
458
+ #
459
+
460
+ def to_subtype
461
+ elem = wd()
462
+ tag_name = elem.tag_name.downcase
463
+
464
+ klass = nil
465
+
466
+ if tag_name == "input"
467
+ klass = case elem.attribute(:type)
468
+ when *Button::VALID_TYPES
469
+ Button
470
+ when 'checkbox'
471
+ CheckBox
472
+ when 'radio'
473
+ Radio
474
+ when 'file'
475
+ FileField
476
+ else
477
+ TextField
478
+ end
479
+ else
480
+ klass = Watir.element_class_for(tag_name)
481
+ end
482
+
483
+ klass.new(@parent, element: elem)
484
+ end
485
+
486
+ #
487
+ # Returns browser.
488
+ #
489
+ # @return [Watir::Browser]
490
+ #
491
+
492
+ def browser
493
+ @parent.browser
494
+ end
495
+
496
+ protected
497
+
498
+ # Ensure that the element exists, making sure that it is not stale and located if necessary
499
+ def assert_exists
500
+ @element ||= @selector[:element]
501
+
502
+ if @element
503
+ ensure_not_stale # ensure not stale
504
+ else
505
+ @element = locate
506
+ end
507
+
508
+ assert_element_found
509
+ end
510
+
511
+ # Ensure that the element isn't stale, by relocating if it is (unless always_locate = false)
512
+ def ensure_not_stale
513
+ # Performance shortcut; only need recursive call to ensure context if stale in current context
514
+ return unless stale?
515
+
516
+ ensure_context
517
+ if stale?
518
+ if Watir.always_locate? && !@selector[:element]
519
+ @element = locate
520
+ else
521
+ reset!
522
+ end
523
+ end
524
+ assert_element_found
525
+ end
526
+
527
+ def stale?
528
+ @element.enabled? # any wire call will check for staleness
529
+ false
530
+ rescue Selenium::WebDriver::Error::ObsoleteElementError
531
+ true
532
+ end
533
+
534
+ def assert_element_found
535
+ unless @element
536
+ raise UnknownObjectException, "unable to locate element, using #{selector_string}"
537
+ end
538
+ end
539
+
540
+ def reset!
541
+ @element = nil
542
+ end
543
+
544
+ def locate
545
+ ensure_context
546
+
547
+ element_validator = element_validator_class.new
548
+ selector_builder = selector_builder_class.new(@parent, @selector, self.class.attribute_list)
549
+ locator = locator_class.new(@parent, @selector, selector_builder, element_validator)
550
+
551
+ locator.locate
552
+ end
553
+
554
+ private
555
+
556
+ def locator_class
557
+ Kernel.const_get("#{Watir.locator_namespace}::#{element_class_name}::Locator")
558
+ rescue NameError
559
+ Kernel.const_get("#{Watir.locator_namespace}::Element::Locator")
560
+ end
561
+
562
+ def element_validator_class
563
+ Kernel.const_get("#{Watir.locator_namespace}::#{element_class_name}::Validator")
564
+ rescue NameError
565
+ Kernel.const_get("#{Watir.locator_namespace}::Element::Validator")
566
+ end
567
+
568
+ def selector_builder_class
569
+ Kernel.const_get("#{Watir.locator_namespace}::#{element_class_name}::SelectorBuilder")
570
+ rescue NameError
571
+ Kernel.const_get("#{Watir.locator_namespace}::Element::SelectorBuilder")
572
+ end
573
+
574
+ def element_class_name
575
+ self.class.name.split('::').last
576
+ end
577
+
578
+ def selector_string
579
+ @selector.inspect
580
+ end
581
+
582
+ # Ensure the driver is in the desired browser context
583
+ def ensure_context
584
+ @parent.is_a?(IFrame) ? @parent.switch_to! : @parent.assert_exists
585
+ end
586
+
587
+ def attribute?(attribute)
588
+ assert_exists
589
+ element_call do
590
+ !!execute_atom(:getAttribute, @element, attribute.to_s.downcase)
591
+ end
592
+ end
593
+
594
+ def assert_enabled
595
+ unless element_call { @element.enabled? }
596
+ raise ObjectDisabledException, "object is disabled #{selector_string}"
597
+ end
598
+ end
599
+
600
+ def assert_writable
601
+ assert_enabled
602
+
603
+ if respond_to?(:readonly?) && readonly?
604
+ raise ObjectReadOnlyException, "object is read only #{selector_string}"
605
+ end
606
+ end
607
+
608
+ def assert_has_input_devices_for(name)
609
+ unless driver.kind_of? Selenium::WebDriver::DriverExtensions::HasInputDevices
610
+ raise NotImplementedError, "#{self.class}##{name} is not supported by this driver"
611
+ end
612
+ end
613
+
614
+ def assert_is_element(obj)
615
+ unless obj.kind_of? Watir::Element
616
+ raise TypeError, "execpted Watir::Element, got #{obj.inspect}:#{obj.class}"
617
+ end
618
+ end
619
+
620
+ def element_call
621
+ yield
622
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError
623
+ if Watir.always_locate? && !@selector[:element]
624
+ @element = locate
625
+ else
626
+ reset!
627
+ end
628
+ assert_element_found
629
+ retry
630
+ end
631
+
632
+ def method_missing(meth, *args, &blk)
633
+ method = meth.to_s
634
+ if method =~ Locators::Element::SelectorBuilder::WILDCARD_ATTRIBUTE
635
+ attribute_value(method.tr('_', '-'), *args)
636
+ else
637
+ super
638
+ end
639
+ end
640
+
641
+ def respond_to_missing?(meth, *)
642
+ Locators::Element::SelectorBuilder::WILDCARD_ATTRIBUTE === meth.to_s || super
643
+ end
644
+
645
+ end # Element
646
+ end # Watir