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,163 @@
1
+ module Watir
2
+ class IFrame < HTMLElement
3
+
4
+ def locate
5
+ @parent.assert_exists
6
+
7
+ selector = @selector.merge(tag_name: frame_tag)
8
+ element_validator = element_validator_class.new
9
+ selector_builder = selector_builder_class.new(@parent, selector, self.class.attribute_list)
10
+ locator = locator_class.new(@parent, selector, selector_builder, element_validator)
11
+
12
+ element = locator.locate
13
+ element or raise UnknownFrameException, "unable to locate #{@selector[:tag_name]} using #{selector_string}"
14
+
15
+ FramedDriver.new(element, driver)
16
+ end
17
+
18
+ def switch_to!
19
+ locate.send :switch!
20
+ end
21
+
22
+ def assert_exists
23
+ if @selector.key? :element
24
+ raise UnknownFrameException, "wrapping a Selenium element as a Frame is not currently supported"
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ #
31
+ # Returns text of iframe body.
32
+ #
33
+ # @return [String]
34
+ #
35
+
36
+ def text
37
+ body.text
38
+ end
39
+
40
+ #
41
+ # Returns HTML code of iframe.
42
+ #
43
+ # @return [String]
44
+ #
45
+
46
+ def html
47
+ assert_exists
48
+ wd.page_source
49
+ end
50
+
51
+ def execute_script(*args)
52
+ browser.execute_script(*args)
53
+ end
54
+
55
+ private
56
+
57
+ def frame_tag
58
+ 'iframe'
59
+ end
60
+
61
+ end # IFrame
62
+
63
+
64
+ class IFrameCollection < ElementCollection
65
+
66
+ def to_a
67
+ # In case `#all_elements` returns empty array, but `#elements`
68
+ # returns non-empty array (i.e. any frame has loaded between these two calls),
69
+ # index will return nil. That's why `#all_elements` should always
70
+ # be called after `#elements.`
71
+ element_indexes = elements.map { |el| all_elements.index(el) }
72
+ element_indexes.map { |idx| element_class.new(@parent, tag_name: @selector[:tag_name], index: idx) }
73
+ end
74
+
75
+ private
76
+
77
+ def all_elements
78
+ selector = { tag_name: @selector[:tag_name] }
79
+
80
+ element_validator = element_validator_class.new
81
+ selector_builder = selector_builder_class.new(@parent, selector, element_class.attribute_list)
82
+ locator = locator_class.new(@parent, selector, selector_builder, element_validator)
83
+
84
+ locator.locate_all
85
+ end
86
+
87
+ end # IFrameCollection
88
+
89
+
90
+ class Frame < IFrame
91
+
92
+ private
93
+
94
+ def frame_tag
95
+ 'frame'
96
+ end
97
+
98
+ end # Frame
99
+
100
+
101
+ class FrameCollection < IFrameCollection
102
+ end # FrameCollection
103
+
104
+
105
+ module Container
106
+
107
+ def frame(*args)
108
+ Frame.new(self, extract_selector(args).merge(tag_name: "frame"))
109
+ end
110
+
111
+ def frames(*args)
112
+ FrameCollection.new(self, extract_selector(args).merge(tag_name: "frame"))
113
+ end
114
+
115
+ end # Container
116
+
117
+
118
+ # @api private
119
+ #
120
+ # another hack..
121
+ #
122
+
123
+ class FramedDriver
124
+ def initialize(element, driver)
125
+ @element = element
126
+ @driver = driver
127
+ end
128
+
129
+ def ==(other)
130
+ wd == other.wd
131
+ end
132
+ alias_method :eql?, :==
133
+
134
+ def send_keys(*args)
135
+ switch!
136
+ @driver.switch_to.active_element.send_keys(*args)
137
+ end
138
+
139
+ protected
140
+
141
+ def wd
142
+ @element
143
+ end
144
+
145
+ private
146
+
147
+ def method_missing(meth, *args, &blk)
148
+ if @driver.respond_to?(meth)
149
+ switch!
150
+ @driver.send(meth, *args, &blk)
151
+ else
152
+ @element.send(meth, *args, &blk)
153
+ end
154
+ end
155
+
156
+ def switch!
157
+ @driver.switch_to.frame @element
158
+ rescue Selenium::WebDriver::Error::NoSuchFrameError => e
159
+ raise Exception::UnknownFrameException, e.message
160
+ end
161
+
162
+ end # FramedDriver
163
+ end # Watir
@@ -0,0 +1,62 @@
1
+ module Watir
2
+ class Image < HTMLElement
3
+
4
+ #
5
+ # Returns true if image is loaded.
6
+ #
7
+ # @return [Boolean]
8
+ #
9
+
10
+ def loaded?
11
+ return false unless complete?
12
+
13
+ driver.execute_script(
14
+ 'return typeof arguments[0].naturalWidth != "undefined" && arguments[0].naturalWidth > 0',
15
+ @element
16
+ )
17
+ end
18
+
19
+ #
20
+ # Returns the image's width in pixels.
21
+ #
22
+ # @return [Fixnum] width
23
+ #
24
+
25
+ def width
26
+ assert_exists
27
+ driver.execute_script "return arguments[0].width", @element
28
+ end
29
+
30
+ #
31
+ # Returns the image's height in pixels.
32
+ #
33
+ # @return [Fixnum] width
34
+ #
35
+
36
+ def height
37
+ assert_exists
38
+ driver.execute_script "return arguments[0].height", @element
39
+ end
40
+
41
+ def file_created_date
42
+ assert_exists
43
+ raise NotImplementedError, "not currently supported by Selenium"
44
+ end
45
+
46
+ def file_size
47
+ assert_exists
48
+ raise NotImplementedError, "not currently supported by Selenium"
49
+ end
50
+
51
+ def save(path)
52
+ assert_exists
53
+ raise NotImplementedError, "not currently supported by Selenium"
54
+ end
55
+
56
+ end # Image
57
+
58
+ module Container
59
+ alias_method :image, :img
60
+ alias_method :images, :imgs
61
+ end # Container
62
+ end # Watir
@@ -0,0 +1,7 @@
1
+ module Watir
2
+ class Input < HTMLElement
3
+
4
+ alias_method :readonly?, :read_only?
5
+
6
+ end # Input
7
+ end # Watir
@@ -0,0 +1,18 @@
1
+ module Watir
2
+ module Container
3
+ alias_method :link, :a
4
+ alias_method :links, :as
5
+ end # Container
6
+
7
+
8
+ class Anchor < HTMLElement
9
+
10
+ #
11
+ # @todo temporarily add href attribute
12
+ #
13
+ # @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23192
14
+ #
15
+ attribute String, :href, :href
16
+
17
+ end # Anchor
18
+ end # Watir
@@ -0,0 +1,74 @@
1
+ module Watir
2
+
3
+ #
4
+ # Represents an option in a select list.
5
+ #
6
+
7
+ class Option < HTMLElement
8
+
9
+ #
10
+ # Selects this option.
11
+ #
12
+ # @example
13
+ # browser.select(id: "foo").options.first.select
14
+ #
15
+
16
+ alias_method :select, :click
17
+
18
+ #
19
+ # Toggles the selected state of this option.
20
+ #
21
+ # @example
22
+ # browser.select(id: "foo").options.first.toggle
23
+ #
24
+
25
+ alias_method :toggle, :click
26
+
27
+ #
28
+ # Clears (i.e. toggles selected state) option.
29
+ #
30
+ # @example
31
+ # browser.select(id: "foo").options.first.clear
32
+ #
33
+
34
+ def clear
35
+ click if selected?
36
+ end
37
+
38
+ #
39
+ # Is this option selected?
40
+ #
41
+ # @return [Boolean]
42
+ #
43
+
44
+ def selected?
45
+ assert_exists
46
+ element_call { @element.selected? }
47
+ end
48
+
49
+ #
50
+ # Returns the text of option.
51
+ #
52
+ # Note that the text is either one of the following respectively:
53
+ # * label attribute
54
+ # * text attribute
55
+ # * inner element text
56
+ #
57
+ # @return [String]
58
+ #
59
+
60
+ def text
61
+ # A little unintuitive - we'll return the 'label' or 'text' attribute if
62
+ # they exist, otherwise the inner text of the element
63
+
64
+ attribute = [:label, :text].find { |a| attribute? a }
65
+
66
+ if attribute
67
+ attribute_value(attribute)
68
+ else
69
+ super
70
+ end
71
+ end
72
+
73
+ end # Option
74
+ end # Watir
@@ -0,0 +1,42 @@
1
+ module Watir
2
+ class Radio < Input
3
+
4
+ #
5
+ # Selects this radio button.
6
+ #
7
+
8
+ def set
9
+ click unless set?
10
+ end
11
+
12
+ #
13
+ # Is this radio set?
14
+ #
15
+ # @return [Boolean]
16
+ #
17
+
18
+ def set?
19
+ assert_exists
20
+ element_call { @element.selected? }
21
+ end
22
+
23
+ end # Radio
24
+
25
+ module Container
26
+ def radio(*args)
27
+ Radio.new(self, extract_selector(args).merge(tag_name: "input", type: "radio"))
28
+ end
29
+
30
+ def radios(*args)
31
+ RadioCollection.new(self, extract_selector(args).merge(tag_name: "input", type: "radio" ))
32
+ end
33
+ end # Container
34
+
35
+ class RadioCollection < InputCollection
36
+ private
37
+
38
+ def element_class
39
+ Radio
40
+ end
41
+ end # RadioCollection
42
+ end # Watir
@@ -0,0 +1,17 @@
1
+ module Watir
2
+
3
+ #
4
+ # Custom class representing table row (tr).
5
+ #
6
+
7
+ class Row < TableRow
8
+ end # Row
9
+
10
+ class RowCollection < TableRowCollection
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(:rowIndex).to_i }
15
+ end
16
+ end
17
+ end # Watir
@@ -0,0 +1,238 @@
1
+ module Watir
2
+ class Select < HTMLElement
3
+ include Watir::Exception
4
+
5
+ #
6
+ # Clears all selected options.
7
+ #
8
+
9
+ def clear
10
+ assert_exists
11
+
12
+ raise Error, "you can only clear multi-selects" unless multiple?
13
+
14
+ options.each do |o|
15
+ click_option(o) if o.selected?
16
+ end
17
+ end
18
+
19
+ #
20
+ # Gets all the options in the select list
21
+ #
22
+ # @return [Watir::OptionCollection]
23
+ #
24
+
25
+ def options
26
+ assert_exists
27
+ super
28
+ end
29
+
30
+ #
31
+ # Returns true if the select list has one or more options where text or label matches the given value.
32
+ #
33
+ # @param [String, Regexp] str_or_rx
34
+ # @return [Boolean]
35
+ #
36
+
37
+ def include?(str_or_rx)
38
+ assert_exists
39
+
40
+ element_call do
41
+ @element.find_elements(:tag_name, 'option').any? do |e|
42
+ str_or_rx === e.text || str_or_rx === e.attribute(:label)
43
+ end
44
+ end
45
+ end
46
+
47
+ #
48
+ # Select the option(s) whose text or label matches the given string.
49
+ # If this is a multi-select and several options match the value given, all will be selected.
50
+ #
51
+ # @param [String, Regexp] str_or_rx
52
+ # @raise [Watir::Exception::NoValueFoundException] if the value does not exist.
53
+ # @return [String] The text of the option selected. If multiple options match, returns the first match.
54
+ #
55
+
56
+ def select(str_or_rx)
57
+ select_by :text, str_or_rx
58
+ end
59
+
60
+ #
61
+ # Selects the option(s) whose value attribute matches the given string.
62
+ #
63
+ # @see +select+
64
+ #
65
+ # @param [String, Regexp] str_or_rx
66
+ # @raise [Watir::Exception::NoValueFoundException] if the value does not exist.
67
+ # @return [String] The option selected. If multiple options match, returns the first match
68
+ #
69
+
70
+ def select_value(str_or_rx)
71
+ select_by :value, str_or_rx
72
+ end
73
+
74
+ #
75
+ # Returns true if any of the selected options' text or label matches the given value.
76
+ #
77
+ # @param [String, Regexp] str_or_rx
78
+ # @raise [Watir::Exception::UnknownObjectException] if the options do not exist
79
+ # @return [Boolean]
80
+ #
81
+
82
+ def selected?(str_or_rx)
83
+ assert_exists
84
+
85
+ match_found = false
86
+
87
+ element_call do
88
+ @element.find_elements(:tag_name, 'option').each do |e|
89
+ matched = str_or_rx === e.text || str_or_rx === e.attribute(:label)
90
+ if matched
91
+ return true if e.selected?
92
+ match_found = true
93
+ end
94
+ end
95
+ end
96
+
97
+ raise(UnknownObjectException, "Unable to locate option matching #{str_or_rx.inspect}") unless match_found
98
+
99
+ false
100
+ end
101
+
102
+ #
103
+ # Returns the value of the first selected option in the select list.
104
+ # Returns nil if no option is selected.
105
+ #
106
+ # @return [String, nil]
107
+ #
108
+
109
+ def value
110
+ o = options.find { |e| e.selected? } || return
111
+ o.value
112
+ end
113
+
114
+
115
+ #
116
+ # Returns an array of currently selected options.
117
+ #
118
+ # @return [Array<Watir::Option>]
119
+ #
120
+
121
+ def selected_options
122
+ options.select { |e| e.selected? }
123
+ end
124
+
125
+ private
126
+
127
+ def select_by(how, str_or_rx)
128
+ assert_exists
129
+
130
+ case str_or_rx
131
+ when String, Numeric
132
+ select_by_string(how, str_or_rx.to_s)
133
+ when Regexp
134
+ select_by_regexp(how, str_or_rx)
135
+ else
136
+ raise TypeError, "expected String or Regexp, got #{str_or_rx.inspect}:#{str_or_rx.class}"
137
+ end
138
+ end
139
+
140
+ def select_by_string(how, string)
141
+ xpath = option_xpath_for(how, string)
142
+
143
+ if multiple?
144
+ elements = element_call do
145
+ @element.find_elements(:xpath, xpath)
146
+ end
147
+ no_value_found(string) if elements.empty?
148
+
149
+ elements.each { |e| click_option(e) unless e.selected? }
150
+ elements.first.text
151
+ else
152
+ begin
153
+ e = element_call do
154
+ @element.find_element(:xpath, xpath)
155
+ end
156
+ rescue Selenium::WebDriver::Error::NoSuchElementError
157
+ no_value_found(string)
158
+ end
159
+
160
+ click_option(e) unless e.selected?
161
+ safe_text(e)
162
+ end
163
+ end
164
+
165
+ def select_by_regexp(how, exp)
166
+ elements = element_call do
167
+ @element.find_elements(:tag_name, 'option')
168
+ end
169
+ no_value_found(nil, "no options in select list") if elements.empty?
170
+
171
+ if multiple?
172
+ found = elements.select do |e|
173
+ next unless matches_regexp?(how, e, exp)
174
+ click_option(e) unless e.selected?
175
+ true
176
+ end
177
+
178
+ no_value_found(exp) if found.empty?
179
+
180
+ found.first.text
181
+ else
182
+ element = elements.find { |e| matches_regexp?(how, e, exp) }
183
+ no_value_found(exp) unless element
184
+
185
+ click_option(element) unless element.selected?
186
+ safe_text(element)
187
+ end
188
+ end
189
+
190
+ def option_xpath_for(how, string)
191
+ string = XpathSupport.escape string
192
+
193
+ case how
194
+ when :text
195
+ ".//option[normalize-space()=#{string} or @label=#{string}]"
196
+ when :value
197
+ ".//option[@value=#{string}]"
198
+ else
199
+ raise Error, "unknown how: #{how.inspect}"
200
+ end
201
+ end
202
+
203
+ def matches_regexp?(how, element, exp)
204
+ case how
205
+ when :text
206
+ element.text =~ exp || element.attribute(:label) =~ exp
207
+ when :value
208
+ element.attribute(:value) =~ exp
209
+ else
210
+ raise Error, "unknown how: #{how.inspect}"
211
+ end
212
+ end
213
+
214
+ def click_option(element)
215
+ element = Option.new(self, element: element) unless element.is_a?(Option)
216
+ element.click
217
+ end
218
+
219
+ def safe_text(element)
220
+ element.text
221
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::UnhandledAlertError
222
+ # guard for scenario where selecting the element changes the page, making our element obsolete
223
+
224
+ ''
225
+ end
226
+
227
+ def no_value_found(arg, msg = nil)
228
+ raise NoValueFoundException, msg || "#{arg.inspect} not found in select list"
229
+ end
230
+ end # Select
231
+
232
+ module Container
233
+ alias_method :select_list, :select
234
+ alias_method :select_lists, :selects
235
+
236
+ Watir.tag_to_class[:select_list] = Select
237
+ end # Container
238
+ end # Watir