webrat 0.2.0 → 0.3.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.
- data/History.txt +76 -14
- data/README.txt +40 -36
- data/Rakefile +80 -18
- data/TODO.txt +9 -3
- data/init.rb +1 -1
- data/lib/webrat.rb +30 -5
- data/lib/webrat/core.rb +12 -0
- data/lib/webrat/core/area.rb +44 -0
- data/lib/webrat/core/field.rb +332 -0
- data/lib/webrat/core/flunk.rb +7 -0
- data/lib/webrat/core/form.rb +130 -0
- data/lib/webrat/core/label.rb +18 -0
- data/lib/webrat/core/link.rb +101 -0
- data/lib/webrat/core/locators.rb +92 -0
- data/lib/webrat/core/logging.rb +25 -0
- data/lib/webrat/core/matchers.rb +4 -0
- data/lib/webrat/core/matchers/have_content.rb +94 -0
- data/lib/webrat/core/matchers/have_selector.rb +39 -0
- data/lib/webrat/core/matchers/have_tag.rb +58 -0
- data/lib/webrat/core/matchers/have_xpath.rb +85 -0
- data/lib/webrat/core/methods.rb +44 -0
- data/lib/webrat/core/mime.rb +29 -0
- data/lib/webrat/core/nokogiri.rb +42 -0
- data/lib/webrat/core/scope.rb +208 -0
- data/lib/webrat/core/select_option.rb +29 -0
- data/lib/webrat/core/session.rb +188 -0
- data/lib/webrat/core_extensions/blank.rb +58 -0
- data/lib/webrat/core_extensions/deprecate.rb +8 -0
- data/lib/webrat/core_extensions/detect_mapped.rb +12 -0
- data/lib/webrat/core_extensions/hash_with_indifferent_access.rb +131 -0
- data/lib/webrat/core_extensions/meta_class.rb +6 -0
- data/lib/webrat/core_extensions/nil_to_param.rb +5 -0
- data/lib/webrat/mechanize.rb +28 -0
- data/lib/webrat/merb.rb +75 -0
- data/lib/webrat/rack.rb +24 -0
- data/lib/webrat/rails.rb +102 -0
- data/lib/webrat/rails/redirect_actions.rb +18 -0
- data/lib/webrat/selenium.rb +3 -0
- data/lib/webrat/selenium/location_strategy_javascript/button.js +12 -0
- data/lib/webrat/selenium/location_strategy_javascript/label.js +16 -0
- data/lib/webrat/selenium/location_strategy_javascript/webrat.js +5 -0
- data/lib/webrat/selenium/location_strategy_javascript/webratlink.js +9 -0
- data/lib/webrat/selenium/location_strategy_javascript/webratlinkwithin.js +15 -0
- data/lib/webrat/selenium/location_strategy_javascript/webratselectwithoption.js +5 -0
- data/lib/webrat/selenium/selenium_extensions.js +6 -0
- data/lib/webrat/selenium/selenium_session.rb +137 -0
- data/lib/webrat/sinatra.rb +19 -0
- metadata +66 -52
- data/Manifest.txt +0 -20
- data/lib/webrat/rails_extensions.rb +0 -27
- data/lib/webrat/session.rb +0 -523
- data/test/checks_test.rb +0 -121
- data/test/chooses_test.rb +0 -74
- data/test/clicks_button_test.rb +0 -308
- data/test/clicks_link_test.rb +0 -193
- data/test/fills_in_test.rb +0 -139
- data/test/helper.rb +0 -21
- data/test/reloads_test.rb +0 -26
- data/test/selects_test.rb +0 -93
- data/test/visits_test.rb +0 -31
@@ -0,0 +1,332 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "webrat/core_extensions/blank"
|
3
|
+
require "webrat/core_extensions/nil_to_param"
|
4
|
+
|
5
|
+
module Webrat
|
6
|
+
class Field #:nodoc:
|
7
|
+
|
8
|
+
def self.class_for_element(element)
|
9
|
+
if element.name == "input"
|
10
|
+
if %w[submit image].include?(element["type"])
|
11
|
+
field_class = "button"
|
12
|
+
else
|
13
|
+
field_class = element["type"] || "text" #default type; 'type' attribute is not mandatory
|
14
|
+
end
|
15
|
+
else
|
16
|
+
field_class = element.name
|
17
|
+
end
|
18
|
+
Webrat.const_get("#{field_class.capitalize}Field")
|
19
|
+
rescue NameError
|
20
|
+
raise "Invalid field element: #{element.inspect}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(form, element)
|
24
|
+
@form = form
|
25
|
+
@element = element
|
26
|
+
|
27
|
+
@value = default_value
|
28
|
+
end
|
29
|
+
|
30
|
+
def label_text
|
31
|
+
return nil if labels.empty?
|
32
|
+
labels.first.text
|
33
|
+
end
|
34
|
+
|
35
|
+
def matches_id?(id)
|
36
|
+
@element["id"] == id.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def matches_name?(name)
|
40
|
+
@element["name"] == name.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def matches_label?(label_text)
|
44
|
+
return false if labels.empty?
|
45
|
+
labels.any? { |label| label.matches_text?(label_text) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def matches_alt?(alt)
|
49
|
+
@element["alt"] =~ /^\W*#{Regexp.escape(alt.to_s)}/i
|
50
|
+
end
|
51
|
+
|
52
|
+
def disabled?
|
53
|
+
@element.attributes.has_key?("disabled") && @element["disabled"] != 'false'
|
54
|
+
end
|
55
|
+
|
56
|
+
def raise_error_if_disabled
|
57
|
+
raise "Cannot interact with disabled form element (#{self})" if disabled?
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_param
|
61
|
+
return nil if disabled?
|
62
|
+
|
63
|
+
key_and_value = "#{name}=#{escaped_value}"
|
64
|
+
|
65
|
+
if defined?(CGIMethods)
|
66
|
+
CGIMethods.parse_query_parameters(key_and_value)
|
67
|
+
elsif defined?(ActionController::AbstractRequest)
|
68
|
+
ActionController::AbstractRequest.parse_query_parameters(key_and_value)
|
69
|
+
else
|
70
|
+
::Merb::Parse.query(key_and_value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def set(value)
|
75
|
+
@value = value
|
76
|
+
end
|
77
|
+
|
78
|
+
def unset
|
79
|
+
@value = default_value
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def id
|
85
|
+
@element["id"]
|
86
|
+
end
|
87
|
+
|
88
|
+
def name
|
89
|
+
@element["name"]
|
90
|
+
end
|
91
|
+
|
92
|
+
def escaped_value
|
93
|
+
CGI.escape(@value.to_s)
|
94
|
+
end
|
95
|
+
|
96
|
+
def labels
|
97
|
+
@labels ||= label_elements.map { |element| Label.new(self, element) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def label_elements
|
101
|
+
return @label_elements unless @label_elements.nil?
|
102
|
+
@label_elements = []
|
103
|
+
|
104
|
+
parent = @element.parent
|
105
|
+
while parent.respond_to?(:parent)
|
106
|
+
if parent.name == 'label'
|
107
|
+
@label_elements.push parent
|
108
|
+
break
|
109
|
+
end
|
110
|
+
parent = parent.parent
|
111
|
+
end
|
112
|
+
|
113
|
+
unless id.blank?
|
114
|
+
@label_elements += @form.element.search("label[@for='#{id}']")
|
115
|
+
end
|
116
|
+
|
117
|
+
@label_elements
|
118
|
+
end
|
119
|
+
|
120
|
+
def default_value
|
121
|
+
@element["value"]
|
122
|
+
end
|
123
|
+
|
124
|
+
def replace_param_value(params, oval, nval)
|
125
|
+
output = Hash.new
|
126
|
+
params.each do |key, value|
|
127
|
+
case value
|
128
|
+
when Hash
|
129
|
+
value = replace_param_value(value, oval, nval)
|
130
|
+
when Array
|
131
|
+
value = value.map { |o| o == oval ? nval : oval }
|
132
|
+
when oval
|
133
|
+
value = nval
|
134
|
+
end
|
135
|
+
output[key] = value
|
136
|
+
end
|
137
|
+
output
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class ButtonField < Field #:nodoc:
|
142
|
+
|
143
|
+
def matches_text?(text)
|
144
|
+
@element.inner_html =~ /#{Regexp.escape(text.to_s)}/i
|
145
|
+
end
|
146
|
+
|
147
|
+
def matches_value?(value)
|
148
|
+
@element["value"] =~ /^\W*#{Regexp.escape(value.to_s)}/i || matches_text?(value) || matches_alt?(value)
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_param
|
152
|
+
return nil if @value.nil?
|
153
|
+
super
|
154
|
+
end
|
155
|
+
|
156
|
+
def default_value
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def click
|
161
|
+
raise_error_if_disabled
|
162
|
+
set(@element["value"]) unless @element["name"].blank?
|
163
|
+
@form.submit
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
class HiddenField < Field #:nodoc:
|
169
|
+
|
170
|
+
def to_param
|
171
|
+
if collection_name?
|
172
|
+
super
|
173
|
+
else
|
174
|
+
checkbox_with_same_name = @form.field(name, CheckboxField)
|
175
|
+
|
176
|
+
if checkbox_with_same_name.to_param.blank?
|
177
|
+
super
|
178
|
+
else
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
protected
|
185
|
+
|
186
|
+
def collection_name?
|
187
|
+
name =~ /\[\]/
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
class CheckboxField < Field #:nodoc:
|
193
|
+
|
194
|
+
def to_param
|
195
|
+
return nil if @value.nil?
|
196
|
+
super
|
197
|
+
end
|
198
|
+
|
199
|
+
def check
|
200
|
+
raise_error_if_disabled
|
201
|
+
set(@element["value"] || "on")
|
202
|
+
end
|
203
|
+
|
204
|
+
def checked?
|
205
|
+
@element["checked"] == "checked"
|
206
|
+
end
|
207
|
+
|
208
|
+
def uncheck
|
209
|
+
raise_error_if_disabled
|
210
|
+
set(nil)
|
211
|
+
end
|
212
|
+
|
213
|
+
protected
|
214
|
+
|
215
|
+
def default_value
|
216
|
+
if @element["checked"] == "checked"
|
217
|
+
@element["value"] || "on"
|
218
|
+
else
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
class PasswordField < Field #:nodoc:
|
226
|
+
end
|
227
|
+
|
228
|
+
class RadioField < Field #:nodoc:
|
229
|
+
|
230
|
+
def to_param
|
231
|
+
return nil if @value.nil?
|
232
|
+
super
|
233
|
+
end
|
234
|
+
|
235
|
+
def choose
|
236
|
+
raise_error_if_disabled
|
237
|
+
other_options.each do |option|
|
238
|
+
option.set(nil)
|
239
|
+
end
|
240
|
+
|
241
|
+
set(@element["value"] || "on")
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
|
246
|
+
def other_options
|
247
|
+
@form.fields.select { |f| f.name == name }
|
248
|
+
end
|
249
|
+
|
250
|
+
def default_value
|
251
|
+
if @element["checked"] == "checked"
|
252
|
+
@element["value"] || "on"
|
253
|
+
else
|
254
|
+
nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
class TextareaField < Field #:nodoc:
|
261
|
+
|
262
|
+
protected
|
263
|
+
|
264
|
+
def default_value
|
265
|
+
@element.inner_html
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
class FileField < Field #:nodoc:
|
271
|
+
|
272
|
+
attr_accessor :content_type
|
273
|
+
|
274
|
+
def set(value, content_type = nil)
|
275
|
+
super(value)
|
276
|
+
@content_type = content_type
|
277
|
+
end
|
278
|
+
|
279
|
+
def to_param
|
280
|
+
if @value.nil?
|
281
|
+
super
|
282
|
+
else
|
283
|
+
replace_param_value(super, @value, test_uploaded_file)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def test_uploaded_file
|
290
|
+
if content_type
|
291
|
+
ActionController::TestUploadedFile.new(@value, content_type)
|
292
|
+
else
|
293
|
+
ActionController::TestUploadedFile.new(@value)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
class TextField < Field #:nodoc:
|
300
|
+
end
|
301
|
+
|
302
|
+
class ResetField < Field #:nodoc:
|
303
|
+
end
|
304
|
+
|
305
|
+
class SelectField < Field #:nodoc:
|
306
|
+
|
307
|
+
def find_option(text)
|
308
|
+
options.detect { |o| o.matches_text?(text) }
|
309
|
+
end
|
310
|
+
|
311
|
+
protected
|
312
|
+
|
313
|
+
def default_value
|
314
|
+
selected_options = @element.search(".//option[@selected='selected']")
|
315
|
+
selected_options = @element.search(".//option[position() = 1]") if selected_options.empty?
|
316
|
+
|
317
|
+
selected_options.map do |option|
|
318
|
+
return "" if option.nil?
|
319
|
+
option["value"] || option.inner_html
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def options
|
324
|
+
option_elements.map { |oe| SelectOption.new(self, oe) }
|
325
|
+
end
|
326
|
+
|
327
|
+
def option_elements
|
328
|
+
@element.search(".//option")
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require "webrat/core/field"
|
2
|
+
require "webrat/core_extensions/blank"
|
3
|
+
|
4
|
+
module Webrat
|
5
|
+
class Form #:nodoc:
|
6
|
+
attr_reader :element
|
7
|
+
|
8
|
+
def initialize(session, element)
|
9
|
+
@session = session
|
10
|
+
@element = element
|
11
|
+
@fields = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def field(locator, *field_types)
|
15
|
+
field_with_id(locator, *field_types) ||
|
16
|
+
field_named(locator, *field_types) ||
|
17
|
+
field_labeled(locator, *field_types) ||
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_select_option(option_text)
|
22
|
+
select_fields = fields_by_type([SelectField])
|
23
|
+
|
24
|
+
select_fields.each do |select_field|
|
25
|
+
result = select_field.find_option(option_text)
|
26
|
+
return result if result
|
27
|
+
end
|
28
|
+
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_button(value = nil)
|
33
|
+
return fields_by_type([ButtonField]).first if value.nil?
|
34
|
+
possible_buttons = fields_by_type([ButtonField])
|
35
|
+
possible_buttons.detect { |possible_button| possible_button.matches_id?(value) } ||
|
36
|
+
possible_buttons.detect { |possible_button| possible_button.matches_value?(value) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def fields
|
40
|
+
return @fields if @fields
|
41
|
+
|
42
|
+
@fields = (@element.search(".//button", ".//input", ".//textarea", ".//select")).collect do |field_element|
|
43
|
+
Field.class_for_element(field_element).new(self, field_element)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def submit
|
48
|
+
@session.request_page(form_action, form_method, params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_with_id(id, *field_types)
|
52
|
+
possible_fields = fields_by_type(field_types)
|
53
|
+
possible_fields.detect { |possible_field| possible_field.matches_id?(id) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def field_named(name, *field_types)
|
57
|
+
possible_fields = fields_by_type(field_types)
|
58
|
+
possible_fields.detect { |possible_field| possible_field.matches_name?(name) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def field_labeled(label, *field_types)
|
62
|
+
possible_fields = fields_by_type(field_types)
|
63
|
+
matching_fields = possible_fields.select do |possible_field|
|
64
|
+
possible_field.matches_label?(label)
|
65
|
+
end
|
66
|
+
matching_fields.min { |a, b| a.label_text.length <=> b.label_text.length }
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def fields_by_type(field_types)
|
72
|
+
if field_types.any?
|
73
|
+
fields.select { |f| field_types.include?(f.class) }
|
74
|
+
else
|
75
|
+
fields
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def params
|
80
|
+
all_params = {}
|
81
|
+
|
82
|
+
fields.each do |field|
|
83
|
+
next if field.to_param.nil?
|
84
|
+
merge(all_params, field.to_param)
|
85
|
+
end
|
86
|
+
|
87
|
+
all_params
|
88
|
+
end
|
89
|
+
|
90
|
+
def form_method
|
91
|
+
@element["method"].blank? ? :get : @element["method"].downcase
|
92
|
+
end
|
93
|
+
|
94
|
+
def form_action
|
95
|
+
@element["action"].blank? ? @session.current_url : @element["action"]
|
96
|
+
end
|
97
|
+
|
98
|
+
HASH = [Hash, HashWithIndifferentAccess] rescue [Hash]
|
99
|
+
|
100
|
+
def merge(all_params, new_param)
|
101
|
+
new_param.each do |key, value|
|
102
|
+
case all_params[key]
|
103
|
+
when *HASH
|
104
|
+
merge_hash_values(all_params[key], value)
|
105
|
+
when Array
|
106
|
+
all_params[key] += value
|
107
|
+
else
|
108
|
+
all_params[key] = value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def merge_hash_values(a, b) # :nodoc:
|
114
|
+
a.keys.each do |k|
|
115
|
+
if b.has_key?(k)
|
116
|
+
case [a[k], b[k]].map{|value| value.class}
|
117
|
+
when [Hash, Hash]
|
118
|
+
a[k] = merge_hash_values(a[k], b[k])
|
119
|
+
b.delete(k)
|
120
|
+
when [Array, Array]
|
121
|
+
a[k] += b[k]
|
122
|
+
b.delete(k)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
a.merge!(b)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|