webrat 0.2.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.
@@ -0,0 +1,37 @@
1
+ == 0.2.0 / 2008-04-04
2
+
3
+ * 4 Major enhancements
4
+
5
+ * Add save_and_open_page to aid in debugging
6
+ * Add radio button support via #chooses method
7
+ * Add basic support for Rails-generated JavaScript link tags
8
+ * Add support for checkboxes (Patches from Kyle Hargraves and Jarkko Laine)
9
+ * Add support for textarea fields (Patch from Sacha Schlegel)
10
+
11
+ * 8 Minor enhancements
12
+
13
+ * Added reloads method to reload the page (Patch from Kamal Fariz Mahyuddi)
14
+ * Prevent making a request if clicking on local anchor link (Patch from Kamal Fariz Mahyuddi)
15
+ * Added clicks_link_within(selector, link_text), allowing restricting link search
16
+ to within a given css selector (Path from Luke Melia)
17
+ * Allow specifying the input name/label when doing a select (Patch from David Chelimsky)
18
+ * Raise a specific exception if the developer tries to manipulate form elements before loading a page (Patch from James Deville)
19
+ * Add support for alternate POST, PUT and DELETE link clicking (Patch from Kyle Hargraves)
20
+ * Change clicks_link to find the shortest matching link (Patch from Luke Melia)
21
+ * Improve matching for labels in potentially ambiguous cases
22
+
23
+ * 7 Bug fixes
24
+
25
+ * Fix incorrect serializing of collection inputs, i.e. name contains [] (Patch from Kamal Fariz Mahyuddi)
26
+ * Serialize empty text field values just like browsers (Patch from Kamal Fariz Mahyuddi)
27
+ * Quick fix to avoid @dom not initialized warnings (Patch from Kamal Fariz Mahyuddi)
28
+ * Docfix: bad reference to #select method in README (Patch from Luke Melia)
29
+ * Ensure Rails-style checkboxes work properly (checkboxes followed by a hidden input with the same name)
30
+ * Fix Edge Rails (a.k.a. 2.0 RC) compatibility (Patch from David Chelimsky)
31
+ * Support param hashes nested more than one level (Patch from David Chelimsky)
32
+
33
+ == 0.1.0 / 2007-11-28
34
+
35
+ * 1 major enhancement
36
+ * Birthday!
37
+
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2007 Bryan Helmkamp, Seth Fitzsimmons
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,20 @@
1
+ History.txt
2
+ MIT-LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ TODO.txt
7
+ init.rb
8
+ install.rb
9
+ lib/webrat.rb
10
+ lib/webrat/rails_extensions.rb
11
+ lib/webrat/session.rb
12
+ test/checks_test.rb
13
+ test/chooses_test.rb
14
+ test/clicks_button_test.rb
15
+ test/clicks_link_test.rb
16
+ test/fills_in_test.rb
17
+ test/helper.rb
18
+ test/reloads_test.rb
19
+ test/selects_test.rb
20
+ test/visits_test.rb
@@ -0,0 +1,86 @@
1
+ = Webrat - Ruby Acceptance Testing for Web applications
2
+
3
+ http://rubyforge.org/projects/webrat
4
+ http://github.com/brynary/webrat
5
+
6
+ * mailto:bryan@brynary.com
7
+ * mailto:seth@mojodna.net
8
+
9
+ == DESCRIPTION:
10
+
11
+ Webrat lets you quickly write robust and thorough acceptance tests for a Ruby
12
+ web application. By leveraging the DOM, it can run tests similarly to an
13
+ in-browser testing solution without the associated performance hit (and
14
+ browser dependency). The result is tests that are less fragile and more
15
+ effective at verifying that the app will respond properly to users.
16
+
17
+ When comparing Webrat with an in-browser testing solution like Watir or
18
+ Selenium, the primary consideration should be how much JavaScript the
19
+ application uses. In-browser testing is currently the only way to test JS, and
20
+ that may make it a requirement for your project. If JavaScript is not central
21
+ to your application, Webrat is a simpler, effective solution that will let you
22
+ run your tests much faster and more frequently. (Benchmarks forthcoming.)
23
+
24
+ Initial development was sponsored by EastMedia (http://www.eastmedia.com).
25
+
26
+ == SYNOPSIS:
27
+
28
+ def test_sign_up
29
+ visits "/"
30
+ clicks_link "Sign up"
31
+ fills_in "Email", :with => "good@example.com"
32
+ selects "Free account"
33
+ clicks_button "Register"
34
+ ...
35
+ end
36
+
37
+ Behind the scenes, this will perform the following work:
38
+
39
+ 1. Verify that loading the home page is successful
40
+ 2. Verify that a "Sign up" link exists on the home page
41
+ 3. Verify that loading the URL pointed to by the "Sign up" link leads to a
42
+ successful page
43
+ 4. Verify that there is an "Email" input field on the Sign Up page
44
+ 5. Verify that there is an select field on the Sign Up page with an option for
45
+ "Free account"
46
+ 6. Verify that there is a "Register" submit button on the page
47
+ 7. Verify that submitting the Sign Up form with the values "good@example.com"
48
+ and "Free account" leads to a successful page
49
+
50
+ Take special note of the things _not_ specified in that test, that might cause
51
+ tests to break unnecessarily as your application evolves:
52
+
53
+ * The input field IDs or names (e.g. "user_email" or "user[email]"), which
54
+ could change if you rename a model
55
+ * The ID of the form element (Webrat can do a good job of guessing, even if
56
+ there are multiple forms on the page.)
57
+ * The URLs of links followed
58
+ * The URL the form submission should be sent to, which could change if you
59
+ adjust your routes or controllers
60
+ * The HTTP method for the login request
61
+
62
+ A test written with Webrat can handle these changes smoothly.
63
+
64
+ == REQUIREMENTS:
65
+
66
+ * Rails >= 1.2.6
67
+ * Hpricot >= 0.6
68
+ * Rails integration tests in Test::Unit _or_
69
+ * RSpec stories (using an RSpec version >= revision 2997)
70
+
71
+ == INSTALL:
72
+
73
+ In your stories/helper.rb:
74
+
75
+ require "webrat"
76
+
77
+ You could also unpack the gem into vendor/plugins.
78
+
79
+ == HISTORY:
80
+
81
+ See CHANGELOG in this directory.
82
+
83
+ == LICENSE:
84
+
85
+ Copyright (c) 2007 Bryan Helmkamp, Seth Fitzsimmons.
86
+ See MIT-LICENSE in this directory.
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/webrat.rb'
4
+
5
+ Hoe.new('webrat', Webrat::VERSION) do |p|
6
+ p.rubyforge_name = 'webrat'
7
+ p.summary = 'Ruby Acceptance Testing for Web applications'
8
+
9
+ p.developer "Bryan Helmkamp", "bryan@brynary.com"
10
+ p.developer "Seth Fitzsimmons", "seth@mojodna.net"
11
+
12
+ # require "rubygems"; require "ruby-debug"; Debugger.start; debugger
13
+
14
+ p.description = p.paragraphs_of('README.txt', 4..6).join("\n\n")
15
+ p.url = p.paragraphs_of('README.txt', 1).first.split("\n").first.strip
16
+ p.changes = p.paragraphs_of('History.txt', 0..3).join("\n\n")
17
+
18
+ p.extra_deps << ["hpricot", ">= 0.6"]
19
+
20
+ p.remote_rdoc_dir = '' # Release to root
21
+ p.test_globs = ['test/**/*_test.rb']
22
+ end
23
+
24
+ desc "Upload rdoc to brynary.com"
25
+ task :publish_rdoc => :docs do
26
+ sh "scp -r doc/ brynary.com:/apps/uploads/webrat"
27
+ end
@@ -0,0 +1,4 @@
1
+ Full support for multiple forms on a page
2
+ Track the current form based on the location of the last manipulated input, use this as a default for clicks_button
3
+ Make current_url work with redirections
4
+ Support for a hash mapping page names to page URLs
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ if RAILS_ENV == "test"
2
+ require File.join(File.dirname(__FILE__), "lib", "webrat")
3
+ end
@@ -0,0 +1 @@
1
+ puts IO.read(File.join(File.dirname(__FILE__), 'README'))
@@ -0,0 +1,6 @@
1
+ require File.join(File.dirname(__FILE__), "webrat", "rails_extensions")
2
+ require File.join(File.dirname(__FILE__), "webrat", "session")
3
+
4
+ class Webrat
5
+ VERSION = '0.2.0'
6
+ end
@@ -0,0 +1,27 @@
1
+ module ActionController
2
+ module Integration
3
+
4
+ class Session
5
+
6
+ unless instance_methods.include?("put_via_redirect")
7
+ # Waiting for http://dev.rubyonrails.org/ticket/10497 to be committed.
8
+ def put_via_redirect(path, parameters = {}, headers = {})
9
+ put path, parameters, headers
10
+ follow_redirect! while redirect?
11
+ status
12
+ end
13
+ end
14
+
15
+ unless instance_methods.include?("delete_via_redirect")
16
+ # Waiting for http://dev.rubyonrails.org/ticket/10497 to be committed.
17
+ def delete_via_redirect(path, parameters = {}, headers = {})
18
+ delete path, parameters, headers
19
+ follow_redirect! while redirect?
20
+ status
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,523 @@
1
+ require "hpricot"
2
+ require "English"
3
+
4
+ module ActionController
5
+ module Integration
6
+
7
+ class Session
8
+ # Issues a GET request for a page, follows any redirects, and verifies the final page
9
+ # load was successful.
10
+ #
11
+ # Example:
12
+ # visits "/"
13
+ def visits(path)
14
+ request_page(:get, path)
15
+ end
16
+
17
+ # Issues a request for the URL pointed to by a link on the current page,
18
+ # follows any redirects, and verifies the final page load was successful.
19
+ #
20
+ # clicks_link has very basic support for detecting Rails-generated
21
+ # JavaScript onclick handlers for PUT, POST and DELETE links, as well as
22
+ # CSRF authenticity tokens if they are present.
23
+ #
24
+ # Example:
25
+ # clicks_link "Sign up"
26
+ def clicks_link(link_text)
27
+ clicks_one_link_of(all_links, link_text)
28
+ end
29
+
30
+ # Works like clicks_link, but only looks for the link text within a given selector
31
+ #
32
+ # Example:
33
+ # clicks_link_within "#user_12", "Vote"
34
+ def clicks_link_within(selector, link_text)
35
+ clicks_one_link_of(links_within(selector), link_text)
36
+ end
37
+
38
+ # Works like clicks_link, but forces a GET request
39
+ #
40
+ # Example:
41
+ # clicks_get_link "Log out"
42
+ def clicks_get_link(link_text)
43
+ clicks_link_with_method(link_text, :get)
44
+ end
45
+
46
+ # Works like clicks_link, but issues a DELETE request instead of a GET
47
+ #
48
+ # Example:
49
+ # clicks_delete_link "Log out"
50
+ def clicks_delete_link(link_text)
51
+ clicks_link_with_method(link_text, :delete)
52
+ end
53
+
54
+ # Works like clicks_link, but issues a POST request instead of a GET
55
+ #
56
+ # Example:
57
+ # clicks_post_link "Vote"
58
+ def clicks_post_link(link_text)
59
+ clicks_link_with_method(link_text, :post)
60
+ end
61
+
62
+ # Works like clicks_link, but issues a PUT request instead of a GET
63
+ #
64
+ # Example:
65
+ # clicks_put_link "Update profile"
66
+ def clicks_put_link(link_text)
67
+ clicks_link_with_method(link_text, :put)
68
+ end
69
+
70
+ # Verifies an input field or textarea exists on the current page, and stores a value for
71
+ # it which will be sent when the form is submitted.
72
+ #
73
+ # Examples:
74
+ # fills_in "Email", :with => "user@example.com"
75
+ # fills_in "user[email]", :with => "user@example.com"
76
+ #
77
+ # The field value is required, and must be specified in <tt>options[:with]</tt>.
78
+ # <tt>field</tt> can be either the value of a name attribute (i.e. <tt>user[email]</tt>)
79
+ # or the text inside a <tt><label></tt> element that points at the <tt><input></tt> field.
80
+ def fills_in(field, options = {})
81
+ value = options[:with]
82
+ return flunk("No value was provided") if value.nil?
83
+ input = find_field_by_name_or_label(field)
84
+ return flunk("Could not find input #{field.inspect}") if input.nil?
85
+ add_form_data(input, value)
86
+ # TODO - Set current form
87
+ end
88
+
89
+ # Verifies that a an option element exists on the current page with the specified
90
+ # text. You can optionally restrict the search to a specific select list by
91
+ # assigning <tt>options[:from]</tt> the value of the select list's name or
92
+ # a label. Stores the option's value to be sent when the form is submitted.
93
+ #
94
+ # Examples:
95
+ # selects "January"
96
+ # selects "February", :from => "event_month"
97
+ # selects "February", :from => "Event Month"
98
+ def selects(option_text, options = {})
99
+ if options[:from]
100
+ select = find_select_list_by_name_or_label(options[:from])
101
+ return flunk("Could not find select list #{options[:from].inspect}") if select.nil?
102
+ option_node = find_option_by_value(option_text, select)
103
+ return flunk("Could not find option #{option_text.inspect}") if option_node.nil?
104
+ else
105
+ option_node = find_option_by_value(option_text)
106
+ return flunk("Could not find option #{option_text.inspect}") if option_node.nil?
107
+ select = option_node.parent
108
+ end
109
+ add_form_data(select, option_node.attributes["value"] || option_node.innerHTML)
110
+ # TODO - Set current form
111
+ end
112
+
113
+ # Verifies that an input checkbox exists on the current page and marks it
114
+ # as checked, so that the value will be submitted with the form.
115
+ #
116
+ # Example:
117
+ # checks 'Remember Me'
118
+ def checks(field)
119
+ checkbox = find_field_by_name_or_label(field)
120
+ return flunk("Could not find checkbox #{field.inspect}") if checkbox.nil?
121
+ return flunk("Input #{checkbox.inspect} is not a checkbox") unless checkbox.attributes['type'] == 'checkbox'
122
+ add_form_data(checkbox, checkbox.attributes["value"] || "on")
123
+ end
124
+
125
+ # Verifies that an input checkbox exists on the current page and marks it
126
+ # as unchecked, so that the value will not be submitted with the form.
127
+ #
128
+ # Example:
129
+ # unchecks 'Remember Me'
130
+ def unchecks(field)
131
+ checkbox = find_field_by_name_or_label(field)
132
+ return flunk("Could not find checkbox #{field.inspect}") if checkbox.nil?
133
+ return flunk("Input #{checkbox.inspect} is not a checkbox") unless checkbox.attributes['type'] == 'checkbox'
134
+ remove_form_data(checkbox)
135
+
136
+ (form_for_node(checkbox) / "input").each do |input|
137
+ next unless input.attributes["type"] == "hidden" && input.attributes["name"] == checkbox.attributes["name"]
138
+ add_form_data(input, input.attributes["value"])
139
+ end
140
+ end
141
+
142
+ # Verifies that an input radio button exists on the current page and marks it
143
+ # as checked, so that the value will be submitted with the form.
144
+ #
145
+ # Example:
146
+ # chooses 'First Option'
147
+ def chooses(field)
148
+ radio = find_field_by_name_or_label(field)
149
+ return flunk("Could not find radio button #{field.inspect}") if radio.nil?
150
+ return flunk("Input #{radio.inspect} is not a radio button") unless radio.attributes['type'] == 'radio'
151
+ add_form_data(radio, radio.attributes["value"] || "on")
152
+ end
153
+
154
+ # Verifies that a submit button exists for the form, then submits the form, follows
155
+ # any redirects, and verifies the final page was successful.
156
+ #
157
+ # Example:
158
+ # clicks_button "Login"
159
+ # clicks_button
160
+ #
161
+ # The URL and HTTP method for the form submission are automatically read from the
162
+ # <tt>action</tt> and <tt>method</tt> attributes of the <tt><form></tt> element.
163
+ def clicks_button(value = nil)
164
+ button = value ? find_button(value) : submit_buttons.first
165
+ return flunk("Could not find button #{value.inspect}") if button.nil?
166
+ add_form_data(button, button.attributes["value"]) unless button.attributes["name"].blank?
167
+ submit_form(form_for_node(button))
168
+ end
169
+
170
+ def submits_form(form_id = nil) # :nodoc:
171
+ end
172
+
173
+ # Saves the currently loaded page out to RAILS_ROOT/tmp/ and opens it in the default
174
+ # web browser if on OS X. Useful for debugging.
175
+ #
176
+ # Example:
177
+ # save_and_open_page
178
+ def save_and_open_page
179
+ return unless File.exist?(RAILS_ROOT + "/tmp")
180
+
181
+ filename = "webrat-#{Time.now.to_i}.html"
182
+ File.open(RAILS_ROOT + "/tmp/#{filename}", "w") do |f|
183
+ f.write response.body
184
+ end
185
+ `open tmp/#{filename}`
186
+ end
187
+
188
+ # Reloads the last page requested. Note that this will resubmit forms
189
+ # and their data.
190
+ #
191
+ # Example:
192
+ # reloads
193
+ def reloads
194
+ request_page(*@last_request_args) if defined?(@last_request_args) && @last_request_args
195
+ end
196
+
197
+ protected # Methods you could call, but probably shouldn't
198
+
199
+ def authenticity_token_value(onclick)
200
+ return unless onclick && onclick.include?("s.setAttribute('name', 'authenticity_token');") &&
201
+ onclick =~ /s\.setAttribute\('value', '([a-f0-9]{40})'\);/
202
+ $LAST_MATCH_INFO.captures.first
203
+ end
204
+
205
+ def http_method_from_js(onclick)
206
+ if !onclick.blank? && onclick.include?("f.submit()")
207
+ http_method_from_js_form(onclick)
208
+ else
209
+ :get
210
+ end
211
+ end
212
+
213
+ def http_method_from_js_form(onclick)
214
+ if onclick.include?("m.setAttribute('name', '_method')")
215
+ http_method_from_fake_method_param(onclick)
216
+ else
217
+ :post
218
+ end
219
+ end
220
+
221
+ def http_method_from_fake_method_param(onclick)
222
+ if onclick.include?("m.setAttribute('value', 'delete')")
223
+ :delete
224
+ elsif onclick.include?("m.setAttribute('value', 'put')")
225
+ :put
226
+ else
227
+ raise "No HTTP method for _method param in #{onclick.inspect}"
228
+ end
229
+ end
230
+
231
+ def clicks_link_with_method(link_text, http_method) # :nodoc:
232
+ link = all_links.detect { |el| el.innerHTML =~ /#{link_text}/i }
233
+ return flunk("No link with text #{link_text.inspect} was found") if link.nil?
234
+ request_page(http_method, link.attributes["href"])
235
+ end
236
+
237
+ def find_shortest_matching_link(links, link_text)
238
+ candidates = links.select { |el| el.innerHTML =~ /#{link_text}/i }
239
+ candidates.sort_by { |el| el.innerText.strip.size }.first
240
+ end
241
+
242
+ def clicks_one_link_of(links, link_text)
243
+ link = find_shortest_matching_link(links, link_text)
244
+
245
+ return flunk("No link with text #{link_text.inspect} was found") if link.nil?
246
+
247
+ onclick = link.attributes["onclick"]
248
+ href = link.attributes["href"]
249
+
250
+ http_method = http_method_from_js(onclick)
251
+ authenticity_token = authenticity_token_value(onclick)
252
+
253
+ request_page(http_method, href, authenticity_token.blank? ? {} : {"authenticity_token" => authenticity_token}) unless href =~ /^#/ && http_method == :get
254
+ end
255
+
256
+ def find_field_by_name_or_label(name_or_label) # :nodoc:
257
+ input = find_field_by_name(name_or_label)
258
+ return input if input
259
+
260
+ label = find_form_label(name_or_label)
261
+ label ? input_for_label(label) : nil
262
+ end
263
+
264
+ def find_select_list_by_name_or_label(name_or_label) # :nodoc:
265
+ select = find_select_list_by_name(name_or_label)
266
+ return select if select
267
+
268
+ label = find_form_label(name_or_label)
269
+ label ? select_list_for_label(label) : nil
270
+ end
271
+
272
+ def find_option_by_value(option_value, select=nil) # :nodoc:
273
+ options = select.nil? ? option_nodes : (select / "option")
274
+ options.detect { |el| el.innerHTML == option_value }
275
+ end
276
+
277
+ def find_button(value = nil) # :nodoc:
278
+ return nil unless value
279
+ submit_buttons.detect { |el| el.attributes["value"] =~ /^\W*#{value}\b/i }
280
+ end
281
+
282
+ def add_form_data(input_element, value) # :nodoc:
283
+ form = form_for_node(input_element)
284
+ data = param_parser.parse_query_parameters("#{input_element.attributes["name"]}=#{value}")
285
+ merge_form_data(form_number(form), data)
286
+ end
287
+
288
+ def remove_form_data(input_element) # :nodoc:
289
+ form = form_for_node(input_element)
290
+ form_number = form_number(form)
291
+ form_data[form_number] ||= {}
292
+ form_data[form_number].delete(input_element.attributes['name'])
293
+ end
294
+
295
+ def submit_form(form) # :nodoc:
296
+ form_number = form_number(form)
297
+ request_page(form_method(form), form_action(form), form_data[form_number])
298
+ end
299
+
300
+ def merge_form_data(form_number, data) # :nodoc:
301
+ form_data[form_number] ||= {}
302
+
303
+ data.each do |key, value|
304
+ case form_data[form_number][key]
305
+ when Hash, HashWithIndifferentAccess; then merge(form_data[form_number][key], value)
306
+ when Array; then form_data[form_number][key] += value
307
+ else form_data[form_number][key] = value
308
+ end
309
+ end
310
+ end
311
+
312
+ def merge(a, b) # :nodoc:
313
+ a.keys.each do |k|
314
+ if b.has_key?(k)
315
+ case [a[k], b[k]].map(&:class)
316
+ when [Hash, Hash]
317
+ a[k] = merge(a[k], b[k])
318
+ b.delete(k)
319
+ when [Array, Array]
320
+ a[k] += b[k]
321
+ b.delete(k)
322
+ end
323
+ end
324
+ end
325
+ a.merge!(b)
326
+ end
327
+
328
+ def request_page(method, url, data = {}) # :nodoc:
329
+ debug_log "REQUESTING PAGE: #{method.to_s.upcase} #{url} with #{data.inspect}"
330
+ @current_url = url
331
+ @last_request_args = [method, url, data]
332
+ self.send "#{method}_via_redirect", @current_url, data || {}
333
+
334
+ if response.body =~ /Exception caught/ || response.body.blank?
335
+ save_and_open_page
336
+ end
337
+
338
+ assert_response :success
339
+ reset_dom
340
+ end
341
+
342
+ def input_for_label(label) # :nodoc:
343
+ if input = (label / "input").first
344
+ input # nested inputs within labels
345
+ else
346
+ # input somewhere else, referenced by id
347
+ input_id = label.attributes["for"]
348
+ (dom / "##{input_id}").first
349
+ end
350
+ end
351
+
352
+ def select_list_for_label(label) # :nodoc:
353
+ if select_list = (label / "select").first
354
+ select_list # nested inputs within labels
355
+ else
356
+ # input somewhere else, referenced by id
357
+ select_list_id = label.attributes["for"]
358
+ (dom / "##{select_list_id}").first
359
+ end
360
+ end
361
+
362
+ def param_parser # :nodoc:
363
+ if defined?(CGIMethods)
364
+ CGIMethods
365
+ else
366
+ ActionController::AbstractRequest
367
+ end
368
+ end
369
+
370
+ def submit_buttons # :nodoc:
371
+ input_fields.select { |el| el.attributes["type"] == "submit" }
372
+ end
373
+
374
+ def find_field_by_name(name) # :nodoc:
375
+ find_input_by_name(name) || find_textarea_by_name(name)
376
+ end
377
+
378
+ def find_input_by_name(name) # :nodoc:
379
+ input_fields.detect { |el| el.attributes["name"] == name }
380
+ end
381
+
382
+ def find_select_list_by_name(name) # :nodoc:
383
+ select_lists.detect { |el| el.attributes["name"] == name }
384
+ end
385
+
386
+ def find_textarea_by_name(name) # :nodoc:
387
+ textarea_fields.detect{ |el| el.attributes['name'] == name }
388
+ end
389
+
390
+ def find_form_label(text) # :nodoc:
391
+ candidates = form_labels.select { |el| el.innerText =~ /^\W*#{text}\b/i }
392
+ candidates.sort_by { |el| el.innerText.strip.size }.first
393
+ end
394
+
395
+ def form_action(form) # :nodoc:
396
+ form.attributes["action"].blank? ? current_url : form.attributes["action"]
397
+ end
398
+
399
+ def form_method(form) # :nodoc:
400
+ form.attributes["method"].blank? ? :get : form.attributes["method"].downcase
401
+ end
402
+
403
+ def add_default_params # :nodoc:
404
+ (dom / "form").each do |form|
405
+ add_default_params_for(form)
406
+ end
407
+ end
408
+
409
+ def add_default_params_for(form) # :nodoc:
410
+ add_default_params_from_inputs_for(form)
411
+ add_default_params_from_checkboxes_for(form)
412
+ add_default_params_from_radio_buttons_for(form)
413
+ add_default_params_from_textareas_for(form)
414
+ add_default_params_from_selects_for(form)
415
+ end
416
+
417
+ def add_default_params_from_inputs_for(form) # :nodoc:
418
+ (form / "input").each do |input|
419
+ next unless %w[text hidden].include?(input.attributes["type"])
420
+ add_form_data(input, input.attributes["value"])
421
+ end
422
+ end
423
+
424
+ def add_default_params_from_checkboxes_for(form) # :nodoc:
425
+ (form / "input[@type='checkbox][@checked='checked']").each do |input|
426
+ add_form_data(input, input.attributes["value"] || "on")
427
+ end
428
+ end
429
+
430
+ def add_default_params_from_radio_buttons_for(form) # :nodoc:
431
+ (form / "input[@type='radio][@checked='checked']").each do |input|
432
+ add_form_data(input, input.attributes["value"])
433
+ end
434
+ end
435
+
436
+ def add_default_params_from_textareas_for(form) # :nodoc:
437
+ (form / "textarea").each do |input|
438
+ add_form_data(input, input.inner_html)
439
+ end
440
+ end
441
+
442
+ def add_default_params_from_selects_for(form) # :nodoc:
443
+ (form / "select").each do |select|
444
+ selected_options = select / "option[@selected='selected']"
445
+ selected_options = select / "option:first" if selected_options.empty?
446
+ selected_options.each do |option|
447
+ add_form_data(select, option.attributes["value"] || option.innerHTML)
448
+ end
449
+ end
450
+ end
451
+
452
+ def form_for_node(node) # :nodoc:
453
+ return node if node.name == "form"
454
+ node = node.parent until node.name == "form"
455
+ node
456
+ end
457
+
458
+ def reset_dom # :nodoc:
459
+ @form_data = []
460
+ @dom = nil
461
+ end
462
+
463
+ def form_data # :nodoc:
464
+ @form_data ||= []
465
+ end
466
+
467
+ def all_links # :nodoc:
468
+ (dom / "a[@href]")
469
+ end
470
+
471
+ def links_within(selector) # :nodoc:
472
+ (dom / selector / "a[@href]")
473
+ end
474
+
475
+ def form_number(form) # :nodoc:
476
+ (dom / "form").index(form)
477
+ end
478
+
479
+ def input_fields # :nodoc:
480
+ (dom / "input")
481
+ end
482
+
483
+ def textarea_fields # :nodoc
484
+ (dom / "textarea")
485
+ end
486
+
487
+ def form_labels # :nodoc:
488
+ (dom / "label")
489
+ end
490
+
491
+ def select_lists # :nodoc:
492
+ (dom / "select")
493
+ end
494
+
495
+ def option_nodes # :nodoc:
496
+ (dom / "option")
497
+ end
498
+
499
+ def dom # :nodoc:
500
+ return @dom if defined?(@dom) && @dom
501
+ raise "You must visit a path before working with the page." unless response
502
+ @dom = Hpricot(response.body)
503
+ add_default_params
504
+ @dom
505
+ end
506
+
507
+ def debug_log(message) # :nodoc:
508
+ return unless logger
509
+ logger.debug
510
+ end
511
+
512
+ def logger # :nodoc:
513
+ if defined? RAILS_DEFAULT_LOGGER
514
+ RAILS_DEFAULT_LOGGER
515
+ else
516
+ nil
517
+ end
518
+ end
519
+
520
+ end
521
+
522
+ end
523
+ end