watir-classic 3.3.0 → 3.4.0

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 (51) hide show
  1. data/CHANGES +17 -0
  2. data/Gemfile.lock +9 -8
  3. data/LICENSE +1 -0
  4. data/README.rdoc +6 -7
  5. data/Rakefile +3 -1
  6. data/VERSION +1 -1
  7. data/lib/watir-classic.rb +0 -5
  8. data/lib/watir-classic/browser.rb +58 -35
  9. data/lib/watir-classic/browsers.rb +1 -1
  10. data/lib/watir-classic/container.rb +39 -33
  11. data/lib/watir-classic/cookies.rb +32 -2
  12. data/lib/watir-classic/core.rb +0 -1
  13. data/lib/watir-classic/dialogs/alert.rb +12 -0
  14. data/lib/watir-classic/dialogs/file_field.rb +11 -0
  15. data/lib/watir-classic/drag_and_drop_helper.rb +14 -0
  16. data/lib/watir-classic/element.rb +292 -257
  17. data/lib/watir-classic/element_collection.rb +26 -8
  18. data/lib/watir-classic/element_extensions.rb +22 -16
  19. data/lib/watir-classic/exceptions.rb +4 -4
  20. data/lib/watir-classic/form.rb +52 -49
  21. data/lib/watir-classic/frame.rb +23 -14
  22. data/lib/watir-classic/ie-class.rb +363 -315
  23. data/lib/watir-classic/ie-process.rb +1 -0
  24. data/lib/watir-classic/ie.rb +0 -17
  25. data/lib/watir-classic/image.rb +58 -64
  26. data/lib/watir-classic/input_elements.rb +224 -219
  27. data/lib/watir-classic/link.rb +14 -15
  28. data/lib/watir-classic/locator.rb +12 -7
  29. data/lib/watir-classic/matches.rb +7 -3
  30. data/lib/watir-classic/modal_dialog.rb +38 -26
  31. data/lib/watir-classic/non_control_elements.rb +29 -0
  32. data/lib/watir-classic/options.rb +10 -15
  33. data/lib/watir-classic/page-container.rb +30 -48
  34. data/lib/watir-classic/process.rb +4 -2
  35. data/lib/watir-classic/screenshot.rb +6 -0
  36. data/lib/watir-classic/supported_elements.rb +36 -14
  37. data/lib/watir-classic/table.rb +81 -71
  38. data/lib/watir-classic/util.rb +9 -11
  39. data/lib/watir-classic/wait.rb +17 -4
  40. data/lib/watir-classic/wait_helper.rb +15 -2
  41. data/lib/watir-classic/win32.rb +2 -1
  42. data/lib/watir-classic/window.rb +35 -7
  43. data/lib/watir-classic/xpath_locator.rb +1 -0
  44. data/lib/watir-classic/yard/global_macros.rb +7 -0
  45. data/spec/frame_spec.rb +17 -0
  46. metadata +5 -7
  47. data/lib/watir-classic/close_all.rb +0 -31
  48. data/lib/watir-classic/contrib/enabled_popup.rb +0 -21
  49. data/lib/watir-classic/contrib/ie-new-process.rb +0 -27
  50. data/lib/watir-classic/contrib/page_checker.rb +0 -29
  51. data/watir.gif +0 -0
@@ -1,6 +1,7 @@
1
1
  require "uri"
2
2
 
3
3
  module Watir
4
+ # Returned by {IE#cookies}.
4
5
  class Cookies
5
6
  include Enumerable
6
7
 
@@ -8,6 +9,15 @@ module Watir
8
9
  @page_container = page_container
9
10
  end
10
11
 
12
+ # Iterate over each cookie.
13
+ #
14
+ # @example
15
+ # browser.cookies.each do |cookie|
16
+ # puts cookie[:name]
17
+ # puts cookie[:value]
18
+ # end
19
+ #
20
+ # @yieldparam [Hash] cookie name and value pair of the cookie.
11
21
  def each
12
22
  @page_container.document.cookie.split(";").each do |cookie|
13
23
  name, value = cookie.strip.split("=")
@@ -15,7 +25,21 @@ module Watir
15
25
  end
16
26
  end
17
27
 
