webrat 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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