18
- def add name, value, options={}
28
+ # Add a cookie.
29
+ #
30
+ # @example Add a cookie with default options:
31
+ # browser.cookies.add "name", "value'
32
+ #
33
+ # @example Add a cookie with options:
34
+ # browser.cookie.add "name", "value", :expires => Time.now, :secure => true, :path => "/foo/bar"
35
+ #
36
+ # @param [String] name name of the cookie.
37
+ # @param [String] value value of the cookie.
38
+ # @param [Hash] options options for the cookie.
39
+ # @option options [Time] :expires Expiration time.
40
+ # @option options [Boolean] :secure (false) Secure flag. Set when value is true.
41
+ # @option options [String] :path Path for cookie.
42
+ def add(name, value, options={})
19
43
  options = options.map do |option|
20
44
  k, v = option
21
45
  if k == :expires
@@ -31,7 +55,12 @@ module Watir
31
55
  @page_container.document.cookie = "#{name}=#{value}#{options}"
32
56
  end
33
57
 
34
- def delete name
58
+ # Delete a cookie.
59
+ #
60
+ # @note does not raise any exceptions when cookie with the specified name is not found.
61
+ #
62
+ # @param [String] name Cookie with the specified name to be deleted.
63
+ def delete(name)
35
64
  options = {:expires => ::Time.now - 60 * 60 * 24}
36
65
  delete_with_options name, options
37
66
 
@@ -68,6 +97,7 @@ module Watir
68
97
  end
69
98
  end
70
99
 
100
+ # Delete all cookies for the page.
71
101
  def clear
72
102
  each {|cookie| delete cookie[:name]}
73
103
  end
@@ -3,7 +3,6 @@
3
3
  require 'timeout'
4
4
  require 'watir-classic/win32ole'
5
5
 
6
- # these are required already in commonwatir-classic, but not when using click_no_wait!
7
6
  require 'watir-classic/util'
8
7
  require 'watir-classic/exceptions'
9
8
  require 'watir-classic/matches'
@@ -1,33 +1,45 @@
1
1
  module Watir
2
+ # Handle different JavaScript dialogs (alert, prompt and confirm).
3
+ # Returned by {Container#alert}.
2
4
  class Alert
3
5
  include ElementExtensions
4
6
 
7
+ # JavaScript dialog titles to search for.
8
+ #
9
+ # @example When the title of your IE dialog is missing, add a new one:
10
+ # Watir::Alert::WINDOW_TITLES << "My missing title"
5
11
  WINDOW_TITLES = ['Message from webpage', 'Windows Internet Explorer', 'Microsoft Internet Explorer', /Mensaje de p.*/, "Explorer User Prompt"]
6
12
 
7
13
  def initialize(container)
8
14
  @container = container
9
15
  end
10
16
 
17
+ # @return [Boolean] true when JavaScript dialog exists and is visible, false otherwise.
11
18
  def exists?
12
19
  dialog.present?
13
20
  end
14
21
 
15
22
  alias_method :present?, :exists?
16
23
 
24
+ # Close the JavaScript dialog.
17
25
  def close
18
26
  dialog.close
19
27
  wait_until_not_exists
20
28
  end
21
29
 
30
+ # @return [String] the visible text of the JavaScript dialog.
22
31
  def text
23
32
  dialog.text
24
33
  end
25
34
 
35
+ # Press the "OK" button on the JavaScript dialog.
26
36
  def ok
27
37
  dialog.button(:value => "OK").click
28
38
  wait_until_not_exists
29
39
  end
30
40
 
41
+ # Set the text on the JavaScript prompt dialog.
42
+ # @param [String] text text to set.
31
43
  def set(text)
32
44
  dialog.text_field.set text
33
45
  end
@@ -1,5 +1,14 @@
1
1
  module Watir
2
+ # Returned by {Container#file_field}.
2
3
  class FileField < InputElement
4
+ # Set the path of the file field.
5
+ #
6
+ # @example
7
+ # browser.file_field.set("c:/foo/bar.txt")
8
+ #
9
+ # @param [String] file_path absolute path to existing file.
10
+ # @macro exists
11
+ # @raise [Errno::ENOENT] when file does not exist.
3
12
  def set(file_path)
4
13
  assert_file_exists(file_path)
5
14
  assert_exists
@@ -11,6 +20,8 @@ module Watir
11
20
 
12
21
  alias_method :value=, :set
13
22
 
23
+ private
24
+
14
25
  def assert_file_exists(file_path)
15
26
  raise Errno::ENOENT, "#{file_path} has to exist to set!" unless File.exists?(file_path)
16
27
  end
@@ -1,6 +1,14 @@
1
1
  module Watir
2
+ # This module has methods for performing drag & drop functionality. It is
3
+ # included into {Element} making each element draggable if they're draggable
4
+ # on the web page too.
2
5
  module DragAndDropHelper
3
6
 
7
+ # Drag and drop this element onto another element.
8
+ #
9
+ # @param [Element] target element to be dragged on.
10
+ # @macro exists
11
+ # @macro enabled
4
12
  def drag_and_drop_on(target)
5
13
  perform_action do
6
14
  assert_target target
@@ -12,6 +20,12 @@ module Watir
12
20
  end
13
21
  end
14
22
 
23
+ # Drag and drop this element by a distance.
24
+ #
25
+ # @param [Fixnum] distance_x distance to drag element on x-axis. Can be negative.
26
+ # @param [Fixnum] distance_y distance to drag element on y-axis. Can be negative.
27
+ # @macro exists
28
+ # @macro enabled
15
29
  def drag_and_drop_by(distance_x, distance_y)
16
30
  perform_action do
17
31
  drag_to do |mouse|
@@ -10,7 +10,36 @@ module Watir
10
10
 
11
11
  attr_accessor :container
12
12
 
13
+ class << self
14
+
15
+ private
16
+
17
+ # @!macro attr_ole
18
+ # @!method $1
19
+ # Retrieve element's $1 from the $2 OLE method.
20
+ # @see http://msdn.microsoft.com/en-us/library/hh773183(v=vs.85).aspx MSDN Documentation
21
+ # @return [String, Boolean, Fixnum] element's "$1" attribute value.
22
+ # Return type depends of the attribute type.
23
+ # @return [String] an empty String if the "$1" attribute does not exist.
24
+ # @macro exists
25
+ def attr_ole(method_name, ole_method_name=nil)
26
+ class_eval %Q[
27
+ def #{method_name}
28
+ assert_exists
29
+ ole_method_name = '#{ole_method_name || method_name.to_s.gsub(/\?$/, '')}'
30
+ ole_object.invoke(ole_method_name) rescue attribute_value(ole_method_name) || '' rescue ''
31
+ end]
32
+ end
33
+ end
34
+
35
+ attr_ole :id
36
+ attr_ole :title
37
+ attr_ole :class_name, :className
38
+ attr_ole :unique_number, :uniqueNumber
39
+ attr_ole :html, :outerHTML
40
+
13
41
  # number of spaces that separate the property from the value in the to_s method
42
+ # @private
14
43
  TO_S_SIZE = 14
15
44
 
16
45
  def initialize(container, specifiers)
@@ -29,66 +58,33 @@ module Watir
29
58
 
30
59
  alias_method :eql?, :==
31
60
 
32
- def locate
33
- @o = @container.locator_for(TaggedElementLocator, @specifiers, self.class).locate
34
- end
35
-
36
- # Return the ole object, allowing any methods of the DOM that Watir doesn't support to be used.
37
- def ole_object
38
- @o
39
- end
40
-
41
- def ole_object=(o)
42
- @o = o
43
- end
61
+ # @return [WIN32OLE] OLE object of the element, allowing any methods of the DOM
62
+ # that Watir doesn't support to be used.
63
+ def ole_object
64
+ @o
65
+ end
44
66
 
45
67
  def inspect
46
68
  '#<%s:0x%x located=%s specifiers=%s>' % [self.class, hash*2, !!ole_object, @specifiers.inspect]
47
69
  end
48
70
 
49
- private
50
-
51
- def self.attr_ole(method_name, ole_method_name=nil)
52
- class_eval %Q[
53
- def #{method_name}
54
- assert_exists
55
- ole_method_name = '#{ole_method_name || method_name.to_s.gsub(/\?$/, '')}'
56
- ole_object.invoke(ole_method_name) rescue attribute_value(ole_method_name) || '' rescue ''
57
- end]
58
- end
59
-
60
- public
61
-
62
- def assert_exists
63
- locate
64
- unless ole_object
65
- exception_class = self.is_a?(Frame) ? UnknownFrameException : UnknownObjectException
66
- raise exception_class.new(Watir::Exception.message_for_unable_to_locate(@specifiers))
67
- end
68
- end
69
-
70
- def assert_enabled
71
- raise ObjectDisabledException, "object #{@specifiers.inspect} is disabled" unless enabled?
71
+ def to_s
72
+ assert_exists
73
+ string_creator.join("\n")
72
74
  end
73
75
 
74
- # return the id of the element
75
- attr_ole :id
76
- # return the title of the element
77
- attr_ole :title
78
- # return the class name of the element
79
- # raise an ObjectNotFound exception if the object cannot be found
80
- attr_ole :class_name, :className
81
- # return the unique COM number for the element
82
- attr_ole :unique_number, :uniqueNumber
83
- # Return the outer html of the object - see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/outerhtml.asp?frame=true
84
- attr_ole :html, :outerHTML
85
-
76
+ # @return [String] element's html tag name in downcase.
77
+ # @macro exists
86
78
  def tag_name
87
79
  assert_exists
88
80
  @o.tagName.downcase
89
81
  end
90
82
 
91
- # returns specific Element subclass for current Element
83
+ # Cast {Element} into specific subclass.
84
+ # @example Convert div element to {Div} class:
85
+ # browser.element(:tag_name => "div").to_subtype # => Watir::Div
86
+ # @return {Element} element casted into specific sub-class of Element.
87
+ # @macro exists
92
88
  def to_subtype
93
89
  assert_exists
94
90
 
@@ -106,44 +102,46 @@ module Watir
106
102
  end
107
103
  end
108
104
 
109
- # send keys to element
105
+ # Send keys to the element
106
+ # @example
107
+ # browser.text_field.send_keys "hello", [:control, "a"], :backspace
108
+ # @param [String, Array<Symbol, String>, Symbol] keys Keys to send to the element.
109
+ # @see https://github.com/jarmo/RAutomation/blob/master/lib/rautomation/adapter/win_32/window.rb RAutomation::Window#send_keys documentation.
110
110
  def send_keys(*keys)
111
111
  focus
112
112
  page_container.send_keys *keys
113
113
  end
114
114
 
115
- # return the css style as a string
115
+ # Retrieve element's css style.
116
+ # @param [String] property When property is specified then only css for that property is returned.
117
+ # @return [String] css style as a one long String.
118
+ # @return [String] css style for specified property if property parameter is specified.
119
+ # @macro exists
116
120
  def style(property=nil)
117
121
  assert_exists
118
122
  css = ole_object.style.cssText
119
123
 
120
124
  if property
121
- properties = Hash[css.downcase.split(";").map { |p| p.split(":").map(&:strip) }]
122
- properties[property]
125
+ properties = Hash[css.downcase.split(";").map { |p| p.split(":").map(&:strip) }]
126
+ properties[property]
123
127
  else
124
128
  css
125
129
  end
126
130
  end
127
131
 
128
- # Return the innerText of the object or an empty string if the object is
129
- # not visible
130
- # Raise an ObjectNotFound exception if the object cannot be found
132
+ # The text value of the element between html tags.
133
+ # @return [String] element's text.
134
+ # @return [String] empty String when element is not visible.
135
+ # @macro exists
131
136
  def text
132
137
  assert_exists
133
138
  visible? ? ole_object.innerText.strip : ""
134
139
  end
135
140
 
136
- def __ole_inner_elements
137
- assert_exists
138
- ole_object.all
139
- end
140
-
141
- def document
142
- assert_exists
143
- ole_object
144
- end
145
-
146
- # Return the element immediately containing self.
141
+ # Retrieve the element immediately containing self.
142
+ # @return [Element] parent element of self.
143
+ # @return [Element] self when parent element does not exist.
144
+ # @macro exists
147
145
  def parent
148
146
  assert_exists
149
147
  parent_element = ole_object.parentelement
@@ -151,202 +149,58 @@ module Watir
151
149
  Element.new(self, :ole_object => parent_element).to_subtype
152
150
  end
153
151
 
154
- def typingspeed
155
- @container.typingspeed
156
- end
157
-
158
- def type_keys
159
- @type_keys || @container.type_keys
160
- end
161
-
162
- def activeObjectHighLightColor
163
- @container.activeObjectHighLightColor
164
- end
165
-
166
- # Return an array with many of the properties, in a format to be used by the to_s method
167
- def string_creator
168
- n = []
169
- n << "id:".ljust(TO_S_SIZE) + self.id.to_s
170
- return n
171
- end
172
-
173
- private :string_creator
174
-
175
- # Display basic details about the object. Sample output for a button is shown.
176
- # Raises UnknownObjectException if the object is not found.
177
- # name b4
178
- # type button
179
- # id b5
180
- # value Disabled Button
181
- # disabled true
182
- def to_s
183
- assert_exists
184
- return string_creator.join("\n")
185
- end
186
-
187
- # This method is responsible for setting and clearing the colored highlighting on the currently active element.
188
- # use :set to set the highlight
189
- # :clear to clear the highlight
190
- # TODO: Make this two methods: set_highlight & clear_highlight
191
- # TODO: Remove begin/rescue blocks
192
- def highlight(set_or_clear)
193
- if set_or_clear == :set
194
- begin
195
- @original_color ||= ole_object.style.backgroundColor
196
- ole_object.style.backgroundColor = @container.activeObjectHighLightColor
197
- rescue
198
- @original_color = nil
199
- end
200
- else
201
- begin
202
- ole_object.style.backgroundColor = @original_color if @original_color
203
- rescue
204
- # we could be here for a number of reasons...
205
- # e.g. page may have reloaded and the reference is no longer valid
206
- ensure
207
- @original_color = nil
208
- end
209
- end
210
- end
211
-
212
- private :highlight
213
-
214
- # This method clicks the active element.
215
- # raises: UnknownObjectException if the object is not found
216
- # ObjectDisabledException if the object is currently disabled
152
+ # Performs a left click on the element.
153
+ # Will wait automatically until browser is ready after the click if page load was triggered for example.
154
+ # @macro exists
155
+ # @macro enabled
217
156
  def click
218
157
  click!
219
158
  @container.wait
220
159
  end
221
160
 
161
+ # Performs a right click on the element.
162
+ # Will wait automatically until browser is ready after the click if page load was triggered for example.
163
+ # @macro enabled
164
+ # @macro exists
222
165
  def right_click
223
166
  perform_action {fire_event("oncontextmenu"); @container.wait}
224
167
  end
225
168
 
169
+ # Performs a double click on the element.
170
+ # Will wait automatically until browser is ready after the click if page load was triggered for example.
171
+ # @macro exists
172
+ # @macro enabled
226
173
  def double_click
227
174
  perform_action {fire_event("ondblclick"); @container.wait}
228
175
  end
229
176
 
230
- def replace_method(method)
231
- method == 'click' ? 'click!' : method
232
- end
233
-
234
- private :replace_method
235
-
236
- def build_method(method_name, *args)
237
- arguments = args.map do |argument|
238
- if argument.is_a?(String)
239
- argument = "'#{argument}'"
240
- else
241
- argument = argument.inspect
242
- end
243
- end
244
- "#{replace_method(method_name)}(#{arguments.join(',')})"
245
- end
246
-
247
- private :build_method
248
-
249
- def generate_ruby_code(element, method_name, *args)
250
- # needs to be done like this to avoid segfault on ruby 1.9.3
251
- tag_name = @specifiers[:tag_name].join("' << '")
252
- element = "#{self.class}.new(#{@page_container.attach_command}, :tag_name => Array.new << '#{tag_name}', :unique_number => #{unique_number})"
253
- method = build_method(method_name, *args)
254
- ruby_code = "$:.unshift(#{$LOAD_PATH.map {|p| "'#{p}'" }.join(").unshift(")});" <<
255
- "require '#{File.expand_path(File.dirname(__FILE__))}/core';#{element}.#{method};"
256
- ruby_code
257
- end
258
-
259
- private :generate_ruby_code
260
-
261
- def spawned_no_wait_command(command)
262
- command = "-e #{command.inspect}"
263
- unless $DEBUG
264
- "start rubyw #{command}"
265
- else
266
- puts "#no_wait command:"
267
- command = "ruby #{command}"
268
- puts command
269
- command
270
- end
271
- end
272
-
273
- private :spawned_no_wait_command
274
-
275
- def click!
276
- perform_action do
277
- # Not sure why but in IE9 Document mode, passing a parameter
278
- # to click seems to work. Firing the onClick event breaks other tests
279
- # so this seems to be the safest change and also works fine in IE8
280
- ole_object.click(0)
281
- end
282
- end
283
-
284
- # Flash the element the specified number of times.
285
- # Defaults to 10 flashes.
286
- def flash number=10
177
+ # Flash the element the specified number of times for troubleshooting purposes.
178
+ # @param [Fixnum] number Number times to flash the element.
179
+ # @macro exists
180
+ def flash(number=10)
287
181
  assert_exists
288
182
  number.times do
289
- highlight(:set)
183
+ set_highlight
290
184
  sleep 0.05
291
- highlight(:clear)
185
+ clear_highlight
292
186
  sleep 0.05
293
187
  end
294
- nil
188
+ self
295
189
  end
296
190
 
297
- # Executes a user defined "fireEvent" for objects with JavaScript events tied to them such as DHTML menus.
298
- # usage: allows a generic way to fire javascript events on page objects such as "onMouseOver", "onClick", etc.
299
- # raises: UnknownObjectException if the object is not found
300
- # ObjectDisabledException if the object is currently disabled
191
+ # Executes a user defined "fireEvent" for element with JavaScript events.
192
+ #
193
+ # @example Fire a onchange event on select_list:
194
+ # browser.select_list.fire_event "onchange"
195
+ #
196
+ # @macro exists
301
197
  def fire_event(event)
302
198
  perform_action {dispatch_event(event); @container.wait}
303
199
  end
304
200
 
305
- def dispatch_event(event)
306
- if IE.version_parts.first.to_i >= 9 && container.page_container.document.documentMode.to_i >= 9
307
- ole_object.dispatchEvent(create_event(event))
308
- else
309
- ole_object.fireEvent(event)
310
- end
311
- end
312
-
313
- def create_event(event)
314
- event =~ /on(.*)/i
315
- event = $1 if $1
316
- event.downcase!
317
- # See http://www.howtocreate.co.uk/tutorials/javascript/domevents
318
- case event
319
- when 'abort', 'blur', 'change', 'error', 'focus', 'load',
320
- 'reset', 'resize', 'scroll', 'select', 'submit', 'unload'
321
- event_name = :initEvent
322
- event_type = 'HTMLEvents'
323
- event_args = [event, true, true]
324
- when 'select'
325
- event_name = :initUIEvent
326
- event_type = 'UIEvent'
327
- event_args = [event, true, true, @container.page_container.document.parentWindow.window,0]
328
- when 'keydown', 'keypress', 'keyup'
329
- event_name = :initKeyboardEvent
330
- event_type = 'KeyboardEvent'
331
- # 'type', bubbles, cancelable, windowObject, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode
332
- event_args = [event, true, true, @container.page_container.document.parentWindow.window, false, false, false, false, 0, 0]
333
- when 'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup',
334
- 'contextmenu', 'drag', 'dragstart', 'dragenter', 'dragover', 'dragleave', 'dragend', 'drop', 'selectstart'
335
- event_name = :initMouseEvent
336
- event_type = 'MouseEvents'
337
- # 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget
338
- event_args = [event, true, true, @container.page_container.document.parentWindow.window, 1, 0, 0, 0, 0, false, false, false, false, 0, @container.page_container.document]
339
- else
340
- raise UnhandledEventException, "Don't know how to trigger event '#{event}'"
341
- end
342
- event = @container.page_container.document.createEvent(event_type)
343
- event.send event_name, *event_args
344
- event
345
- end
346
-
347
- # This method sets focus on the active element.
348
- # raises: UnknownObjectException if the object is not found
349
- # ObjectDisabledException if the object is currently disabled
201
+ # Set focus on the element.
202
+ # @macro exists
203
+ # @macro enabled
350
204
  def focus
351
205
  assert_exists
352
206
  assert_enabled
@@ -354,13 +208,16 @@ module Watir
354
208
  ole_object.focus(0)
355
209
  end
356
210
 
211
+ # @return [Boolean] true when element is in focus, false otherwise.
212
+ # @macro exists
213
+ # @macro enabled
357
214
  def focused?
358
215
  assert_exists
359
216
  assert_enabled
360
217
  @page_container.document.activeElement.uniqueNumber == unique_number
361
218
  end
362
219
 
363
- # Returns whether this element actually exists.
220
+ # @return [Boolean] true when element exists, false otherwise.
364
221
  def exists?
365
222
  begin
366
223
  locate
@@ -372,22 +229,24 @@ module Watir
372
229
 
373
230
  alias :exist? :exists?
374
231
 
375
- # Returns true if the element is enabled, false if it isn't.
376
- # raises: UnknownObjectException if the object is not found
232
+ # @return [Boolean] true if the element is enabled, false otherwise.
233
+ # @macro exists
377
234
  def enabled?
378
235
  assert_exists
379
236
  !disabled?
380
237
  end
381
238
 
239
+ # @return [Boolean] true if the element is disabled, false otherwise.
240
+ # @macro exists
382
241
  def disabled?
383
242
  assert_exists
384
243
  false
385
244
  end
386
245
 
387
- # If any parent element isn't visible then we cannot write to the
388
- # element. The only realiable way to determine this is to iterate
389
- # up the DOM element tree checking every element to make sure it's
390
- # visible.
246
+ # Retrieve the status of element's visibility.
247
+ # When any parent element is not also visible then the current element is determined as not visible too.
248
+ # @return [Boolean] true if element is visible, false otherwise.
249
+ # @macro exists
391
250
  def visible?
392
251
  # Now iterate up the DOM element tree and return false if any
393
252
  # parent element isn't visible
@@ -409,22 +268,22 @@ module Watir
409
268
  end
410
269
 
411
270
  # Get attribute value for any attribute of the element.
412
- # Returns null if attribute doesn't exist.
271
+ # @return [String] the value of the attribute.
272
+ # @return [Object] nil if the attribute does not exist.
273
+ # @macro exists
413
274
  def attribute_value(attribute_name)
414
275
  assert_exists
415
276
  ole_object.getAttribute(attribute_name)
416
277
  end
417
278
 
418
- def perform_action
419
- assert_exists
420
- assert_enabled
421
- highlight(:set)
422
- yield
423
- highlight(:clear)
424
- end
425
-
426
- private :perform_action
427
-
279
+ # Make it possible to use *_no_wait commands and retrieve element html5 data-attribute
280
+ # values.
281
+ #
282
+ # @example Use click without waiting:
283
+ # browser.button.click_no_wait
284
+ #
285
+ # @example Retrieve html5 data attribute value:
286
+ # browser.div.data_model # => value of data-model="foo" html attribute
428
287
  def method_missing(method_name, *args, &block)
429
288
  meth = method_name.to_s
430
289
  if meth =~ /(.*)_no_wait/ && self.respond_to?($1)
@@ -438,6 +297,182 @@ module Watir
438
297
  super
439
298
  end
440
299
  end
441
-
300
+
301
+ # @private
302
+ def locate
303
+ @o = @container.locator_for(TaggedElementLocator, @specifiers, self.class).locate
304
+ end
305
+
306
+ # @private
307
+ def __ole_inner_elements
308
+ assert_exists
309
+ ole_object.all
310
+ end
311
+
312
+ # @private
313
+ def document
314
+ assert_exists
315
+ ole_object
316
+ end
317
+
318
+ # @private
319
+ def assert_exists
320
+ locate
321
+ unless ole_object
322
+ exception_class = self.is_a?(Frame) ? UnknownFrameException : UnknownObjectException
323
+ raise exception_class.new(Watir::Exception.message_for_unable_to_locate(@specifiers))
324
+ end
325
+ end
326
+
327
+ # @private
328
+ def assert_enabled
329
+ raise ObjectDisabledException, "object #{@specifiers.inspect} is disabled" unless enabled?
330
+ end
331
+
332
+ # @private
333
+ def typingspeed
334
+ @container.typingspeed
335
+ end
336
+
337
+ # @private
338
+ def type_keys
339
+ @type_keys || @container.type_keys
340
+ end
341
+
342
+ # @private
343
+ def active_object_highlight_color
344
+ @container.active_object_highlight_color
345
+ end
346
+
347
+ # @private
348
+ def click!
349
+ perform_action do
350
+ # Not sure why but in IE9 Document mode, passing a parameter
351
+ # to click seems to work. Firing the onClick event breaks other tests
352
+ # so this seems to be the safest change and also works fine in IE8
353
+ ole_object.click(0)
354
+ end
355
+ end
356
+
357
+ # @private
358
+ def dispatch_event(event)
359
+ if IE.version_parts.first.to_i >= 9 && container.page_container.document.documentMode.to_i >= 9
360
+ ole_object.dispatchEvent(create_event(event))
361
+ else
362
+ ole_object.fireEvent(event)
363
+ end
364
+ end
365
+
366
+ private
367
+
368
+ def create_event(event)
369
+ event =~ /on(.*)/i
370
+ event = $1 if $1
371
+ event.downcase!
372
+ # See http://www.howtocreate.co.uk/tutorials/javascript/domevents
373
+ case event
374
+ when 'abort', 'blur', 'change', 'error', 'focus', 'load',
375
+ 'reset', 'resize', 'scroll', 'select', 'submit', 'unload'
376
+ event_name = :initEvent
377
+ event_type = 'HTMLEvents'
378
+ event_args = [event, true, true]
379
+ when 'select'
380
+ event_name = :initUIEvent
381
+ event_type = 'UIEvent'
382
+ event_args = [event, true, true, @container.page_container.document.parentWindow.window,0]
383
+ when 'keydown', 'keypress', 'keyup'
384
+ event_name = :initKeyboardEvent
385
+ event_type = 'KeyboardEvent'
386
+ # 'type', bubbles, cancelable, windowObject, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode
387
+ event_args = [event, true, true, @container.page_container.document.parentWindow.window, false, false, false, false, 0, 0]
388
+ when 'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup',
389
+ 'contextmenu', 'drag', 'dragstart', 'dragenter', 'dragover', 'dragleave', 'dragend', 'drop', 'selectstart'
390
+ event_name = :initMouseEvent
391
+ event_type = 'MouseEvents'
392
+ # 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget
393
+ event_args = [event, true, true, @container.page_container.document.parentWindow.window, 1, 0, 0, 0, 0, false, false, false, false, 0, @container.page_container.document]
394
+ else
395
+ raise UnhandledEventException, "Don't know how to trigger event '#{event}'"
396
+ end
397
+ event = @container.page_container.document.createEvent(event_type)
398
+ event.send event_name, *event_args
399
+ event
400
+ end
401
+
402
+ # Return an array with many of the properties, in a format to be used by the to_s method
403
+ def string_creator
404
+ n = []
405
+ n << "id:".ljust(TO_S_SIZE) + self.id.to_s
406
+ n
407
+ end
408
+
409
+ # This method is responsible for setting colored highlighting on the currently active element.
410
+ def set_highlight
411
+ perform_highlight do
412
+ @original_color = ole_object.style.backgroundColor
413
+ ole_object.style.backgroundColor = @container.active_object_highlight_color
414
+ end
415
+ end
416
+
417
+ # This method is responsible for clearing colored highlighting on the currently active element.
418
+ def clear_highlight
419
+ perform_highlight do
420
+ ole_object.style.backgroundColor = @original_color if @original_color
421
+ end
422
+ end
423
+
424
+ def perform_highlight
425
+ yield
426
+ rescue
427
+ # we could be here for a number of reasons...
428
+ # e.g. page may have reloaded and the reference is no longer valid
429
+ end
430
+
431
+ def replace_method(method)
432
+ method == 'click' ? 'click!' : method
433
+ end
434
+
435
+ def build_method(method_name, *args)
436
+ arguments = args.map do |argument|
437
+ if argument.is_a?(String)
438
+ argument = "'#{argument}'"
439
+ else
440
+ argument = argument.inspect
441
+ end
442
+ end
443
+ "#{replace_method(method_name)}(#{arguments.join(',')})"
444
+ end
445
+
446
+ def generate_ruby_code(element, method_name, *args)
447
+ # needs to be done like this to avoid segfault on ruby 1.9.3
448
+ tag_name = @specifiers[:tag_name].join("' << '")
449
+ element = "#{self.class}.new(#{@page_container.attach_command}, :tag_name => Array.new << '#{tag_name}', :unique_number => #{unique_number})"
450
+ method = build_method(method_name, *args)
451
+ ruby_code = "$:.unshift(#{$LOAD_PATH.map {|p| "'#{p}'" }.join(").unshift(")});" <<
452
+ "require '#{File.expand_path(File.dirname(__FILE__))}/core';#{element}.#{method};"
453
+ ruby_code
454
+ end
455
+
456
+ def spawned_no_wait_command(command)
457
+ command = "-e #{command.inspect}"
458
+ unless $DEBUG
459
+ "start rubyw #{command}"
460
+ else
461
+ puts "#no_wait command:"
462
+ command = "ruby #{command}"
463
+ puts command
464
+ command
465
+ end
466
+ end
467
+
468
+ def perform_action
469
+ assert_exists
470
+ assert_enabled
471
+ set_highlight
472
+ yield
473
+ ensure
474
+ clear_highlight
475
+ end
476
+
442
477
  end
443
478
  end